mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
Merge remote-tracking branch 'origin/main' into capy/bun-entry-isolation
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # CHANGELOG.md # bin/gstack-relink # test/fixtures/skill-md-size-allowlist.json
This commit is contained in:
commit
1b54f47c1c
90 files changed
+866
-809
No files matched your search
+36
-1
@@ -15,13 +15,48 @@
|
||||
|
||||
### Changed
|
||||
- **`gstack-evidence run` no longer scrubs dotenv values from the child environment,** and `GSTACK_EVIDENCE_KEEP_DOTENV` is gone. Bun no longer loads those values into the wrapper, so the child sees your shell's environment, as it would if you ran the command by hand. The old value-equality scrub (#2652, @namtrok) also dropped shell-exported variables whose value matched the file.
|
||||
- **Raised SKILL.md size pins:** 80 pins in `test/fixtures/skill-md-size-allowlist.json` grew by 33 to 330 characters each, from the isolation flags alone (8,253 characters in total). For the same reason, ten parity caps in `test/helpers/carve-guards.ts` rose by the measured growth: ship, review, autoplan, spec, land-and-deploy and the three plan reviews (at most 132 skeleton bytes and 0.0016 union ratio).
|
||||
- **Raised SKILL.md size pins:** 80 pins in `test/fixtures/skill-md-size-allowlist.json` grew by 33 to 330 characters each, from the isolation flags alone (8,253 characters in total).
|
||||
- **Directly executed `./scripts/*.ts` files no longer read gstack's own `.env`.** `bun run <script>` and the package scripts (`bun run test`, `bun run eval:bg:pr`) still load it.
|
||||
|
||||
### If you…
|
||||
- **…add a bin or a hook:** start it with the isolated shebang, and pass `bun --no-env-file --config=/dev/null` (or `bunIsolationArgs()` in TypeScript) wherever you start Bun. If you don't, the ratchet names the line.
|
||||
- **…relied on a repo `.env` to configure gstack:** export the variable in your shell or set it with `gstack-config`.
|
||||
|
||||
## [1.91.158.0] - 2026-10-19
|
||||
|
||||
**`gstack-config set plan_mode_preamble off` leaves the plan-mode preamble out of every installed skill, for people who never use plan mode.** The "Plan Mode Safe Operations" and "Skill Invocation During Plan Mode" sections add about 1.6 KB to each of 50 Claude skills. With the key off, the next `./setup` renders every host's skills with `gen-skill-docs --plan-mode=off`, and the default Claude install serves that render instead of the committed files. The default is on, and with it on every render and the committed SKILL.md files are byte-for-byte unchanged.
|
||||
|
||||
### Added
|
||||
- **`gen-skill-docs --plan-mode=off`** drops the two sections at generation time. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" keep their marker. Hosts whose capabilities declare no plan mode already render the sections empty, so in practice the flag matters for Claude, Copilot and OpenCode. Fixes #3168. From #3213 by @xingzihai, finished with the config wiring.
|
||||
- **The `plan_mode_preamble` config key** (`on` | `off`, default `on`). It is a closed domain like `explain_level`: an unknown value warns and stores `on`. Setting it outside `./setup` says it applies on the next setup run and prints the `./setup --refresh-registered` command that applies it now.
|
||||
- **Every installed render carries the choice:** all 16 `gen:skill-docs` call sites in `setup`, the Claude render helper, `gstack-config gbrain-refresh`, and the router that `gstack-relink` re-renders in place (as it already does for `disabled_skills`). When setup has to build, `scripts/build.sh` renders Factory, OpenCode, Cursor, Copilot and Antigravity without the flag, so with the key off setup renders those hosts again afterwards.
|
||||
- **The default Claude install gets a render when the key is off.** Without a working gbrain (absent, or the detector failed), `gstack_claude_render_action` returns `plain` instead of serving the committed files; with gbrain the brain-aware render already runs and now carries the flag too. The activation record (`~/.gstack/render/claude.overlay`) gains `plan_mode=off`. Turning the key back on removes that render on the next setup when gbrain is absent or its detector fails (a failed detector otherwise keeps a render), and re-renders the brain-aware render with the sections when gbrain is present. A failed render with the key off keeps the previous render. Records written with the key on are unchanged.
|
||||
|
||||
### Fixed
|
||||
- **`gstack-relink` refreshes the `_gstack-command` router alias after it re-renders the router.** It used to copy the router before the re-render, so the alias kept the previous router (with `disabled_skills`, the full skill list) until the next relink.
|
||||
|
||||
### Changed
|
||||
- **Gate integrity:** new tests `test/plan-mode-off-build.test.ts` (from #3213, unchanged), `test/plan-mode-off-generation.test.ts`, `test/plan-mode-preamble-config.test.ts`, `test/plan-mode-preamble-render.test.ts` and `test/plan-mode-preamble-setup.test.ts`. No existing test was edited, and no threshold, trial count, case kind or judge changed.
|
||||
|
||||
## [1.91.157.0] - 2026-10-19
|
||||
|
||||
**Claude SKILL.md files no longer carry a "When to invoke" body section, which saves about 20 KB across 56 skills.** Claude Code reads a skill's body only after the skill is invoked, so routing prose there never routed anything. Routing still comes from the catalog description and the CLAUDE.md routing block, and voice triggers move into the root router's routing block. From #3129 by @thanhnguyentung95.
|
||||
|
||||
### Changed
|
||||
- **Catalog trim (`scripts/gen-skill-docs.ts`):** the Claude render keeps the one-line catalog description and no longer inserts the routing and voice-trigger prose into the body. `--catalog-mode=full` and the other hosts are unchanged: they keep the full description.
|
||||
- **Voice triggers in the root router:** a new `{{VOICE_TRIGGER_ROUTING}}` resolver (`scripts/resolvers/voice-triggers.ts`) adds one `- Voice trigger "…" → invoke /<skill>` line per skill to the root `SKILL.md` routing block, Claude only. That's 21 lines, and the root router's eager ceiling rises from 4289 to 4821 tokens.
|
||||
- **Context budget:** 54 per-skill eager ceilings go down (net −5,139 tokens). The `claude/design-review` size pin drops by 437 chars, and the claude ship golden is refreshed.
|
||||
- **Gate integrity:** the tests that pinned the removed section now assert its absence, or check the template description instead:
|
||||
- `test/catalog-trim.test.ts` (the three `buildWhenToInvokeSection` tests go with the function);
|
||||
- `test/catalog-mode-full.test.ts` and `test/cso-preserved.test.ts`;
|
||||
- `test/gen-skill-docs.test.ts` (the benchmark routing and CSO voice tests read the template);
|
||||
- `test/skill-validation.test.ts` (trigger and proactive phrases are checked in `SKILL.md.tmpl`);
|
||||
- `test/helpers/parity-harness.ts` (no `## When to invoke` heading required);
|
||||
- `test/helpers/skill-fixture.ts` and `test/skill-e2e-retro.test.ts` (the section is dropped from the review, review-army and retro E2E section lists). `scripts/harness-version.json` records the `skill-fixture.ts` change as non-behavioral: `HARNESS_VERSION` stays 6, and the regenerated SKILL.md bytes already start those cases' new series.
|
||||
- `test/helpers/manual-judge-review-fixture.ts` puts the removed section back when it rebuilds the historical cookie-workflow approval, so the approved prompt hash and line range still match. The approval itself is unchanged.
|
||||
|
||||
One new free test checks that the root router routes every voice trigger, and it skips the `connect-chrome` alias symlink. No threshold, trial count, case kind or judge changed. No context-budget ceiling rose except the root router's.
|
||||
|
||||
## [1.91.156.0] - 2026-10-19
|
||||
|
||||
**The weekly marathon's office-hours case can run its review formatter again, and its validator now checks cap fixes the way the formatter does.** Both defects came from #3164 and were in the test, not the skill. The marathon fixture copied `bin/gstack-office-hours-review` with a hand-kept list of two library files; #3164 added a third import (`lib/spec-review-cap.ts`), so every formatter command in the fixture failed at module load, the agent correctly declared the review UNREVIEWED, and the case went red in marathon run 39032308051 (#3246). With the module restored, a focused rerun showed the second defect: when the review stops at the round cap and the agent applies the remaining blocking fixes with `--fixes`, the formatter records them as verified, but the test never gave the validator that list, so it expected "fixes unconfirmed" and rejected a correct report.
|
||||
|
||||
@@ -797,6 +797,8 @@ types into that element; bare `browse type <text>` types into whatever has focus
|
||||
|
||||
**Don't use some skills?** `gstack-config set disabled_skills make-pdf,pair-agent` stops registering them on every host, so their descriptions stop loading into each session. Claude Code updates right away; other hosts on their next `./setup --host <name>` or `/gstack-upgrade`. Their files stay installed, so a skill that calls a disabled one still works (you get a warning). Typos are rejected with the closest skill name, `gstack-upgrade` can't be disabled, and `gstack-config set disabled_skills ""` re-enables everything.
|
||||
|
||||
**Never use plan mode?** `gstack-config set plan_mode_preamble off` leaves the "Plan Mode Safe Operations" and "Skill Invocation During Plan Mode" sections (about 1.6 KB per skill) out of every installed skill on the next `./setup` run; the command prints the exact `./setup --refresh-registered` line that applies it. Claude Code then serves a render under `~/.gstack/render/claude` instead of the committed files. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" keep their marker. `gstack-config set plan_mode_preamble on` (the default) and another `./setup` bring the sections back. It matters for Claude Code, Copilot and OpenCode: hosts without plan mode (Codex and others) never carry these sections.
|
||||
|
||||
**Want namespaced commands?** `cd ~/.claude/skills/gstack && ./setup --prefix` — switches from `/qa` to `/gstack-qa`. Useful if you run other skill packs alongside gstack.
|
||||
|
||||
**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. `${CODEX_HOME:-~/.codex}/skills/gstack` is a runtime directory, not the checkout: `./setup --status` shows the Codex row's source checkout. Fix: `cd <that source> && git pull && ./setup --host codex` — for a repo-local install, run it from inside the project.
|
||||
|
||||
@@ -16,14 +16,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Sends any gstack request to the right skill
|
||||
(planning, review, QA, shipping, debugging, docs, security, design). Routes QA by
|
||||
intent and browser interaction to /browse. Use when you invoke gstack without a specific
|
||||
skill, or ask "which gstack skill fits this?".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
@@ -230,6 +222,27 @@ Generic “second opinion”, “outside review”, or “cross-model review”
|
||||
- User asks what gstack has learned, "show learnings" → invoke `/learn`
|
||||
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
||||
- User asks for code quality dashboard, "health check" → invoke `/health`
|
||||
- Voice trigger "auto plan", "automatic review" → invoke `/autoplan`
|
||||
- Voice trigger "speed test", "check performance" → invoke `/benchmark`
|
||||
- Voice trigger "compare models", "model shootout", "which model is best" → invoke `/benchmark-models`
|
||||
- Voice trigger "open the browser", "look at this page" → invoke `/browse`
|
||||
- Voice trigger "code x", "code ex", "get another opinion" → invoke `/codex`
|
||||
- Voice trigger "see-so", "see so", "security review", "security check", "vulnerability scan", "run security" → invoke `/cso`
|
||||
- Voice trigger "build the design", "code the mockup", "make it real" → invoke `/design-html`
|
||||
- Voice trigger "dx audit", "test the developer experience", "try the onboarding", "developer experience test" → invoke `/devex-review`
|
||||
- Voice trigger "upgrade the tools", "update the tools", "gee stack upgrade", "g stack upgrade" → invoke `/gstack-upgrade`
|
||||
- Voice trigger "clean the iOS debug bridge", "remove DebugBridge", "strip the gstack iOS instrumentation" → invoke `/ios-clean`
|
||||
- Voice trigger "review the iOS design", "audit the iPhone app's visuals", "design QA the iPhone app" → invoke `/ios-design-review`
|
||||
- Voice trigger "fix the iOS bug", "patch the iPhone app", "auto-fix the iOS issue" → invoke `/ios-fix`
|
||||
- Voice trigger "iOS quality check", "test the iPhone app", "test the iPad app", "run iOS QA" → invoke `/ios-qa`
|
||||
- Voice trigger "resync the iOS debug bridge", "regenerate iOS accessors", "update the gstack iOS instrumentation" → invoke `/ios-sync`
|
||||
- Voice trigger "make this a pdf", "make it a pdf", "export to pdf", "turn this into a pdf", "turn this markdown into a pdf", "generate a pdf", "make a pdf from", "pdf this markdown" → invoke `/make-pdf`
|
||||
- Voice trigger "show me the browser" → invoke `/open-gstack-browser`
|
||||
- Voice trigger "pair agent", "connect agent", "share my browser", "remote browser access" → invoke `/pair-agent`
|
||||
- Voice trigger "dx review", "developer experience review", "devex review", "devex audit", "API design review", "onboarding review" → invoke `/plan-devex-review`
|
||||
- Voice trigger "tech review", "technical review", "plan engineering review" → invoke `/plan-eng-review`
|
||||
- Voice trigger "quality check", "test the app", "run QA" → invoke `/qa`
|
||||
- Voice trigger "bug report", "just check for bugs" → invoke `/qa-only`
|
||||
|
||||
**Route only to skills in your available-skills list.** A rule above whose skill is
|
||||
not listed there is turned off on this install; skip it, never try to invoke it.
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ quality gates that produce better results than answering inline.
|
||||
- User asks to plan, preregister, price or pilot a paid eval → invoke `/eval-plan`
|
||||
- User asks what gstack has learned, "show learnings" → invoke `/learn`
|
||||
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
||||
- User asks for code quality dashboard, "health check" → invoke `/health`
|
||||
- User asks for code quality dashboard, "health check" → invoke `/health`{{VOICE_TRIGGER_ROUTING}}
|
||||
|
||||
**Route only to skills in your available-skills list.** A rule above whose skill is
|
||||
not listed there is turned off on this install; skip it, never try to invoke it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.156.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.158.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
@@ -32,19 +32,6 @@ hooks:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Surfaces
|
||||
taste decisions (close approaches, borderline scope, outside-review disagreements) at a final
|
||||
approval gate. One command, fully reviewed plan out.
|
||||
Use when asked to "auto review", "autoplan", "run all reviews", "review this plan
|
||||
automatically", or "make the decisions for me".
|
||||
Proactively suggest when the user has a plan file and wants to run the full review
|
||||
gauntlet without answering 15-30 intermediate questions.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "auto plan", "automatic review".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -16,18 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Runs the same prompt through Claude,
|
||||
GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,
|
||||
and optionally quality via LLM judge. Answers "which model is actually best
|
||||
for this skill?" with data instead of vibes. Separate from /benchmark, which
|
||||
measures web page performance. Use when: "benchmark models", "compare models",
|
||||
"which model is best for X", "cross-model comparison", "model shootout".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "compare models", "model shootout", "which model is best".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,18 +17,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Establishes
|
||||
baselines for page load times, Core Web Vitals, and resource sizes.
|
||||
Compares before/after on every PR. Tracks performance trends over time.
|
||||
Use when: "web performance benchmark", "page speed", "lighthouse", "web vitals",
|
||||
"bundle size", "load time". Not for AI model, agent eval or business
|
||||
benchmarks; use /benchmark-models for cross-model skill comparisons.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "speed test", "check performance".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
+23
-4
@@ -94,6 +94,13 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
|
||||
# # (for power users who know the terms)
|
||||
# # Unknown values default to "default" with a warning.
|
||||
# # See docs/designs/PLAN_TUNING_V1.md for rationale.
|
||||
# plan_mode_preamble: on # on = every skill carries the plan-mode preamble
|
||||
# # off = rendered skills omit the "Plan Mode Safe
|
||||
# # Operations" and "Skill Invocation During
|
||||
# # Plan Mode" sections (~1.8 KB each), for
|
||||
# # users who never enter plan mode
|
||||
# # Applies on the next ./setup run. Unknown values
|
||||
# # default to "on" with a warning.
|
||||
#
|
||||
# ─── Artifacts sync (renamed from gbrain_sync_mode in v1.27.0.0) ─────
|
||||
# artifacts_sync_mode: off # off | artifacts-only | full
|
||||
@@ -279,6 +286,7 @@ lookup_default() {
|
||||
update_check) echo "true" ;;
|
||||
skill_prefix) echo "false" ;;
|
||||
explain_level) echo "default" ;;
|
||||
plan_mode_preamble) echo "on" ;; # on | off — off drops the plan-mode preamble on the next ./setup (#3168)
|
||||
codex_reviews) echo "enabled" ;;
|
||||
design_detector) echo "auto" ;; # auto | off — impeccable engine pre-pass in the design skills
|
||||
design_detector_install_prompted) echo "false" ;; # true once the user answered the one-time engine install offer with "never ask again"
|
||||
@@ -734,6 +742,10 @@ $_gstack_legacy_root"
|
||||
echo "Warning: explain_level '$VALUE' not recognized. Valid values: default, terse. Using default." >&2
|
||||
VALUE="default"
|
||||
fi
|
||||
if [ "$KEY" = "plan_mode_preamble" ] && [ "$VALUE" != "on" ] && [ "$VALUE" != "off" ]; then
|
||||
echo "Warning: plan_mode_preamble '$VALUE' not recognized. Valid values: on, off. Using on." >&2
|
||||
VALUE="on"
|
||||
fi
|
||||
if [ "$KEY" = "artifacts_sync_mode" ] && [ "$VALUE" != "off" ] && [ "$VALUE" != "artifacts-only" ] && [ "$VALUE" != "full" ]; then
|
||||
echo "Warning: artifacts_sync_mode '$VALUE' not recognized. Valid values: off, artifacts-only, full. Using off." >&2
|
||||
VALUE="off"
|
||||
@@ -985,6 +997,11 @@ $_gstack_legacy_root"
|
||||
gstack_install_registry_rows | awk -F '\t' '$1 != "claude" { print " apply to " $1 " (" $4 "): cd " $6 " && ./setup --host " $1 }' )
|
||||
fi
|
||||
fi
|
||||
# Installed skills are rendered by ./setup, so the preamble choice lands
|
||||
# on its next run (every registered host).
|
||||
if [ "$KEY" = "plan_mode_preamble" ] && [ -z "${GSTACK_SETUP_RUNNING:-}" ]; then
|
||||
printf 'gstack-config: plan_mode_preamble=%s applies on the next ./setup run. Apply now: cd %q && ./setup --refresh-registered\n' "$VALUE" "$(cd "$(dirname "$0")/.." && pwd)"
|
||||
fi
|
||||
;;
|
||||
unset)
|
||||
KEY="${2:?Usage: gstack-config unset <key>}"
|
||||
@@ -1009,7 +1026,7 @@ $_gstack_legacy_root"
|
||||
echo "# ─── Active values (including defaults for unset keys) ───"
|
||||
report_root_disagreement
|
||||
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
|
||||
skill_prefix explain_level \
|
||||
skill_prefix explain_level plan_mode_preamble \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted artifacts_sync_removals plan_tune_hooks \
|
||||
timeline_stop_hook design_detector design_detector_install_prompted memorable_recall \
|
||||
@@ -1034,7 +1051,7 @@ $_gstack_legacy_root"
|
||||
defaults)
|
||||
echo "# gstack-config defaults"
|
||||
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
|
||||
skill_prefix explain_level \
|
||||
skill_prefix explain_level plan_mode_preamble \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted artifacts_sync_removals plan_tune_hooks \
|
||||
timeline_stop_hook design_detector design_detector_install_prompted memorable_recall \
|
||||
@@ -1065,6 +1082,8 @@ $_gstack_legacy_root"
|
||||
# generated SKILL.md files match your installation state.
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/gstack-render-claude.sh" 2>/dev/null || { echo "$0: $SCRIPT_DIR/gstack-render-claude.sh is missing. fix: reinstall with ./setup or /gstack-upgrade" >&2; exit 1; }
|
||||
_PLAN_MODE_ARG=""
|
||||
[ "$("$0" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"
|
||||
DETECT_BIN="$SCRIPT_DIR/gstack-gbrain-detect"
|
||||
DETECTION_FILE="$STATE_DIR/gbrain-detection.json"
|
||||
mkdir -p "$STATE_DIR"
|
||||
@@ -1133,7 +1152,7 @@ $_gstack_legacy_root"
|
||||
gstack_claude_overlay "$INSTALL_DIR" "$0"
|
||||
RENDER_TMP="$RENDER_DIR.tmp.$$"
|
||||
rm -rf "$RENDER_TMP"
|
||||
if ( cd "$INSTALL_DIR" && bun run gen:skill-docs:user --host claude --out-dir "$RENDER_TMP" --link-root "$RENDER_DIR" --model "$_GSTACK_OVERLAY" >/dev/null 2>&1 ); then
|
||||
if ( cd "$INSTALL_DIR" && bun run gen:skill-docs:user --host claude --out-dir "$RENDER_TMP" --link-root "$RENDER_DIR" --model "$_GSTACK_OVERLAY" ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} >/dev/null 2>&1 ); then
|
||||
gstack_render_lock "$RENDER_DIR"
|
||||
_swap_in_render "$RENDER_DIR" "$RENDER_TMP"
|
||||
gstack_render_unlock "$RENDER_DIR"
|
||||
@@ -1159,7 +1178,7 @@ $_gstack_legacy_root"
|
||||
# brain blocks, so removing gbrain keeps the chosen overlay.
|
||||
INSTALL_DIR="$HOME/.claude/skills/gstack"
|
||||
RENDER_DIR="${GSTACK_USER_RENDER_DIR:-$STATE_DIR/render/claude}"
|
||||
if { [ -d "$RENDER_DIR" ] || "$0" has claude_overlay_model; } \
|
||||
if { [ -d "$RENDER_DIR" ] || "$0" has claude_overlay_model || [ -n "$_PLAN_MODE_ARG" ]; } \
|
||||
&& [ -d "$INSTALL_DIR" ] && [ ! -L "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/VERSION" ] && [ -f "$INSTALL_DIR/package.json" ]; then
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo "Skip: bun not on PATH — the Claude render at $RENDER_DIR was not refreshed. Install bun, then re-run 'gstack-config gbrain-refresh'."
|
||||
|
||||
+10
-3
@@ -461,19 +461,26 @@ done
|
||||
# (in place, like gstack-patch-names). Clearing the list restores the committed
|
||||
# bytes. Only a generated router is rewritten, and only from a checkout that
|
||||
# carries the generator; a failed render keeps the current router.
|
||||
# plan_mode_preamble=off (#3168) renders it without the plan-mode preamble the
|
||||
# same way, and turning it back on restores the committed bytes.
|
||||
_ROUTER="$INSTALL_DIR/SKILL.md"
|
||||
_GEN_SRC="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
if { [ "$DISABLED" != " " ] || grep -q '^Disabled on this install' "$_ROUTER" 2>/dev/null; } \
|
||||
_PLAN_MODE_ARG=""
|
||||
[ "$("$GSTACK_CONFIG" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"
|
||||
if { [ "$DISABLED" != " " ] || grep -q '^Disabled on this install' "$_ROUTER" 2>/dev/null \
|
||||
|| [ -n "$_PLAN_MODE_ARG" ] \
|
||||
|| { grep -q 'PLAN MODE EXCEPTION' "$_ROUTER" 2>/dev/null && ! grep -q '^## Plan Mode Safe Operations' "$_ROUTER" 2>/dev/null; }; } \
|
||||
&& grep -q '<!-- AUTO-GENERATED from' "$_ROUTER" 2>/dev/null && [ -f "$_GEN_SRC/scripts/gen-skill-docs.ts" ]; then
|
||||
_ROUTER_TMP="$(mktemp -d "${TMPDIR:-/tmp}/gstack-router.XXXXXX")" || _ROUTER_TMP=""
|
||||
_DISABLED_CSV="$(printf '%s' "$DISABLED" | xargs | tr ' ' ',')"
|
||||
if [ -n "$_ROUTER_TMP" ] && command -v bun >/dev/null 2>&1 \
|
||||
&& (cd "$_GEN_SRC" && bun --no-env-file --config=/dev/null run scripts/gen-skill-docs.ts --host claude --out-dir "$_ROUTER_TMP" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} >/dev/null 2>&1) \
|
||||
&& (cd "$_GEN_SRC" && bun --no-env-file --config=/dev/null run scripts/gen-skill-docs.ts --host claude --out-dir "$_ROUTER_TMP" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} >/dev/null 2>&1) \
|
||||
&& [ -s "$_ROUTER_TMP/SKILL.md" ] && cp "$_ROUTER_TMP/SKILL.md" "$_ROUTER.tmp.$$" && mv "$_ROUTER.tmp.$$" "$_ROUTER"; then
|
||||
_link_root_skill_alias
|
||||
[ -n "$_DISABLED_CSV" ] && echo "Router: disabled skills left out of $_ROUTER"
|
||||
else
|
||||
rm -f "$_ROUTER.tmp.$$"
|
||||
echo "gstack-relink: could not re-render the router for disabled_skills (bun missing or generation failed); $_ROUTER still lists every skill. Fix: install Bun, then run $0" >&2
|
||||
echo "gstack-relink: could not re-render the router for disabled_skills or plan_mode_preamble (bun missing or generation failed); $_ROUTER is unchanged. Fix: install Bun, then run $0" >&2
|
||||
fi
|
||||
[ -z "$_ROUTER_TMP" ] || rm -rf "$_ROUTER_TMP"
|
||||
fi
|
||||
|
||||
+20
-10
@@ -9,10 +9,15 @@
|
||||
# upgrade falls back to `claude` with the reason printed.
|
||||
#
|
||||
# Each render keeps a plain-text activation record beside it
|
||||
# (<render dir>.overlay: overlay, source, model, version, gbrain, render), so
|
||||
# (<render dir>.overlay: overlay, source, model, version, gbrain, render, and
|
||||
# plan_mode=off for a render without the plan-mode preamble), so
|
||||
# `./setup --status` and the change-only banner never need bun. No record
|
||||
# means the default `claude` overlay on the committed render.
|
||||
#
|
||||
# Callers set _PLAN_MODE_ARG to --plan-mode=off when gstack-config
|
||||
# plan_mode_preamble is off (#3168); empty or unset means on. The committed
|
||||
# files carry the preamble, so with it off the default install is rendered.
|
||||
#
|
||||
# Callers define `_swap_in_render` (setup and bin/gstack-config each keep
|
||||
# their tested copy) and may define `log` and `bun_cmd`; plain echo and bun
|
||||
# are used otherwise.
|
||||
@@ -81,15 +86,18 @@ gstack_render_unlock() {
|
||||
|
||||
# gstack_claude_render_action RENDER_DIR GBRAIN — what the default install's
|
||||
# render dir needs: `user` (brain-aware :user render), `plain` (pinned
|
||||
# overlay, no brain blocks), `remove` (serve the committed files) or `keep`.
|
||||
# overlay or plan-mode preamble off, no brain blocks), `remove` (serve the
|
||||
# committed files) or `keep`.
|
||||
# GBRAIN is ok, absent or unknown (detector missing or failed: no brain
|
||||
# blocks, and a render is left alone unless an overlay change needs it).
|
||||
# blocks, and a render is left alone unless an overlay or plan-mode change
|
||||
# needs it).
|
||||
gstack_claude_render_action() {
|
||||
local render_dir="$1" gbrain="$2" previous
|
||||
if [ "$gbrain" = ok ]; then _GSTACK_RENDER_ACTION=user; return 0; fi
|
||||
if [ "$_GSTACK_OVERLAY" != claude ]; then _GSTACK_RENDER_ACTION=plain; return 0; fi
|
||||
if [ "$_GSTACK_OVERLAY" != claude ] || [ -n "${_PLAN_MODE_ARG:-}" ]; then _GSTACK_RENDER_ACTION=plain; return 0; fi
|
||||
previous="$(gstack_claude_overlay_field "$render_dir.overlay" overlay)"
|
||||
if [ "$gbrain" = unknown ] && [ "${previous:-claude}" = claude ]; then
|
||||
if [ "$gbrain" = unknown ] && [ "${previous:-claude}" = claude ] \
|
||||
&& [ -z "$(gstack_claude_overlay_field "$render_dir.overlay" plan_mode)" ]; then
|
||||
_GSTACK_RENDER_ACTION=keep
|
||||
else
|
||||
_GSTACK_RENDER_ACTION=remove
|
||||
@@ -102,8 +110,8 @@ gstack_claude_render_plain() {
|
||||
local src="$1" RENDER_DIR="$2" RENDER_TMP out rc=0
|
||||
RENDER_TMP="$RENDER_DIR.tmp.$$"
|
||||
rm -rf "$RENDER_TMP"
|
||||
_gstack_render_say "Rendering Claude skills with the $_GSTACK_OVERLAY overlay into $RENDER_DIR (source checkout stays clean)..."
|
||||
out="$(cd "$src" && _gstack_render_bun run gen:skill-docs --host claude --out-dir "$RENDER_TMP" --link-root "$RENDER_DIR" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} 2>&1)" || rc=$?
|
||||
_gstack_render_say "Rendering Claude skills with the $_GSTACK_OVERLAY overlay${_PLAN_MODE_ARG:+ and no plan-mode preamble} into $RENDER_DIR (source checkout stays clean)..."
|
||||
out="$(cd "$src" && _gstack_render_bun run gen:skill-docs --host claude --out-dir "$RENDER_TMP" --link-root "$RENDER_DIR" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} 2>&1)" || rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
rm -rf "$RENDER_TMP"
|
||||
printf '%s\n' "$out" | tail -3
|
||||
@@ -150,9 +158,11 @@ gstack_claude_render_settle() {
|
||||
if [ "$render_kind" = committed ] && [ "$_GSTACK_OVERLAY_SOURCE" = default ]; then
|
||||
rm -f "$record"
|
||||
else
|
||||
printf 'overlay=%s\nsource=%s\nmodel=%s\nversion=%s\ngbrain=%s\nrender=%s\n' \
|
||||
"$served" "$_GSTACK_OVERLAY_SOURCE" "$_GSTACK_OVERLAY_ID" "${version:-unknown}" "$gbrain" "$render_kind" \
|
||||
> "$record.tmp.$$" && mv "$record.tmp.$$" "$record"
|
||||
{
|
||||
printf 'overlay=%s\nsource=%s\nmodel=%s\nversion=%s\ngbrain=%s\nrender=%s\n' \
|
||||
"$served" "$_GSTACK_OVERLAY_SOURCE" "$_GSTACK_OVERLAY_ID" "${version:-unknown}" "$gbrain" "$render_kind"
|
||||
[ "$render_kind" != user ] || [ -z "${_PLAN_MODE_ARG:-}" ] || printf 'plan_mode=off\n'
|
||||
} > "$record.tmp.$$" && mv "$record.tmp.$$" "$record"
|
||||
fi
|
||||
gstack_render_unlock "$render_dir"
|
||||
if [ "$served" != "${previous:-claude}" ]; then
|
||||
|
||||
@@ -16,14 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use when asked to open a site, test a page, take a
|
||||
screenshot, or dogfood a flow.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "open the browser", "look at this page".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,15 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Watches the live app for console errors,
|
||||
performance regressions, and page failures. Takes
|
||||
periodic screenshots, compares against pre-deploy baselines, and alerts
|
||||
on anomalies. Use when: "monitor deploy", "canary", "post-deploy check",
|
||||
"watch production", "verify deploy".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -25,15 +25,6 @@ hooks:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Warns before rm -rf, DROP TABLE,
|
||||
force-push, git reset --hard, kubectl delete, and similar destructive operations.
|
||||
User can override each warning. Use when touching prod, debugging live systems,
|
||||
or working in a shared environment. Use when asked to "be careful", "safety mode",
|
||||
"prod mode", or "careful mode".
|
||||
|
||||
# /careful — Destructive Command Guardrails
|
||||
|
||||
Safety mode is now **active**. Every Bash and PowerShell command will be checked
|
||||
|
||||
@@ -18,17 +18,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Code review: independent diff review via
|
||||
codex review with pass/fail gate. Challenge: adversarial mode that tries to break
|
||||
your code. Consult: ask codex anything with session continuity for follow-ups.
|
||||
The "200 IQ autistic developer" second opinion. Use when asked to "codex review",
|
||||
"codex challenge", "ask codex", "second opinion", or "consult codex".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "code x", "code ex", "get another opinion".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,15 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Loads the most recent
|
||||
saved state (preferring the current branch, falling back across branches) so
|
||||
you can pick up where you left off — even across Conductor workspace handoffs.
|
||||
Use when asked to "resume", "restore context", "where was I", or
|
||||
"pick up where I left off". Pair with /context-save.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,14 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Captures git state, decisions made, and remaining work
|
||||
so any future session can pick up without losing a beat.
|
||||
Use when asked to "save progress", "save state", "context save", or
|
||||
"save my work". Pair with /context-restore to resume later.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -13,13 +13,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use when: "security audit", "threat model", "OWASP", "CSO review", "recheck a vulnerability".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "see-so", "see so", "security review", "security check", "vulnerability scan", "run security".
|
||||
|
||||
# /cso — evidence before assurance
|
||||
|
||||
Find exploitable defects. State attacker, boundary, impact, and challenge. Static assessment remains available without runtime or scanner profiles. Qualified comprehensive profiles add reproduction and repair candidates without changing the branch. Trusted `gstack-cso` owns execution, persistence, and proof labels.
|
||||
|
||||
@@ -43,15 +43,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Creates DESIGN.md as your project's design source
|
||||
of truth. For existing sites, use /plan-design-review to infer the system instead.
|
||||
Use when asked to "design system", "brand guidelines", or "create DESIGN.md".
|
||||
Proactively suggest when starting a new project's UI with no existing
|
||||
design system or DESIGN.md.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,19 +20,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Works with approved mockups from /design-shotgun, CEO plans from /plan-ceo-review,
|
||||
design review context from /plan-design-review, or from scratch with a user
|
||||
description. Text actually reflows, heights are computed, layouts are dynamic.
|
||||
30KB overhead, zero deps. Smart API routing: picks the right Pretext patterns
|
||||
for each design type. Use when: "finalize this design", "turn this into HTML",
|
||||
"build me a page", "implement this design", or after any planning skill.
|
||||
Proactively suggest when user has approved a design or has a plan ready.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "build the design", "code the mockup", "make it real".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,16 +20,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Iteratively fixes issues
|
||||
in source code, committing each fix atomically and re-verifying with before/after
|
||||
screenshots. For plan-mode design review (before implementation), use /plan-design-review.
|
||||
Use when asked to "audit the design", "visual QA", "check if it looks good", or "design polish".
|
||||
Proactively suggest when the user mentions visual inconsistencies or
|
||||
wants to polish the look of a live site.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -37,15 +37,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Standalone design exploration you can
|
||||
run anytime. Use when: "explore designs", "show me options", "design variants",
|
||||
"visual brainstorm", or "I don't like how this looks".
|
||||
Proactively suggest when the user describes a UI feature but hasn't seen
|
||||
what it could look like.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -14,13 +14,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use for
|
||||
/deslop-shared-libs, $deslop-shared-libs, or requests to find code worth sharing.
|
||||
Recommendations only.
|
||||
|
||||
# Find code worth sharing
|
||||
|
||||
Find up to five new opportunities to share code, then recommend the best three.
|
||||
|
||||
@@ -19,19 +19,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Actually TESTS the developer experience
|
||||
in the Aside browser: navigates docs, tries the getting started flow, times
|
||||
TTHW, screenshots error messages, evaluates CLI help text. Produces a DX
|
||||
scorecard with evidence. Compares against /plan-devex-review scores if they
|
||||
exist (the boomerang: plan said 3 minutes, reality says 8). Use when asked to
|
||||
"test the DX", "DX audit", "developer experience test", or "try the
|
||||
onboarding". Proactively suggest after shipping a developer-facing feature.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "dx audit", "test the developer experience", "try the onboarding", "developer experience test".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,14 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
The SVG/PNG use clean mermaid style; the
|
||||
.excalidraw carries the hand-drawn aesthetic. Fully offline.
|
||||
Use when asked to "make a diagram", "draw the architecture", "create a
|
||||
flowchart", "diagram this", or "visualize this flow".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -23,15 +23,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Uses the Diataxis framework (tutorial / how-to / reference / explanation) to produce
|
||||
complete, structured documentation. Can be invoked standalone or called by
|
||||
/document-release when it finds coverage gaps. Use when asked to "write docs",
|
||||
"generate documentation", "document this feature", "create a tutorial", or
|
||||
"explain this module".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,17 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Reads relevant project docs, cross-references the
|
||||
diff, builds a Diataxis coverage map (reference/how-to/tutorial/explanation),
|
||||
updates README/ARCHITECTURE/CONTRIBUTING/CLAUDE.md to match what shipped,
|
||||
detects architecture diagram drift, polishes CHANGELOG voice with a sell-test
|
||||
rubric, cleans up TODOS, and optionally bumps VERSION. Surfaces documentation
|
||||
debt in the PR body. Use when asked to "update the docs", "sync documentation",
|
||||
or "post-ship docs". Proactively suggest a documentation audit before merge.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -31,15 +31,6 @@ hooks:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Blocks Edit,
|
||||
Write and NotebookEdit outside the allowed path. Use when debugging to prevent accidentally
|
||||
"fixing" unrelated code, or when you want to scope changes to one module.
|
||||
Use when asked to "freeze", "restrict edits", "only edit this folder",
|
||||
or "lock down edits".
|
||||
|
||||
# /freeze — Restrict Edits to a Directory
|
||||
|
||||
Lock file edits to a specific directory. Any Edit, Write or NotebookEdit
|
||||
|
||||
@@ -15,15 +15,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Detects global vs vendored install,
|
||||
runs the upgrade, and shows what's new. Use when asked to "upgrade gstack",
|
||||
"update gstack", or "get latest version".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "upgrade the tools", "update the tools", "gee stack upgrade", "g stack upgrade".
|
||||
|
||||
# /gstack-upgrade
|
||||
|
||||
Upgrade gstack to the latest version and show what's new.
|
||||
|
||||
@@ -41,14 +41,6 @@ hooks:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Combines /careful (warns before rm -rf, DROP TABLE, force-push, etc.) with
|
||||
/freeze (blocks edits outside a specified directory). Use for maximum safety
|
||||
when touching prod or debugging live systems. Use when asked to "guard mode",
|
||||
"full safety", "lock it down", or "maximum safety".
|
||||
|
||||
# /guard — Full Safety Mode
|
||||
|
||||
Activates both destructive command warnings and directory-scoped edit restrictions.
|
||||
|
||||
@@ -19,15 +19,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Wraps existing project tools (type checker, linter,
|
||||
test runner, dead code detector, shell linter), computes a weighted composite
|
||||
0-10 score, and tracks trends over time. Use when: "health check",
|
||||
"code quality", "how healthy is the codebase", "run all checks",
|
||||
"quality score".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -61,17 +61,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Four phases: investigate,
|
||||
analyze, hypothesize, implement. Iron Law: no fixes without root cause.
|
||||
Use when asked to "debug this", "fix this bug", "why is this broken",
|
||||
"investigate this error", or "root cause analysis".
|
||||
Proactively invoke this skill (do NOT debug directly) when the user reports
|
||||
errors, 500 errors, stack traces, unexpected behavior, "it was working
|
||||
yesterday", or is troubleshooting why something stopped working.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -18,18 +18,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Cleans up StateServer, DebugOverlay, accessor codegen output, and
|
||||
app-side hooks installed by /ios-qa. This is a convenience wrapper —
|
||||
the structural Release-build guard (Package.swift conditional + CI
|
||||
swift build -c release check) is the safety-critical path.
|
||||
Use when asked to "clean the iOS debug bridge", "remove DebugBridge",
|
||||
or "strip the gstack iOS instrumentation".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "clean the iOS debug bridge", "remove DebugBridge", "strip the gstack iOS instrumentation".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,21 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Connects to a real
|
||||
iPhone via the same StateServer as /ios-qa, screenshots every screen,
|
||||
evaluates against Apple HIG, DESIGN.md, and design best practices. Scores
|
||||
each dimension 0-10 with "what would make it a 10" framing — mirrors
|
||||
/plan-design-review for browser. For plan-stage design review (before
|
||||
implementation), use /plan-design-review. For live web visual audits, use
|
||||
/design-review.
|
||||
Use when asked to "review the iOS design", "audit the iPhone app's
|
||||
visuals", or "design QA the iOS app".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "review the iOS design", "audit the iPhone app's visuals", "design QA the iPhone app".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,20 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Takes a bug found by /ios-qa, reads the source,
|
||||
writes the fix, rebuilds, redeploys, and verifies the fix on the real
|
||||
device. Closes the loop: find bug → fix bug → confirm fix — zero human
|
||||
intervention. Captures the pre-bug state snapshot as a regression test
|
||||
fixture, so the bug can never recur silently.
|
||||
Use when /ios-qa reports a bug and you want it fixed automatically, or
|
||||
when asked to "fix this iOS bug", "patch the iPhone app", or "auto-fix
|
||||
the iOS issue".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "fix the iOS bug", "patch the iPhone app", "auto-fix the iOS issue".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -22,21 +22,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Connects to a real iPhone or iPad via
|
||||
USB CoreDevice IPv6 tunnel, reads Swift source to understand every screen, then
|
||||
runs a vision-driven agent loop: screenshot → analyze → decide → act →
|
||||
verify → repeat. All interaction happens via HTTP to an embedded
|
||||
StateServer in the app under test. Optionally exposes the device over
|
||||
Tailscale so remote agents (OpenClaw, Codex, any HTTP-capable agent) can
|
||||
run iOS QA from anywhere without touching the hardware.
|
||||
Use when asked to "ios qa", "test my iPhone app", "test my iPad app",
|
||||
"find bugs on the device", or "qa the iOS app".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "iOS quality check", "test the iPhone app", "test the iPad app", "run iOS QA".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,17 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Updates StateServer.swift, DebugOverlay.swift, Package.swift,
|
||||
and the typed @Observable state accessors. Use after you upgrade gstack
|
||||
or add new ViewModels/properties that need accessor coverage.
|
||||
Use when asked to "resync the iOS debug bridge", "regenerate iOS
|
||||
accessors", or "update the gstack iOS instrumentation".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "resync the iOS debug bridge", "regenerate iOS accessors", "update the gstack iOS instrumentation".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,14 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Merges the PR, waits for CI and deploy,
|
||||
verifies production health via canary checks. Takes over after /ship
|
||||
creates the PR. Use when: "merge", "land", "deploy", "merge and verify",
|
||||
"land it", "ship it to production".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -16,15 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Shows which VERSION slots
|
||||
are currently claimed by open PRs, which sibling Conductor workspaces have
|
||||
WIP work likely to ship soon, and what slot /ship would pick next. No
|
||||
mutations — just a snapshot. Use when asked to "landing report", "what's in
|
||||
the queue", "show me open PRs", or "which version do I claim next".
|
||||
|
||||
# /landing-report — Version Queue Dashboard
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
@@ -19,15 +19,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Review, search, prune, and export what gstack
|
||||
has learned across sessions. Use when asked to "what have we learned",
|
||||
"show learnings", "prune stale learnings", or "export learnings".
|
||||
Proactively suggest when the user asks about past patterns or wonders
|
||||
"didn't we fix this before?"
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -16,17 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Proper 1in margins,
|
||||
intelligent page breaks, page numbers, cover pages, running headers, curly
|
||||
quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Not a draft
|
||||
artifact — a finished artifact. Use when asked to "make a PDF", "export to
|
||||
PDF", "turn this markdown into a PDF", or "generate a document".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "make this a pdf", "make it a pdf", "export to pdf", "turn this into a pdf", "turn this markdown into a pdf", "generate a pdf", "make a pdf from", "pdf this markdown".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -47,21 +47,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Startup mode: six forcing questions that expose
|
||||
demand reality, status quo, desperate specificity, narrowest wedge, observation,
|
||||
and future-fit. Builder mode: design thinking brainstorming for side projects,
|
||||
hackathons, learning, and open source. Saves a design doc.
|
||||
Use when asked to "brainstorm this", "I have an idea", "help me think through
|
||||
this", "office hours", or "is this worth building".
|
||||
Proactively invoke this skill (do NOT answer directly) when the user describes
|
||||
a new product idea, asks whether something is worth building, wants to think
|
||||
through design decisions for something that doesn't exist yet, or is exploring
|
||||
a concept before any code is written.
|
||||
Use before /plan-ceo-review or /plan-eng-review.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -16,16 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Opens a visible browser window where you can watch every action in real time.
|
||||
The sidebar shows a live activity feed and chat. Anti-bot stealth built in.
|
||||
Use when asked to "open gstack browser", "launch browser", "connect chrome",
|
||||
"open chrome", "real browser", "launch chrome", "side panel", or "control my browser".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "show me the browser".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.156",
|
||||
"version": "1.91.158",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -16,19 +16,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
One command generates a setup key and
|
||||
prints instructions the other agent can follow to connect. Works with OpenClaw,
|
||||
Hermes, Codex, Cursor, or any agent that can make HTTP requests. The remote agent
|
||||
gets its own tab with full page access by default (the pairing ceremony is the
|
||||
trust boundary; --restrict narrows it).
|
||||
Use when asked to "pair agent", "connect agent", "share browser", "remote browser",
|
||||
"let another agent use my browser", or "give browser access".
|
||||
|
||||
Voice triggers (speech-to-text aliases): "pair agent", "connect agent", "share my browser", "remote browser access".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -49,18 +49,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Rethink the problem, find the 10-star product,
|
||||
challenge premises, expand scope when it creates a better product. Four modes:
|
||||
SCOPE EXPANSION (dream big), SELECTIVE EXPANSION (hold scope + cherry-pick
|
||||
expansions), HOLD SCOPE (maximum rigor), SCOPE REDUCTION (strip to essentials).
|
||||
Use when asked to "think bigger", "expand scope", "strategy review", "rethink this",
|
||||
or "is this ambitious enough".
|
||||
Proactively suggest when the user is questioning scope or ambition of a plan,
|
||||
or when the plan feels like it could be thinking bigger.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -18,16 +18,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Rates each design dimension 0-10, explains what would make it a 10,
|
||||
then fixes the plan to get there. Works in plan mode. For live site
|
||||
visual audits, use /design-review. Use when asked to "review the design plan"
|
||||
or "design critique".
|
||||
Proactively suggest when the user has a plan with UI/UX components that
|
||||
should be reviewed before implementation.
|
||||
|
||||
# /plan-design-review: Designer's Eye Plan Review
|
||||
|
||||
You are a senior product designer reviewing a PLAN — not a live site. Your job is
|
||||
|
||||
@@ -19,20 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Explores developer personas,
|
||||
benchmarks against competitors, designs magical moments, and traces friction
|
||||
points before scoring. Three modes: DX EXPANSION (competitive advantage),
|
||||
DX POLISH (bulletproof every touchpoint), DX TRIAGE (critical gaps only).
|
||||
Use when asked to "DX review", "developer experience audit", "devex review",
|
||||
or "API design review".
|
||||
Proactively suggest when the user has a plan for developer-facing products
|
||||
(APIs, CLIs, SDKs, libraries, platforms, docs).
|
||||
|
||||
Voice triggers (speech-to-text aliases): "dx review", "developer experience review", "devex review", "devex audit", "API design review", "onboarding review".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,18 +19,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Lock in the execution plan — architecture,
|
||||
data flow, diagrams, edge cases, test coverage, performance. Walks through
|
||||
issues interactively with opinionated recommendations. Use when asked to
|
||||
"review the architecture", "engineering review", or "lock in the plan".
|
||||
Proactively suggest when the user has a plan or design doc and is about to
|
||||
start coding — to catch architecture issues before implementation.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "tech review", "technical review", "plan engineering review".
|
||||
|
||||
# Plan Review Mode
|
||||
|
||||
Review the selected target. Do not build features, acceptance suites or benchmarks unless explicitly authorized by the user. Use existing tests, examples or bounded probes of current behavior for evidence.
|
||||
|
||||
@@ -23,21 +23,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Review which AskUserQuestion prompts fire across gstack skills, set per-question preferences
|
||||
(never-ask / always-ask / ask-only-for-one-way), inspect the dual-track
|
||||
profile (what you declared vs what your behavior suggests), and enable/disable
|
||||
question tuning. Conversational interface — no CLI syntax required.
|
||||
|
||||
Use when asked to "tune questions", "stop asking me that", "too many questions",
|
||||
"show my profile", "what questions have I been asked", "show my vibe",
|
||||
"developer profile", or "turn off question tuning".
|
||||
|
||||
Proactively suggest when the user says the same gstack question has come up before,
|
||||
or when they explicitly override a recommendation for the Nth time.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,17 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Produces a
|
||||
structured report with contract evidence or browser scores and repro steps — but never
|
||||
fixes anything. Use when asked to "just report bugs", "qa report only", or
|
||||
"test but don't fix". For the full test-fix-verify loop, use /qa instead.
|
||||
Proactively suggest when the user wants a bug report without any code changes.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "bug report", "just check for bugs".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
-12
@@ -20,18 +20,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Commit verified fixes atomically. Use when asked to "qa", "QA", "test this site", "find bugs",
|
||||
"test and fix", or "fix what's broken".
|
||||
Proactively suggest when the user says a feature is ready for testing
|
||||
or asks "does this work?". Three tiers: Quick (critical/high only),
|
||||
Standard (+ medium), Exhaustive (+ cosmetic). Produces contract outcomes or browser health scores,
|
||||
fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only.
|
||||
|
||||
Voice triggers (speech-to-text aliases): "quality check", "test the app", "run QA".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -37,15 +37,6 @@ gbrain:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Analyzes commit history, work patterns,
|
||||
and code quality metrics with persistent history and trend tracking.
|
||||
Team-aware: breaks down per-person contributions with praise and growth areas.
|
||||
Use when asked to "weekly retro", "what did we ship", or "engineering retrospective".
|
||||
Proactively suggest at the end of a work week or sprint.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -22,14 +22,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Analyzes diff against the base branch for SQL safety, LLM trust
|
||||
boundary violations, conditional side effects, and other structural issues. Use when
|
||||
asked to "review this PR", "code review", "pre-landing review", or "check my diff".
|
||||
Proactively suggest when the user is about to merge or land code changes.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,12 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Read-only; returns one JSON document. Use when asked to
|
||||
"scrape", "get data from", "pull", "extract from", or "what's on" a page.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
+16
-55
@@ -25,6 +25,7 @@ import { HOST_PATHS } from './resolvers/types';
|
||||
import { RESOLVERS } from './resolvers/index';
|
||||
import { rewriteCarvedSectionRefs, SKILL_BYTE_CEILING, usesLazySections } from './resolvers/sections';
|
||||
import { insertRuntimePreludes } from './resolvers/runtime-root';
|
||||
import { extractVoiceTriggers } from './resolvers/voice-triggers';
|
||||
import { ALL_HOST_NAMES, resolveHostArg, getHostConfig } from '../hosts/index';
|
||||
import type { HostConfig } from './host-config';
|
||||
|
||||
@@ -51,6 +52,7 @@ export interface GenerationOptions {
|
||||
explainLevel?: 'default' | 'terse';
|
||||
respectDetection?: boolean;
|
||||
log?: (message: string) => void;
|
||||
planMode?: boolean;
|
||||
}
|
||||
|
||||
interface RenderOptions {
|
||||
@@ -62,6 +64,7 @@ interface RenderOptions {
|
||||
catalogMode: 'trim' | 'full';
|
||||
explainLevel: 'default' | 'terse';
|
||||
gbrainDetected: boolean;
|
||||
planMode: boolean;
|
||||
}
|
||||
|
||||
export interface GeneratedArtifact {
|
||||
@@ -129,6 +132,11 @@ function parseGenerationArgs(args: string[]): GenerationOptions {
|
||||
if (explainLevel !== 'default' && explainLevel !== 'terse') {
|
||||
throw new Error(`Unknown explain level: ${explainLevel}. Use 'default' or 'terse'.`);
|
||||
}
|
||||
const planModeValue = value('--plan-mode');
|
||||
if (planModeValue !== undefined && planModeValue !== 'on' && planModeValue !== 'off') {
|
||||
throw new Error(`Unknown plan mode: ${planModeValue}. Use 'on' or 'off'.`);
|
||||
}
|
||||
const planMode = planModeValue === 'off' ? false : true;
|
||||
const outDir = value('--out-dir');
|
||||
const linkRoot = value('--link-root');
|
||||
const disabledValue = value('--disabled-skills');
|
||||
@@ -142,7 +150,7 @@ function parseGenerationArgs(args: string[]): GenerationOptions {
|
||||
// Swap-in callers use --link-root for the FINAL serving path (#2692).
|
||||
// Direct --out-dir callers retain their existing links into the render.
|
||||
return {
|
||||
host, model, catalogMode, explainLevel,
|
||||
host, model, catalogMode, explainLevel, planMode,
|
||||
dryRun: args.includes('--dry-run'),
|
||||
respectDetection: args.includes('--respect-detection'),
|
||||
outputRoot: outDir === undefined ? ROOT : path.resolve(outDir),
|
||||
@@ -209,30 +217,6 @@ function rewriteSectionBase(content: string, linkRoot: string | null): string {
|
||||
|
||||
// ─── Voice Trigger Processing ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Extract voice-triggers YAML list from frontmatter.
|
||||
* Returns an array of trigger strings, or [] if no voice-triggers field.
|
||||
*/
|
||||
function extractVoiceTriggers(content: string): string[] {
|
||||
const fmStart = content.indexOf('---\n');
|
||||
if (fmStart !== 0) return [];
|
||||
const fmEnd = content.indexOf('\n---', fmStart + 4);
|
||||
if (fmEnd === -1) return [];
|
||||
const frontmatter = content.slice(fmStart + 4, fmEnd);
|
||||
|
||||
const triggers: string[] = [];
|
||||
let inVoice = false;
|
||||
for (const line of frontmatter.split('\n')) {
|
||||
if (/^voice-triggers:/.test(line)) { inVoice = true; continue; }
|
||||
if (inVoice) {
|
||||
const m = line.match(/^\s+-\s+"(.+)"$/);
|
||||
if (m) triggers.push(m[1]);
|
||||
else if (!/^\s/.test(line)) break;
|
||||
}
|
||||
}
|
||||
return triggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess voice triggers: fold voice-triggers YAML field into description,
|
||||
* then strip the field from frontmatter. Must run BEFORE transformFrontmatter
|
||||
@@ -271,8 +255,10 @@ export { extractVoiceTriggers, processVoiceTriggers };
|
||||
// "(gstack)" tag. This pile is the always-loaded catalog surface — every
|
||||
// session pays for the full text. The catalog trim splits the description
|
||||
// into a one-line catalog entry (lead sentence + "(gstack)") that stays in
|
||||
// the frontmatter, and a "## When to invoke" body section that holds the
|
||||
// routing/voice triggers prose for in-skill discovery.
|
||||
// the frontmatter and drops the routing/voice prose. The body is not a home
|
||||
// for it: Claude Code loads a skill body only after the skill is invoked, so
|
||||
// routing text there can no longer route anything. Voice triggers live on in
|
||||
// the root router's routing block ({{VOICE_TRIGGER_ROUTING}}).
|
||||
//
|
||||
// Opt-out: `--catalog-mode=full` keeps v1.44 behavior (no trim, full
|
||||
// description in frontmatter). Use when debugging routing regressions or
|
||||
@@ -358,20 +344,6 @@ export function buildTrimmedDescription(parts: CatalogParts): string {
|
||||
return `${lead}${suffix}`;
|
||||
}
|
||||
|
||||
/** Build the body section that holds the routing/voice prose. */
|
||||
export function buildWhenToInvokeSection(parts: CatalogParts): string {
|
||||
const lines: string[] = ['## When to invoke this skill', ''];
|
||||
if (parts.routingProse) {
|
||||
lines.push(parts.routingProse);
|
||||
lines.push('');
|
||||
}
|
||||
if (parts.voiceLine) {
|
||||
lines.push(parts.voiceLine);
|
||||
lines.push('');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a string as a YAML inline scalar value (the text after `key: `),
|
||||
* quoting only when a plain scalar would be invalid or ambiguous.
|
||||
@@ -398,9 +370,6 @@ export function toYamlInlineScalar(s: string): string {
|
||||
/**
|
||||
* Apply catalog trim to a SKILL.md body:
|
||||
* - shorten frontmatter `description:` to lead + (gstack)
|
||||
* - insert "## When to invoke" body section AFTER the generated header
|
||||
* (so it lands near the top of body content, where routing guidance
|
||||
* belongs)
|
||||
*
|
||||
* Returns the rewritten content plus the extracted parts.
|
||||
*/
|
||||
@@ -445,16 +414,7 @@ export function applyCatalogTrim(content: string, skillName: string): { content:
|
||||
// pattern and silently corrupt the frontmatter.
|
||||
const newDescLine = `description: ${toYamlInlineScalar(newDesc)}\n`;
|
||||
const newFrontmatter = frontmatter.replace(descMatch[0], () => newDescLine);
|
||||
let newContent = '---\n' + newFrontmatter + content.slice(fmEnd);
|
||||
|
||||
// Insert body section after frontmatter (after the closing ---\n and any
|
||||
// existing GENERATED header). We insert before the first non-comment line.
|
||||
const bodyStart = newContent.indexOf('\n---\n') + 5;
|
||||
const whenToInvoke = '\n' + buildWhenToInvokeSection(parts).trim() + '\n';
|
||||
// Skip past the generated header if present (it lives after frontmatter close)
|
||||
const headerMatch = newContent.slice(bodyStart).match(/^(<!--[^>]*-->\s*\n)+/);
|
||||
const insertAt = bodyStart + (headerMatch ? headerMatch[0].length : 0);
|
||||
newContent = newContent.slice(0, insertAt) + whenToInvoke + '\n' + newContent.slice(insertAt);
|
||||
const newContent = '---\n' + newFrontmatter + content.slice(fmEnd);
|
||||
|
||||
return { content: newContent, parts };
|
||||
}
|
||||
@@ -734,7 +694,7 @@ function buildContext(
|
||||
const interactive = interactiveMatch ? interactiveMatch[1] === 'true' : undefined;
|
||||
return {
|
||||
skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host],
|
||||
preambleTier, model: options.model ?? getHostConfig(host).defaultModel, interactive, explainLevel: options.explainLevel, installRoot: options.installRoot,
|
||||
preambleTier, model: options.model ?? getHostConfig(host).defaultModel, interactive, explainLevel: options.explainLevel, installRoot: options.installRoot, planMode: options.planMode,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -967,6 +927,7 @@ export async function runGeneration(settings: GenerationOptions = {}): Promise<G
|
||||
catalogMode: settings.catalogMode ?? 'trim',
|
||||
explainLevel: settings.explainLevel ?? 'default',
|
||||
gbrainDetected: loadGbrainOverride(settings.respectDetection ?? false),
|
||||
planMode: settings.planMode ?? true,
|
||||
};
|
||||
const hosts = settings.host === 'all' ? ALL_HOST_NAMES as Host[] : [settings.host ?? 'claude'];
|
||||
const log = settings.log ?? (() => {});
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"test/helpers/session-drain-policy.ts": {"blob":"6211239dd0485e6495a700b824a7e0e61d1bcd97","since":6},
|
||||
"test/helpers/session-ledger.ts": {"blob":"425323284a368f5e77937e03884db4dea4e6134e","since":6,"nonBehavioral":"Claude-host verdicts are unchanged: the Codex host (EVALS_HOST=codex, codex-host-runner) runs only when selected and records its own per-model series; the Codex runner refactor (execCodex extraction, onSpawn hook) keeps its events and verdicts and adds token cost accounting; host fields on the plan/report are absent for claude"},
|
||||
"test/helpers/session-runner.ts": {"blob":"cc65a66adafa9c053e3da3c6f17b5ac86c0eade7","since":6,"nonBehavioral":"Claude-host verdicts are unchanged: the Codex host (EVALS_HOST=codex, codex-host-runner) runs only when selected and records its own per-model series; the Codex runner refactor (execCodex extraction, onSpawn hook) keeps its events and verdicts and adds token cost accounting; host fields on the plan/report are absent for claude"},
|
||||
"test/helpers/skill-fixture.ts": {"blob":"b188064594eb4201a54a80ba1bc6f28e31d7f49e","since":6},
|
||||
"test/helpers/skill-fixture.ts": {"blob":"ffece830f35b0f1562005105e9296dc4d8ca50c2","since":6,"nonBehavioral":"skill-fixture.ts drops 'When to invoke this skill' from the review, review-army and retro section lists because #3129 stops generating that heading; extraction would throw otherwise. The fixture bytes follow the regenerated SKILL.md, whose owned prompt bytes already start those cases' new series."},
|
||||
"test/helpers/test-selection.ts": {"blob":"51655f89f66be7a5ec487e2abfe4744300ebd5f6","since":6},
|
||||
"test/helpers/touchfiles.ts": {"blob":"15f369056d41b9dc42dcd86f81f60c878074704c","since":6}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,14 @@ import { generateSidewaysSweep, generateFlakeFixNumber } from './sideways';
|
||||
import { generateCoordinatorContract } from './coordinator-contract';
|
||||
import { generateAutoplanDriverCodes, generateAutoplanDriverCommands, generateAutoplanDriverFiles, generateAutoplanDriverGate, generateAutoplanDriverPhases, generateAutoplanDriverResultFormat } from './autoplan-driver';
|
||||
import { generateQAScope, generateQAExploratory, generateQAFunctional, generateQAResource, generateQAReview, generateQAReviewPreflight, generateQAMethodReads } from './qa';
|
||||
import { generateVoiceTriggerRouting } from './voice-triggers';
|
||||
|
||||
export const RESOLVERS: Record<string, ResolverFn> = {
|
||||
AUTOPLAN_PUBLICATION_HOOK: generateAutoplanPublicationHook,
|
||||
CEO_MODE_HANDOFF_HOOK: generateCeoModeHandoffHook,
|
||||
OUTSIDE_SELF_GUARD: (ctx, args) => outsideVoiceGuard({ ...ctx, host: args?.[0] === 'claude-code' ? 'codex' : 'claude' }),
|
||||
OUTSIDE_VOICE_ROUTING: generateOutsideVoiceRouting,
|
||||
VOICE_TRIGGER_ROUTING: generateVoiceTriggerRouting,
|
||||
OUTSIDE_LABEL: (ctx) => outsideVoiceFor(ctx).label,
|
||||
NATIVE_LABEL: (ctx) => outsideVoiceFor(ctx).nativeLabel,
|
||||
OUTSIDE_PROVIDER: (ctx) => outsideVoiceFor(ctx).id,
|
||||
|
||||
@@ -25,6 +25,7 @@ import { getHostConfig } from '../../../hosts/index';
|
||||
*/
|
||||
export function generatePlanModeInfo(ctx: TemplateContext): string {
|
||||
if (!getHostConfig(ctx.host).capabilities.planMode) return '';
|
||||
if (ctx.planMode === false) return '';
|
||||
const entryOrder = ctx.skillName === 'plan-eng-review'
|
||||
? 'starting from the Scope gate, then follow its Startup sequence'
|
||||
: 'starting from the Scope gate (current target announcement or answered question), then preamble and Step 0';
|
||||
|
||||
@@ -117,6 +117,14 @@ export interface TemplateContext {
|
||||
* null/undefined = the host's default root (committed render, unchanged bytes).
|
||||
*/
|
||||
installRoot?: string | null;
|
||||
/**
|
||||
* Plan-mode preamble block. Defaults to true (block ships).
|
||||
* false (opt-in via `--plan-mode=off`): the "## Plan Mode Safe Operations" +
|
||||
* "## Skill Invocation During Plan Mode" section is dropped from the
|
||||
* preamble. "PLAN MODE EXCEPTION — ALWAYS RUN" markers inside skill bodies
|
||||
* still apply.
|
||||
*/
|
||||
planMode?: boolean;
|
||||
}
|
||||
|
||||
/** Resolver function signature. args is populated for parameterized placeholders like {{INVOKE_SKILL:name}}. */
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Voice triggers: speech-to-text aliases a template lists under
|
||||
* `voice-triggers:` in its frontmatter.
|
||||
*
|
||||
* Every host folds them into the skill's description, but Claude's catalog trim
|
||||
* keeps only the lead sentence. {{VOICE_TRIGGER_ROUTING}} gives them a cheap home
|
||||
* in the root router's routing block instead: one routing line per skill.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { TemplateContext } from './types';
|
||||
import { discoverTemplates, includesSkill } from '../discover-skills';
|
||||
import { getHostConfig } from '../../hosts/index';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..', '..');
|
||||
|
||||
/**
|
||||
* Extract voice-triggers YAML list from frontmatter.
|
||||
* Returns an array of trigger strings, or [] if no voice-triggers field.
|
||||
*/
|
||||
export function extractVoiceTriggers(content: string): string[] {
|
||||
const fmStart = content.indexOf('---\n');
|
||||
if (fmStart !== 0) return [];
|
||||
const fmEnd = content.indexOf('\n---', fmStart + 4);
|
||||
if (fmEnd === -1) return [];
|
||||
const frontmatter = content.slice(fmStart + 4, fmEnd);
|
||||
|
||||
const triggers: string[] = [];
|
||||
let inVoice = false;
|
||||
for (const line of frontmatter.split('\n')) {
|
||||
if (/^voice-triggers:/.test(line)) { inVoice = true; continue; }
|
||||
if (inVoice) {
|
||||
const m = line.match(/^\s+-\s+"(.+)"$/);
|
||||
if (m) triggers.push(m[1]);
|
||||
else if (!/^\s/.test(line)) break;
|
||||
}
|
||||
}
|
||||
return triggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude only: other hosts keep the voice triggers in each skill's description.
|
||||
* The template places this at the end of the last routing line, so each line
|
||||
* starts with its own newline and an empty result leaves the list unchanged.
|
||||
*/
|
||||
export function generateVoiceTriggerRouting(ctx: TemplateContext): string {
|
||||
if (ctx.host !== 'claude') return '';
|
||||
const hostConfig = getHostConfig(ctx.host);
|
||||
let out = '';
|
||||
// Sorted so the render doesn't depend on filesystem iteration order.
|
||||
const skillDirs = discoverTemplates(ROOT).map(t => path.dirname(t.tmpl)).sort();
|
||||
for (const skillDir of skillDirs) {
|
||||
if (skillDir === '.' || !includesSkill(hostConfig, skillDir)) continue;
|
||||
const content = fs.readFileSync(path.join(ROOT, skillDir, 'SKILL.md.tmpl'), 'utf-8').replace(/\r\n/g, '\n');
|
||||
const triggers = extractVoiceTriggers(content);
|
||||
if (triggers.length === 0) continue;
|
||||
out += `\n- Voice trigger ${triggers.map(t => `"${t}"`).join(', ')} → invoke \`/${skillDir}\``;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -1168,14 +1168,14 @@ _render_install() {
|
||||
rm -rf "$tmp"
|
||||
rc=0
|
||||
if [ "$host" = claude ]; then
|
||||
out="$(cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs:user --host claude --out-dir "$tmp" --link-root "$render" --install-root "$named" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} 2>&1)" || rc=$?
|
||||
out="$(cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs:user --host claude --out-dir "$tmp" --link-root "$render" --install-root "$named" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} 2>&1)" || rc=$?
|
||||
want="$(find "$SOURCE_GSTACK_DIR" -mindepth 2 -maxdepth 2 -name SKILL.md | wc -l)"
|
||||
got="$(find "$tmp" -mindepth 2 -maxdepth 2 -name SKILL.md 2>/dev/null | wc -l)"
|
||||
[ "$rc" -ne 0 ] || { [ "$want" -eq "$got" ] && grep -qF "$named/bin/gstack-skill-start --skill" "$tmp/SKILL.md" 2>/dev/null; } \
|
||||
|| { rc=1; out="render incomplete: $got of $want skills, or a start line that does not name $named"; }
|
||||
[ "$rc" -ne 0 ] || "$SOURCE_GSTACK_DIR/bin/gstack-patch-names" "$tmp" "$SKILL_PREFIX" >/dev/null || rc=$?
|
||||
else
|
||||
out="$(cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" --install-root "$named" --out-dir "$tmp" --link-root "$render" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} 2>&1)" || rc=$?
|
||||
out="$(cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" --install-root "$named" --out-dir "$tmp" --link-root "$render" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} 2>&1)" || rc=$?
|
||||
want="$(find "$SOURCE_GSTACK_DIR/.agents/skills" -mindepth 2 -maxdepth 2 -name SKILL.md 2>/dev/null | wc -l)"
|
||||
got="$(find "$tmp/.agents/skills" -mindepth 2 -maxdepth 2 -name SKILL.md 2>/dev/null | wc -l)"
|
||||
[ "$rc" -ne 0 ] || { [ "$got" -gt 0 ] && { [ "$want" -eq 0 ] || [ "$want" -eq "$got" ]; } && grep -qF "GSTACK_ROOT=\"$named\"" "$tmp/.agents/skills/gstack-review/SKILL.md" 2>/dev/null; } \
|
||||
@@ -1374,6 +1374,11 @@ _DISABLED_SKILLS="$(gstack_disabled_normalize "$("$GSTACK_CONFIG" get disabled_s
|
||||
# per-install renders) omits disabled skills (C8); the default Claude router is
|
||||
# rendered in place by gstack-relink.
|
||||
_DISABLED_CSV="$(printf '%s' "$_DISABLED_SKILLS" | xargs | tr ' ' ',')"
|
||||
# plan_mode_preamble=off (#3168): every render below omits the plan-mode
|
||||
# preamble; the default Claude install then serves a render instead of the
|
||||
# committed files (bin/gstack-render-claude.sh).
|
||||
_PLAN_MODE_ARG=""
|
||||
[ "$("$GSTACK_CONFIG" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"
|
||||
# _remove_disabled_host_entry ENTRY — drop a non-Claude host's gstack-owned
|
||||
# entry for a disabled skill (a link, or a generated copy); anything else stays.
|
||||
_remove_disabled_host_entry() {
|
||||
@@ -2075,58 +2080,60 @@ if [ "$NEEDS_AGENTS_GEN" -eq 1 ]; then
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
_prune_stale_generated "$SOURCE_GSTACK_DIR" "$AGENTS_DIR" "$CODEX_SKILLS"
|
||||
fi
|
||||
|
||||
# 1c-1g: a build (NEEDS_BUILD=1) already rendered these hosts, but without
|
||||
# --plan-mode=off, so plan_mode_preamble=off renders them again.
|
||||
# 1c. Generate .factory/ Factory Droid skill docs
|
||||
if [ "$INSTALL_FACTORY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
if [ "$INSTALL_FACTORY" -eq 1 ] && { [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }; then
|
||||
log "Generating .factory/ skill docs..."
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host factory ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host factory ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
fi
|
||||
|
||||
# 1d. Generate .opencode/ OpenCode skill docs
|
||||
if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
if [ "$INSTALL_OPENCODE" -eq 1 ] && { [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }; then
|
||||
log "Generating .opencode/ skill docs..."
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host opencode ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host opencode ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
fi
|
||||
|
||||
# 1e. Generate .cursor/ Cursor skill docs
|
||||
if [ "$INSTALL_CURSOR" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
if [ "$INSTALL_CURSOR" -eq 1 ] && { [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }; then
|
||||
log "Generating .cursor/ skill docs..."
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host cursor ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host cursor ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
fi
|
||||
|
||||
# 1f. Generate .copilot/ GitHub Copilot skill docs
|
||||
if [ "$INSTALL_COPILOT" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
if [ "$INSTALL_COPILOT" -eq 1 ] && { [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }; then
|
||||
log "Generating .copilot/ skill docs..."
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host copilot ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host copilot ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
fi
|
||||
|
||||
# 1g. Generate .agy/ Antigravity CLI skill docs
|
||||
if [ "$INSTALL_AGY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
|
||||
if [ "$INSTALL_AGY" -eq 1 ] && { [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }; then
|
||||
log "Generating .agy/ skill docs..."
|
||||
(
|
||||
cd "$SOURCE_GSTACK_DIR"
|
||||
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
|
||||
bun_cmd run gen:skill-docs --host agy ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"}
|
||||
bun_cmd run gen:skill-docs --host agy ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}
|
||||
)
|
||||
fi
|
||||
|
||||
@@ -2737,7 +2744,7 @@ link_codex_skill_dirs() {
|
||||
|
||||
if [ ! -d "$agents_dir" ]; then
|
||||
echo " Generating .agents/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host codex --model "$CODEX_GENERATION_MODEL" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
|
||||
if [ ! -d "$agents_dir" ]; then
|
||||
@@ -3026,7 +3033,7 @@ link_factory_skill_dirs() {
|
||||
|
||||
if [ ! -d "$factory_dir" ]; then
|
||||
echo " Generating .factory/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host factory ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host factory ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
|
||||
if [ ! -d "$factory_dir" ]; then
|
||||
@@ -3069,7 +3076,7 @@ link_opencode_skill_dirs() {
|
||||
|
||||
if [ ! -d "$opencode_dir" ]; then
|
||||
echo " Generating .opencode/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host opencode ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host opencode ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
|
||||
if [ ! -d "$opencode_dir" ]; then
|
||||
@@ -3217,7 +3224,7 @@ link_cursor_skill_dirs() {
|
||||
|
||||
if [ ! -d "$cursor_dir" ]; then
|
||||
echo " Generating .cursor/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host cursor ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host cursor ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
|
||||
if [ ! -d "$cursor_dir" ]; then
|
||||
@@ -3327,7 +3334,7 @@ link_copilot_skill_dirs() {
|
||||
|
||||
if [ ! -d "$copilot_dir" ]; then
|
||||
echo " Generating .copilot/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host copilot ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host copilot ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
if [ ! -d "$copilot_dir" ]; then
|
||||
echo " warning: .copilot/skills/ generation failed — run 'bun run gen:skill-docs --host copilot' manually" >&2
|
||||
@@ -3401,7 +3408,7 @@ link_agy_skill_dirs() {
|
||||
|
||||
if [ ! -d "$agy_dir" ]; then
|
||||
echo " Generating .agy/ skill docs..."
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host agy ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$gstack_dir" && bun_cmd run gen:skill-docs --host agy ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
fi
|
||||
if [ ! -d "$agy_dir" ]; then
|
||||
echo " warning: .agy/skills/ generation failed — run 'bun run gen:skill-docs --host agy' manually" >&2
|
||||
@@ -3644,7 +3651,7 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then
|
||||
KIRO_GSTACK="$KIRO_SKILLS/gstack"
|
||||
# Host identity controls outside-review routing as well as skill availability.
|
||||
# Never borrow .agents or temporarily replace a live Codex model profile.
|
||||
( cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs --host kiro ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} )
|
||||
( cd "$SOURCE_GSTACK_DIR" && bun_cmd run gen:skill-docs --host kiro ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} )
|
||||
mkdir -p "$KIRO_SKILLS"
|
||||
if _sidecar_root_user_owned "$KIRO_GSTACK"; then
|
||||
echo " left in place (existing Kiro runtime root is not gstack-managed): $KIRO_GSTACK" >&2
|
||||
@@ -4179,7 +4186,7 @@ if [ -x "$DETECT_BIN" ]; then
|
||||
# No pipe before the || guard: `cmd | tail -3` reports TAIL's exit
|
||||
# status, so a generator crash read as success (same masking the
|
||||
# main gen:skill-docs site had). Capture, show the tail, propagate.
|
||||
_GEN_USER_OUT=$(bun_cmd run gen:skill-docs:user --host claude --out-dir "$_GSTACK_RENDER_TMP" --link-root "$_GSTACK_RENDER_DIR" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} 2>&1)
|
||||
_GEN_USER_OUT=$(bun_cmd run gen:skill-docs:user --host claude --out-dir "$_GSTACK_RENDER_TMP" --link-root "$_GSTACK_RENDER_DIR" --model "$_GSTACK_OVERLAY" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} ${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"} 2>&1)
|
||||
_GEN_USER_RC=$?
|
||||
printf '%s\n' "$_GEN_USER_OUT" | tail -3
|
||||
exit "$_GEN_USER_RC"
|
||||
@@ -4206,7 +4213,7 @@ if [ -x "$DETECT_BIN" ]; then
|
||||
# A render from a previous gbrain install would shadow canonical files
|
||||
# on the next link/relink — drop it and restore canonical links. A
|
||||
# pinned overlay re-renders it without brain blocks instead (below).
|
||||
if [ -d "$_GSTACK_RENDER_DIR" ] && [ -z "${GSTACK_SKIP_GBRAIN_REGEN:-}" ] && [ "$INSTALL_CLAUDE" -eq 1 ] && [ "$_GSTACK_OVERLAY" = claude ]; then
|
||||
if [ -d "$_GSTACK_RENDER_DIR" ] && [ -z "${GSTACK_SKIP_GBRAIN_REGEN:-}" ] && [ "$INSTALL_CLAUDE" -eq 1 ] && [ "$_GSTACK_OVERLAY" = claude ] && [ -z "$_PLAN_MODE_ARG" ]; then
|
||||
rm -rf "$_GSTACK_RENDER_DIR"
|
||||
gstack_claude_render_settle "$SOURCE_GSTACK_DIR" "$_GSTACK_RENDER_DIR" absent removed
|
||||
if [ "${_CLAUDE_SKILLS_LINKED:-0}" -eq 1 ]; then
|
||||
|
||||
@@ -15,13 +15,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Opens an interactive picker UI where you select which cookie domains to import.
|
||||
Use before QA testing authenticated pages. Use when asked to "import cookies",
|
||||
"login to the site", or "authenticate the browser".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,16 +19,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Detects your deploy
|
||||
platform (Fly.io, Render, Vercel, Netlify, Heroku, GitHub Actions, custom),
|
||||
production URL, health check endpoints, and deploy status commands. Writes
|
||||
the configuration to CLAUDE.md so all future deploys are automatic.
|
||||
Use when: "setup deploy", "configure deployment", "set up land-and-deploy",
|
||||
"how do I deploy with gstack", "add deploy config".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -21,13 +21,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
One command from zero to "gbrain is running, and this agent
|
||||
can call it." Use when: "setup gbrain", "connect gbrain", "start
|
||||
gbrain", "install gbrain", "configure gbrain for this machine".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -22,14 +22,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use when asked to "ship", "deploy",
|
||||
"push to main", "create a PR", "merge and push", or "get it deployed".
|
||||
Proactively invoke this skill (do NOT push/PR directly) when the user says code
|
||||
is ready, asks about deploying, wants to push code up, or asks to create a PR.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,16 +17,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Future /scrape calls with the same intent run
|
||||
the codified script in ~200ms instead of re-driving the page. Walks
|
||||
back through the conversation, synthesizes script.ts + script.test.ts
|
||||
+ fixture, runs the test in a temp dir, and asks before committing.
|
||||
Use when asked to "skillify", "codify", "save this scrape", or
|
||||
"make this permanent".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,14 +20,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Files the issue,
|
||||
optionally spawns a Claude Code agent in a fresh worktree, and lets /ship close
|
||||
the source issue on merge. Use when asked to "spec this out", "file an issue",
|
||||
"write up a ticket", "make this a GitHub issue", or "turn this into a backlog item".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,15 +20,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Wraps the gstack-gbrain-sync orchestrator with
|
||||
state probing, native code-surface registration, capability checks,
|
||||
and a verdict block. Re-runnable, idempotent. Use when: "sync gbrain",
|
||||
"refresh gbrain", "re-index this repo", "gbrain search isn't finding
|
||||
things".
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,11 +19,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Report-only unless you approve a batch. Use for /test-audit.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('catalog mode CLI behavior', () => {
|
||||
test('omitting the flag defaults to trim', () => {
|
||||
expect(frontmatter(defaultContent)).toMatch(/^description: "?Ship workflow:[^\n]*\(gstack\)"?$/m);
|
||||
expect(frontmatter(defaultContent)).not.toMatch(/Use when asked to/i);
|
||||
expect(defaultContent).toContain('## When to invoke this skill');
|
||||
expect(defaultContent).not.toContain('## When to invoke this skill');
|
||||
});
|
||||
|
||||
for (const form of ['equals', 'separate'] as const) {
|
||||
@@ -53,14 +53,12 @@ describe('catalog mode CLI behavior', () => {
|
||||
expect(fm).toMatch(/^description: \|\s*$/m);
|
||||
expect(fm).toMatch(/Use when asked to/i);
|
||||
expect(fm).not.toBe(frontmatter(defaultContent));
|
||||
expect(result.content.slice(fm.length)).not.toContain('## When to invoke this skill');
|
||||
});
|
||||
|
||||
test(`${form} flag form accepts explicit trim and matches the default catalog`, () => {
|
||||
const result = render(args('trim'));
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(frontmatter(result.content)).toBe(frontmatter(defaultContent));
|
||||
expect(result.content).toContain('## When to invoke this skill');
|
||||
});
|
||||
|
||||
test(`${form} flag form rejects invalid modes before writing output`, () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* Unit tests for catalog-trim helpers (gen-skill-docs.ts T4 functions).
|
||||
*
|
||||
* splitCatalogDescription, buildTrimmedDescription, buildWhenToInvokeSection,
|
||||
* applyCatalogTrim — these handle every skill's frontmatter rewrite at gen
|
||||
* splitCatalogDescription, buildTrimmedDescription, applyCatalogTrim — these handle every skill's frontmatter rewrite at gen
|
||||
* time. Two bugs already shipped here:
|
||||
*
|
||||
* v1.45.0.0 design-consultation: when the first sentence exceeded 200 chars,
|
||||
@@ -21,7 +20,6 @@ import { describe, test, expect } from 'bun:test';
|
||||
import {
|
||||
splitCatalogDescription,
|
||||
buildTrimmedDescription,
|
||||
buildWhenToInvokeSection,
|
||||
applyCatalogTrim,
|
||||
toYamlInlineScalar,
|
||||
} from '../scripts/gen-skill-docs';
|
||||
@@ -229,41 +227,6 @@ describe('buildTrimmedDescription', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildWhenToInvokeSection', () => {
|
||||
test('produces markdown H2 with routing prose and voice line', () => {
|
||||
const out = buildWhenToInvokeSection({
|
||||
lead: 'Lead.',
|
||||
routingProse: 'Use when asked to ship.',
|
||||
voiceLine: 'Voice triggers (speech-to-text aliases): "ship it".',
|
||||
hasGstackTag: true,
|
||||
});
|
||||
expect(out).toContain('## When to invoke this skill');
|
||||
expect(out).toContain('Use when asked to ship.');
|
||||
expect(out).toContain('Voice triggers');
|
||||
});
|
||||
|
||||
test('omits routing block when routingProse is empty', () => {
|
||||
const out = buildWhenToInvokeSection({
|
||||
lead: 'Lead.',
|
||||
routingProse: '',
|
||||
voiceLine: null,
|
||||
hasGstackTag: true,
|
||||
});
|
||||
expect(out).toContain('## When to invoke this skill');
|
||||
expect(out).not.toContain('Use when');
|
||||
});
|
||||
|
||||
test('emits even when only voice line is present', () => {
|
||||
const out = buildWhenToInvokeSection({
|
||||
lead: 'Lead.',
|
||||
routingProse: '',
|
||||
voiceLine: 'Voice triggers: x.',
|
||||
hasGstackTag: true,
|
||||
});
|
||||
expect(out).toContain('Voice triggers: x.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyCatalogTrim', () => {
|
||||
const minimalSkill = `---
|
||||
name: example
|
||||
@@ -280,7 +243,7 @@ preamble-tier: 2
|
||||
Original body content here.
|
||||
`;
|
||||
|
||||
test('rewrites multi-line description into one-line + body section', () => {
|
||||
test('rewrites multi-line description into one line and adds no body section', () => {
|
||||
const result = applyCatalogTrim(minimalSkill, 'example');
|
||||
expect(result).not.toBeNull();
|
||||
const { content, parts } = result!;
|
||||
@@ -288,10 +251,10 @@ Original body content here.
|
||||
// description with an interior colon ("Example skill:") is YAML-quoted, so
|
||||
// the value is wrapped in double quotes — tolerate the optional quotes.
|
||||
expect(content).toMatch(/^description: "?Example skill:[^\n]*\(gstack\)"?\n/m);
|
||||
// Body has the When to invoke section
|
||||
expect(content).toContain('## When to invoke this skill');
|
||||
expect(content).toContain('Use when asked to do an example task.');
|
||||
expect(content).toContain('Proactively suggest when');
|
||||
// The body gains no routing section: Claude Code reads a skill body only
|
||||
// after invocation, so routing prose there routes nothing.
|
||||
expect(content).not.toContain('## When to invoke this skill');
|
||||
expect(content).not.toContain('Use when asked to do an example task.');
|
||||
// Original body still present
|
||||
expect(content).toContain('# Example body');
|
||||
expect(content).toContain('Original body content here.');
|
||||
|
||||
@@ -103,6 +103,5 @@ describe('CSO v3 always-loaded policy', () => {
|
||||
const description = frontmatter.match(/^description:\s+(.+)$/m)![1].trim();
|
||||
expect(description.length).toBeLessThanOrEqual(200);
|
||||
expect(description).toContain('(gstack)');
|
||||
expect(controller()).toContain('## When to invoke this skill');
|
||||
});
|
||||
});
|
||||
Vendored
+55
-55
@@ -2,68 +2,68 @@
|
||||
"_comment": "Context-budget ratchet ceilings (~tokens). Regenerate: bun test/helpers/capture-context-budget.ts. Headroom: alwaysOnTotal x1.05, eagerPerInvocation x1.1. Graded by test/context-budget-ratchet.test.ts via lib/context-bill.ts checkBudget.",
|
||||
"alwaysOnTotal": 7458,
|
||||
"eagerPerInvocation": {
|
||||
"autoplan": 17136,
|
||||
"benchmark": 7938,
|
||||
"benchmark-models": 4231,
|
||||
"browse": 8961,
|
||||
"autoplan": 17005,
|
||||
"benchmark": 7813,
|
||||
"benchmark-models": 4086,
|
||||
"browse": 8943,
|
||||
"browser-skills/hackernews-frontpage": 371,
|
||||
"canary": 13520,
|
||||
"canary": 13441,
|
||||
"capy/skill/gstack": 2923,
|
||||
"careful": 1328,
|
||||
"codex": 15717,
|
||||
"context-restore": 10995,
|
||||
"context-save": 10588,
|
||||
"cso": 4715,
|
||||
"design-consultation": 16277,
|
||||
"design-html": 14555,
|
||||
"design-review": 35921,
|
||||
"design-shotgun": 14980,
|
||||
"deslop-shared-libs": 3448,
|
||||
"careful": 1239,
|
||||
"codex": 15594,
|
||||
"context-restore": 10909,
|
||||
"context-save": 10517,
|
||||
"cso": 4660,
|
||||
"design-consultation": 16188,
|
||||
"design-html": 14380,
|
||||
"design-review": 35808,
|
||||
"design-shotgun": 14901,
|
||||
"deslop-shared-libs": 3411,
|
||||
"devex-review": 20161,
|
||||
"diagram": 4336,
|
||||
"document-generate": 11809,
|
||||
"document-release": 10235,
|
||||
"diagram": 4268,
|
||||
"document-generate": 11713,
|
||||
"document-release": 10092,
|
||||
"eval-plan": 9811,
|
||||
"freeze": 1194,
|
||||
"gstack": 4289,
|
||||
"gstack-upgrade": 6407,
|
||||
"guard": 1082,
|
||||
"health": 11521,
|
||||
"investigate": 13465,
|
||||
"ios-clean": 8280,
|
||||
"ios-design-review": 8477,
|
||||
"ios-fix": 8170,
|
||||
"ios-qa": 11742,
|
||||
"ios-sync": 8295,
|
||||
"land-and-deploy": 19061,
|
||||
"landing-report": 8993,
|
||||
"learn": 8708,
|
||||
"make-pdf": 5348,
|
||||
"office-hours": 24993,
|
||||
"open-gstack-browser": 4981,
|
||||
"freeze": 1115,
|
||||
"gstack": 4821,
|
||||
"gstack-upgrade": 6212,
|
||||
"guard": 1000,
|
||||
"health": 11440,
|
||||
"investigate": 13344,
|
||||
"ios-clean": 8139,
|
||||
"ios-design-review": 8305,
|
||||
"ios-fix": 8009,
|
||||
"ios-qa": 11551,
|
||||
"ios-sync": 8167,
|
||||
"land-and-deploy": 18991,
|
||||
"landing-report": 8901,
|
||||
"learn": 8626,
|
||||
"make-pdf": 5204,
|
||||
"office-hours": 24792,
|
||||
"open-gstack-browser": 4872,
|
||||
"openclaw/skills/gstack-openclaw-ceo-review": 2764,
|
||||
"openclaw/skills/gstack-openclaw-investigate": 1429,
|
||||
"openclaw/skills/gstack-openclaw-office-hours": 4433,
|
||||
"openclaw/skills/gstack-openclaw-retro": 2542,
|
||||
"pair-agent": 11156,
|
||||
"plan-ceo-review": 21151,
|
||||
"plan-design-review": 22384,
|
||||
"plan-devex-review": 17595,
|
||||
"plan-eng-review": 13663,
|
||||
"plan-tune": 14773,
|
||||
"qa": 13497,
|
||||
"qa-only": 11543,
|
||||
"retro": 19033,
|
||||
"review": 21135,
|
||||
"scrape": 7354,
|
||||
"setup-browser-cookies": 3628,
|
||||
"setup-deploy": 11549,
|
||||
"setup-gbrain": 15760,
|
||||
"ship": 21164,
|
||||
"skillify": 11648,
|
||||
"spec": 14922,
|
||||
"sync-gbrain": 15477,
|
||||
"test-audit": 10348,
|
||||
"unfreeze": 457
|
||||
"pair-agent": 10995,
|
||||
"plan-ceo-review": 21007,
|
||||
"plan-design-review": 22284,
|
||||
"plan-devex-review": 17556,
|
||||
"plan-eng-review": 13521,
|
||||
"plan-tune": 14589,
|
||||
"qa": 13348,
|
||||
"qa-only": 11429,
|
||||
"retro": 18938,
|
||||
"review": 21047,
|
||||
"scrape": 7312,
|
||||
"setup-browser-cookies": 3565,
|
||||
"setup-deploy": 11440,
|
||||
"setup-gbrain": 15702,
|
||||
"ship": 21081,
|
||||
"skillify": 11551,
|
||||
"spec": 14840,
|
||||
"sync-gbrain": 15399,
|
||||
"test-audit": 10324,
|
||||
"unfreeze": 409
|
||||
}
|
||||
}
|
||||
-8
@@ -22,14 +22,6 @@ triggers:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use when asked to "ship", "deploy",
|
||||
"push to main", "create a PR", "merge and push", or "get it deployed".
|
||||
Proactively invoke this skill (do NOT push/PR directly) when the user says code
|
||||
is ready, asks about deploying, wants to push code up, or asks to create a PR.
|
||||
|
||||
## Preamble (run first)
|
||||
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"agy/gstack-plan-devex-review": 135760,
|
||||
"agy/gstack-plan-eng-review": 152702,
|
||||
"agy/gstack-review": 124398,
|
||||
"claude/design-review": 136973,
|
||||
"claude/design-review": 136536,
|
||||
"codex/gstack-autoplan": 130628,
|
||||
"codex/gstack-design-consultation": 109452,
|
||||
"codex/gstack-design-review": 145033,
|
||||
|
||||
@@ -437,14 +437,13 @@ describe('gen-skill-docs', () => {
|
||||
});
|
||||
|
||||
test('benchmark routing is scoped to web page performance', () => {
|
||||
const generated = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md'), 'utf-8');
|
||||
// The template description is the routing source; the Claude catalog trim
|
||||
// drops its tail from benchmark/SKILL.md.
|
||||
const template = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md.tmpl'), 'utf-8');
|
||||
for (const content of [generated, template]) {
|
||||
const useWhen = content.match(/Use when:[\s\S]*?(?=\n\n|\(gstack\))/)?.[0] ?? '';
|
||||
expect(useWhen).toContain('"web performance benchmark"');
|
||||
expect(useWhen).not.toMatch(/"benchmark"|"performance"/);
|
||||
expect(content).toContain('/benchmark-models');
|
||||
}
|
||||
const useWhen = template.match(/Use when:[\s\S]*?(?=\n\n|\(gstack\))/)?.[0] ?? '';
|
||||
expect(useWhen).toContain('"web performance benchmark"');
|
||||
expect(useWhen).not.toMatch(/"benchmark"|"performance"/);
|
||||
expect(template).toContain('/benchmark-models');
|
||||
const router = fs.readFileSync(path.join(ROOT, 'SKILL.md.tmpl'), 'utf-8');
|
||||
expect(router).toContain('web performance regression → invoke `/benchmark`');
|
||||
expect(router).toContain('model or skill benchmarks → invoke `/benchmark-models`');
|
||||
@@ -4444,12 +4443,29 @@ describe('voice-triggers processing', () => {
|
||||
expect(processVoiceTriggers(content)).toBe(content);
|
||||
});
|
||||
|
||||
test('generated CSO SKILL.md contains voice triggers in description', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'cso', 'SKILL.md'), 'utf-8');
|
||||
test('CSO voice triggers fold into the full description', () => {
|
||||
// The Claude catalog trim drops this line from cso/SKILL.md; full-mode and
|
||||
// other-host renders still carry it.
|
||||
const content = processVoiceTriggers(fs.readFileSync(path.join(ROOT, 'cso', 'SKILL.md.tmpl'), 'utf-8'));
|
||||
expect(content).toContain('"see-so"');
|
||||
expect(content).toContain('Voice triggers (speech-to-text aliases):');
|
||||
});
|
||||
|
||||
test('Claude root router routes every skill voice trigger the catalog trim drops', () => {
|
||||
const routing = between(fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8'), '**Routing rules', '**Route only to skills');
|
||||
const skills = fs.readdirSync(ROOT).filter(d => !fs.lstatSync(path.join(ROOT, d)).isSymbolicLink() && fs.existsSync(path.join(ROOT, d, 'SKILL.md.tmpl')));
|
||||
let routed = 0;
|
||||
for (const skill of skills) {
|
||||
const triggers = extractVoiceTriggers(fs.readFileSync(path.join(ROOT, skill, 'SKILL.md.tmpl'), 'utf-8').replace(/\r\n/g, '\n'));
|
||||
if (triggers.length === 0) continue;
|
||||
const line = routing.split('\n').find(l => l.endsWith(`→ invoke \`/${skill}\``) && l.startsWith('- Voice trigger '));
|
||||
expect(line, skill).toBeDefined();
|
||||
for (const t of triggers) expect(line, skill).toContain(`"${t}"`);
|
||||
routed++;
|
||||
}
|
||||
expect(routed).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('generated CSO SKILL.md does NOT contain raw voice-triggers field', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'cso', 'SKILL.md'), 'utf-8');
|
||||
const fmEnd = content.indexOf('\n---', 4);
|
||||
|
||||
@@ -155,7 +155,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
},
|
||||
behavioral: 'external',
|
||||
externalTest: 'test/skill-e2e-ship-section-loading.test.ts',
|
||||
maxSkeletonBytes: 81_120, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 78_275. + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): Step 16 stage 4 ZERO-RUN/unknown receipt rows, stage 5 honesty inventory, Step 17 open-gate ready-PR gate; measured 80,540 (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured skeleton 81,110 (2026-10-19).
|
||||
maxSkeletonBytes: 81_000, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 78_275. + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): Step 16 stage 4 ZERO-RUN/unknown receipt rows, stage 5 honesty inventory, Step 17 open-gate ready-PR gate; measured 80,540 (2026-10-10).
|
||||
minUnionBytes: 181_000, // token-reduction Phases 1-2 (v1.69.x branch); measured union 201,464
|
||||
mustContain: ['VERSION', 'CHANGELOG', 'review', 'merge', 'PR'],
|
||||
// v1.58.5.0: pre-push-guard install (#2077) stacks on the shared first-run-guidance preamble.
|
||||
@@ -168,7 +168,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
// wave's headline capability) grows the union to 1.195x. Deliberate:
|
||||
// the section is on-demand (loads only for Apple store targets), so
|
||||
// per-invocation cost for non-iOS ships is one manifest line.
|
||||
maxSizeRatio: 1.6504, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 275,422 / 187,706 = 1.4673 (2026-10-06). Combined Oct 6 wave head (B free-text files + D measure loop + A rework): measured 280,363 / 187,706 = 1.4936 (2026-10-06). + gate-once push rule (no second paid gate after a CI push); measured 280,573 / 187,706 = 1.4947 (2026-10-06). + the measurement bar's decision step (exit-code branches for MEETS, MEETS-qualified, needs-classify with the qualifying and fix-round classes, EXTEND once; the measure prose was tightened to offset most of it); measured 281,058 / 187,706 = 1.4973 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 281,438 / 187,706 = 1.4994 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 281,556 / 187,706 = 1.5000 (2026-10-07). + Oct 7 wave B4: /ship publishes through gstack-post (title prepared in a file in the pr-body section, compose block plus gstack-post exit-code branches replacing the two scans); measured 281,894 / 187,706 = 1.5018 (2026-10-07). + B2 early Greptile PR (lazy Step 6.5 section: detect, public-repo consent, push, provisional title file, open; Step 10's bounded wait; the stop-leaves-the-draft rule); measured 286,626 / 187,706 = 1.5270 (2026-10-07). B3, the classifier, B4 and B2 merged: measured 287,128 / 187,706 = 1.5297 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 281,370 / 187,706 = 1.4990 (2026-10-07). Merged with the guard area: measured 287,440 / 187,706 = 1.5313 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 288,783 / 187,706 = 1.5385 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): the shared GATE_SCAN_BLOCK in the core checklist (2.5) and after Step 7's child, the four-option gate question and record fields in 9.4, tests_ran states in Step 5, Gate edits and per-lane counts in the PR body with draft-while-open; measured 300,143 / 187,706 = 1.5990 (2026-10-10). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + Oct 10 multi-agent wave PR E2 (ship/sections/handoff.md: the no-PR automation handoff, with its Section-index row and STOP pointer in the skeleton), offset by tightening Steps 1–3, 12, 14–17 and 20–21 prose so the skeleton stays under 81,000 bytes (80,997); C-pregate landed 1,123 union bytes in tests.md in between; measured 308,619 / 187,706 = 1.6442 on the integra
Line truncated
|
||||
maxSizeRatio: 1.6488, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 275,422 / 187,706 = 1.4673 (2026-10-06). Combined Oct 6 wave head (B free-text files + D measure loop + A rework): measured 280,363 / 187,706 = 1.4936 (2026-10-06). + gate-once push rule (no second paid gate after a CI push); measured 280,573 / 187,706 = 1.4947 (2026-10-06). + the measurement bar's decision step (exit-code branches for MEETS, MEETS-qualified, needs-classify with the qualifying and fix-round classes, EXTEND once; the measure prose was tightened to offset most of it); measured 281,058 / 187,706 = 1.4973 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 281,438 / 187,706 = 1.4994 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 281,556 / 187,706 = 1.5000 (2026-10-07). + Oct 7 wave B4: /ship publishes through gstack-post (title prepared in a file in the pr-body section, compose block plus gstack-post exit-code branches replacing the two scans); measured 281,894 / 187,706 = 1.5018 (2026-10-07). + B2 early Greptile PR (lazy Step 6.5 section: detect, public-repo consent, push, provisional title file, open; Step 10's bounded wait; the stop-leaves-the-draft rule); measured 286,626 / 187,706 = 1.5270 (2026-10-07). B3, the classifier, B4 and B2 merged: measured 287,128 / 187,706 = 1.5297 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 281,370 / 187,706 = 1.4990 (2026-10-07). Merged with the guard area: measured 287,440 / 187,706 = 1.5313 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 288,783 / 187,706 = 1.5385 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): the shared GATE_SCAN_BLOCK in the core checklist (2.5) and after Step 7's child, the four-option gate question and record fields in 9.4, tests_ran states in Step 5, Gate edits and per-lane counts in the PR body with draft-while-open; measured 300,143 / 187,706 = 1.5990 (2026-10-10). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + Oct 10 multi-agent wave PR E2 (ship/sections/handoff.md: the no-PR automation handoff, with its Section-index row and STOP pointer in the skeleton), offset by tightening Steps 1–3, 12, 14–17 and 20–21 prose so the skeleton stays under 81,000 bytes (80,997); C-pregate landed 1,123 union bytes in tests.md in between; measured 308,619 / 187,706 = 1.6442 on the integra
Line truncated
|
||||
},
|
||||
'plan-ceo-review': {
|
||||
skill: 'plan-ceo-review',
|
||||
@@ -191,7 +191,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
mustContain: ['SCOPE EXPANSION', 'SELECTIVE EXPANSION', 'HOLD SCOPE', 'SCOPE REDUCTION'],
|
||||
// Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch
|
||||
// prose replacing the smaller opt-in question) lands this ~5.2% over baseline.
|
||||
maxSizeRatio: 1.0881, // + the Aside probe's failure reason; measured 1.0803 + Oct 10 multi-agent wave PR D1 (reality rows): 0A/0B block in the skeleton, the Reality rows required output, the Completion Summary row and the checklist row; measured 165,042 / 151,747 = 1.0876 (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 165,105 / 151,747 = 1.0880 (2026-10-19).
|
||||
maxSizeRatio: 1.0880, // + the Aside probe's failure reason; measured 1.0803 + Oct 10 multi-agent wave PR D1 (reality rows): 0A/0B block in the skeleton, the Reality rows required output, the Completion Summary row and the checklist row; measured 165,042 / 151,747 = 1.0876 (2026-10-10).
|
||||
},
|
||||
'plan-eng-review': {
|
||||
skill: 'plan-eng-review',
|
||||
@@ -223,7 +223,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
// 1.08 → 1.10: the scope-gate exceptions block (+ its adversarial-review
|
||||
// hardening: host-anchored mode signal, precedence, passing-mention
|
||||
// guards) and the plan-mode preamble reword land the union at 1.092.
|
||||
maxSizeRatio: 1.2197, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 146,905 / 124,597 = 1.1790 (2026-10-06). Combined Oct 6 wave head (A and B): measured 147,524 / 124,597 = 1.1840 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 147,693 / 124,597 = 1.1854 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 147,752 / 124,597 = 1.1858 (2026-10-07). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 147,864 / 124,597 = 1.1867 (2026-10-07). + Oct 10 multi-agent wave PR D1 (reality rows): the Scope Challenge reality-rows block, the deep incident table (18 rows, each tied to the incident that earned it), the Completion summary line and the checklist row; measured 151,423 / 124,597 = 1.2153 (2026-10-10). + #280: the Design Doc Check finds the CEO plan (`gstack-ceo-plan-find`) and names it in the data rule, +325 bytes on main's 151,630; measured 151,955 / 124,597 = 1.2196 (2026-10-12). Fix wave PR 4 (owner-approved raise 1.2170 → 1.2196): #3182's timer-bounded native fallback (net −17 bytes) plus #280's cross-branch rule, offset by tightening the Design Doc Check; measured 151,950 / 124,597 = 1.2195 (2026-10-15). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 151,968 / 124,597 = 1.2197 (2026-10-19).
|
||||
maxSizeRatio: 1.2196, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 146,905 / 124,597 = 1.1790 (2026-10-06). Combined Oct 6 wave head (A and B): measured 147,524 / 124,597 = 1.1840 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 147,693 / 124,597 = 1.1854 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 147,752 / 124,597 = 1.1858 (2026-10-07). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 147,864 / 124,597 = 1.1867 (2026-10-07). + Oct 10 multi-agent wave PR D1 (reality rows): the Scope Challenge reality-rows block, the deep incident table (18 rows, each tied to the incident that earned it), the Completion summary line and the checklist row; measured 151,423 / 124,597 = 1.2153 (2026-10-10). + #280: the Design Doc Check finds the CEO plan (`gstack-ceo-plan-find`) and names it in the data rule, +325 bytes on main's 151,630; measured 151,955 / 124,597 = 1.2196 (2026-10-12). Fix wave PR 4 (owner-approved raise 1.2170 → 1.2196): #3182's timer-bounded native fallback (net −17 bytes) plus #280's cross-branch rule, offset by tightening the Design Doc Check; measured 151,950 / 124,597 = 1.2195 (2026-10-15).
|
||||
},
|
||||
'plan-design-review': {
|
||||
skill: 'plan-design-review',
|
||||
@@ -274,7 +274,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
mustContain: ['developer experience', 'Getting Started'],
|
||||
// Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch
|
||||
// prose replacing the smaller opt-in question) lands this ~5.7% over baseline.
|
||||
maxSizeRatio: 1.0802, // + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 135,283 / 125,243 = 1.0802 (2026-10-19).
|
||||
maxSizeRatio: 1.08,
|
||||
},
|
||||
'office-hours': {
|
||||
skill: 'office-hours',
|
||||
@@ -484,10 +484,10 @@ do not launch the downstream skill or open a browser.`,
|
||||
gateAfterStop: undefined, // operational multi-STOP skill, like ship
|
||||
},
|
||||
behavioral: 'plan',
|
||||
maxSkeletonBytes: 80_960, // Shared-code identity/skip/action rules + critical-severity validation; measured 74,493 (2026-09-17). + v1.91.12.0 merge of #2999 (review clarity repairs: await reads, research alongside dispatch, /review deadline and setup authority, findings sources) with #3002 (guarded state-root lines, plan-check checkpoints); each fit alone; measured 74,881 (2026-10-01). + Oct 8 wave: shared voice directive bans "load-bearing" and adds the reply-language rule, offset by tightening the bounded closer (net +17 bytes per tier-2+ skill); measured 74,884 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W2): Step 3.5 Diff scans with the shared GATE_SCAN_BLOCK, the Step 4 carry, the four-option `review-gate-disposition` question in 5c, gate record fields in 5.8 and the Gate edits report line; measured 80,054 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): Step 4's enum-only paragraph becomes the sideways sweep with the forced-CRITICAL list, plus the flake-fix number sentence in the Gate Integrity paragraph; measured skeleton 80,812 (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured skeleton 80,954 (2026-10-19).
|
||||
maxSkeletonBytes: 80_900, // Shared-code identity/skip/action rules + critical-severity validation; measured 74,493 (2026-09-17). + v1.91.12.0 merge of #2999 (review clarity repairs: await reads, research alongside dispatch, /review deadline and setup authority, findings sources) with #3002 (guarded state-root lines, plan-check checkpoints); each fit alone; measured 74,881 (2026-10-01). + Oct 8 wave: shared voice directive bans "load-bearing" and adds the reply-language rule, offset by tightening the bounded closer (net +17 bytes per tier-2+ skill); measured 74,884 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W2): Step 3.5 Diff scans with the shared GATE_SCAN_BLOCK, the Step 4 carry, the four-option `review-gate-disposition` question in 5c, gate record fields in 5.8 and the Gate edits report line; measured 80,054 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): Step 4's enum-only paragraph becomes the sideways sweep with the forced-CRITICAL list, plus the flake-fix number sentence in the Gate Integrity paragraph; measured skeleton 80,812 (2026-10-10).
|
||||
minUnionBytes: 89_000, // Phase 4 wave 1; measured union 93,357
|
||||
mustContain: ['confidence', 'P1', 'P2', 'Review Army', 'adversarial'],
|
||||
maxSizeRatio: 1.2969, // Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 131,314 / 108,523 = 1.2100 (2026-10-06). Combined Oct 6 wave head (A and B): measured 131,966 / 108,523 = 1.2160 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 132,199 / 108,523 = 1.2182 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 132,258 / 108,523 = 1.2187 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 132,233 / 108,523 = 1.2185 (2026-10-07). Merged with the guard area: measured 132,522 / 108,523 = 1.2211 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 133,902 / 108,523 = 1.2339 (2026-10-08). + gate integrity W2 (above); measured 1.282 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): sideways sweep and flake-fix number in Step 4; `Diff read` and `Undeclared behavior changes` in the review-mode Scope Check (plan-completion section); measured 140,497 / 108,523 = 1.2946 (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 140,737 / 108,523 = 1.2968 (2026-10-19).
|
||||
maxSizeRatio: 1.2960, // Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 131,314 / 108,523 = 1.2100 (2026-10-06). Combined Oct 6 wave head (A and B): measured 131,966 / 108,523 = 1.2160 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 132,199 / 108,523 = 1.2182 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 132,258 / 108,523 = 1.2187 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 132,233 / 108,523 = 1.2185 (2026-10-07). Merged with the guard area: measured 132,522 / 108,523 = 1.2211 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 133,902 / 108,523 = 1.2339 (2026-10-08). + gate integrity W2 (above); measured 1.282 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): sideways sweep and flake-fix number in Step 4; `Diff read` and `Undeclared behavior changes` in the review-mode Scope Check (plan-completion section); measured 140,497 / 108,523 = 1.2946 (2026-10-10).
|
||||
},
|
||||
codex: {
|
||||
skill: 'codex',
|
||||
@@ -539,7 +539,7 @@ do not launch the downstream skill or open a browser.`,
|
||||
},
|
||||
behavioral: 'prompt',
|
||||
maxSkeletonBytes: 74_500, // + Aside browser contract for Step 7 canary ({{ASIDE_SETUP}}); measured 73_523
|
||||
maxSizeRatio: 1.1121, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077. + both untrusted-content marker formats in the browser fallback (W7f) + the readiness gate's project test command and live eval-store paths (s06 H6/H7); measured 1.1076 (2026-10-02). Re-measured 1.0943 (2026-10-03); cap = measured + 0.005 headroom. + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 115,226 / 104,578 = 1.1018 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 116,295 / 104,578 = 1.1120 (2026-10-19).
|
||||
maxSizeRatio: 1.1120, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077. + both untrusted-content marker formats in the browser fallback (W7f) + the readiness gate's project test command and live eval-store paths (s06 H6/H7); measured 1.1076 (2026-10-02). Re-measured 1.0943 (2026-10-03); cap = measured + 0.005 headroom. + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 115,226 / 104,578 = 1.1018 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10).
|
||||
minUnionBytes: 91_000, // Phase 4 wave 1; estimated union ~94.9KB
|
||||
mustContain: ['readiness', 'merge', 'canary', 'revert', 'staging'],
|
||||
},
|
||||
@@ -577,7 +577,7 @@ do not launch the downstream skill or open a browser.`,
|
||||
maxSkeletonBytes: 70_000, // Phase-specific outside coverage, native fallback, and harness guard.
|
||||
minUnionBytes: 85_000, // measured union 86,926
|
||||
mustContain: ['6 Decision Principles', 'TASTE DECISION', 'USER CHALLENGE', 'consensus', 'Restore Point'],
|
||||
maxSizeRatio: 1.1504, // Four validated outside invocations replace raw CLI calls; phases keep independent coverage. + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 114,297 / 101,979 = 1.1208 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 114,533 / 101,979 = 1.1231 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site, and phase-close publishing each report with the `true autoplan-published <phase>` no-op before the next phase entry in a later message (CEO-1); measured 114,320 / 101,979 = 1.1210 (2026-10-07). Merged with the guard area: measured 115,149 / 101,979 = 1.1291 (2026-10-07). + Oct 8 wave (permission cards after a background reviewer): Step 1 puts RESTORE_PATH, and so every phase artifact, in the project's git-excluded `.gstack/tmp/autoplan/`, which Claude Code reads without a card after the skill turn ends; measured 115,270 / 101,979 = 1.1303 (2026-10-08). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 115,267 / 101,979 = 1.1303 (2026-10-07). Both merged: measured 115,126 / 101,979 = 1.1289 (2026-10-08). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + Oct 10 multi-agent wave B9/B10/B12: Phase 0 ESTIMATE and the phase-close timing record, the final gate as a gate list (`gstack-gate render/parse`, stable ids, gate_rev) and the URGENT block above the summary, offset by tightening the Phase 4 prose and the tasks-section; measured 114,980 / 101,979 = 1.1275 (2026-10-10). + Oct 10 multi-agent wave PR D1 (reality rows): the reality-rows check line in the CEO and Eng phase files, the Pre-Gate rule (unattended miss is `incomplete`, never a warning) and the two Pre-Gate cells; measured 117,042 / 101,979 = 1.1477 (2026-10-10). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 117,315 / 101,979 = 1.1504 (2026-10-19).
|
||||
maxSizeRatio: 1.1490, // Four validated outside invocations replace raw CLI calls; phases keep independent coverage. + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 114,297 / 101,979 = 1.1208 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 114,533 / 101,979 = 1.1231 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site, and phase-close publishing each report with the `true autoplan-published <phase>` no-op before the next phase entry in a later message (CEO-1); measured 114,320 / 101,979 = 1.1210 (2026-10-07). Merged with the guard area: measured 115,149 / 101,979 = 1.1291 (2026-10-07). + Oct 8 wave (permission cards after a background reviewer): Step 1 puts RESTORE_PATH, and so every phase artifact, in the project's git-excluded `.gstack/tmp/autoplan/`, which Claude Code reads without a card after the skill turn ends; measured 115,270 / 101,979 = 1.1303 (2026-10-08). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 115,267 / 101,979 = 1.1303 (2026-10-07). Both merged: measured 115,126 / 101,979 = 1.1289 (2026-10-08). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + Oct 10 multi-agent wave B9/B10/B12: Phase 0 ESTIMATE and the phase-close timing record, the final gate as a gate list (`gstack-gate render/parse`, stable ids, gate_rev) and the URGENT block above the summary, offset by tightening the Phase 4 prose and the tasks-section; measured 114,980 / 101,979 = 1.1275 (2026-10-10). + Oct 10 multi-agent wave PR D1 (reality rows): the reality-rows check line in the CEO and Eng phase files, the Pre-Gate rule (unattended miss is `incomplete`, never a warning) and the two Pre-Gate cells; measured 117,042 / 101,979 = 1.1477 (2026-10-10).
|
||||
},
|
||||
spec: {
|
||||
skill: 'spec',
|
||||
@@ -606,7 +606,7 @@ do not launch the downstream skill or open a browser.`,
|
||||
behavioral: 'prompt',
|
||||
maxSkeletonBytes: 58_400, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 57_356
|
||||
minUnionBytes: 64_500, // measured union 67,430
|
||||
maxSizeRatio: 1.0708, // Oct 10 multi-agent wave PR E2: the lazy gate-and-file section renders {{COORDINATOR_CONTRACT}} (the block every spawned lane inherits word for word; `gstack-autoplan contract` prints the same bytes) before the --execute spawn pipe; measured 86,614 / 81,577 = 1.0617 after the contract became a fail-closed `CONTRACT=$(…)` capture (2026-10-10). The skeleton is unchanged (57,107). + GSTA-56 (S1): every shipped bun call carries --no-env-file --config=/dev/null so the audited checkout's .env* and bunfig.toml preload are never loaded (33 bytes per call); measured 87,347 / 81,577 = 1.0707 (2026-10-19).
|
||||
maxSizeRatio: 1.07, // Oct 10 multi-agent wave PR E2: the lazy gate-and-file section renders {{COORDINATOR_CONTRACT}} (the block every spawned lane inherits word for word; `gstack-autoplan contract` prints the same bytes) before the --execute spawn pipe; measured 86,614 / 81,577 = 1.0617 after the contract became a fail-closed `CONTRACT=$(…)` capture (2026-10-10). The skeleton is unchanged (57,107).
|
||||
mustContain: ['HARD GATE', 'dedupe', 'quality gate', 'acceptance criteria', 'archive'],
|
||||
},
|
||||
'setup-gbrain': {
|
||||
|
||||
@@ -35,6 +35,8 @@ const LATER_WAVE_EDITS: Array<[string, string]> = [
|
||||
"The skill-start output above already ran artifacts sync. Act on its lines:\nGBrain hint text (if present) tells you when to prefer `gbrain` over Grep;\n`ARTIFACTS_SYNC:` reports sync health (`off`, `mode=... | queue=N`,\n`remote-mode`, or a restore hint naming `gstack-brain-restore`).\n\nThe one-time privacy stop-gate (artifacts-sync consent) arrives as a\n`GSTACK_INSTRUCTION` block from skill-start when consent is actually pending\n\u2014 fire it via AskUserQuestion exactly as the block instructs."],
|
||||
// Oct 8 wave: the tier-1 voice directive gained the reply-language rule (two lines longer).
|
||||
["Reply in the language of the user's latest message unless asked otherwise. Code, commands, paths, identifiers and quoted output stay verbatim.\n\n", ''],
|
||||
// #3129 dropped the Claude body's "When to invoke" section above the preamble (seven lines shorter).
|
||||
['<!-- Regenerate: bun run gen:skill-docs -->\n\n## Preamble (run first)\n', '<!-- Regenerate: bun run gen:skill-docs -->\n\n\n## When to invoke this skill\n\nOpens an interactive picker UI where you select which cookie domains to import.\nUse before QA testing authenticated pages. Use when asked to "import cookies",\n"login to the site", or "authenticate the browser".\n\n## Preamble (run first)\n'],
|
||||
];
|
||||
|
||||
// The honest-install wave (lazy Chromium) made the fallback probe call
|
||||
|
||||
@@ -229,7 +229,7 @@ const CARVED_INVARIANTS: ParityInvariant[] = Object.values(CARVE_GUARDS).map((g)
|
||||
mustContain: g.mustContain,
|
||||
// CSO's helper trust boundary requires its private startup; demanding the
|
||||
// shared Preamble here would silently reintroduce conflicting policy.
|
||||
mustHaveHeadings: g.skill === 'cso' ? ['## When to invoke'] : ['## Preamble', '## When to invoke'],
|
||||
mustHaveHeadings: g.skill === 'cso' ? [] : ['## Preamble'],
|
||||
maxSizeRatio: g.maxSizeRatio ?? 1.05,
|
||||
}));
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import * as path from 'path';
|
||||
/** /review E2E (sql-injection, enum-completeness, design-lite): the core
|
||||
* review workflow without the shared preamble, Review Army, or Fix-First. */
|
||||
export const REVIEW_E2E_SECTIONS = [
|
||||
'When to invoke this skill',
|
||||
'Step 0: Detect platform and base branch',
|
||||
'Step 1: Check branch',
|
||||
'Step 2: Read the checklist',
|
||||
@@ -54,7 +53,6 @@ export const REVIEW_E2E_SECTIONS = [
|
||||
* (delivery-audit test) + Step 4.5 specialist dispatch (quality score,
|
||||
* JSON findings schema, MULTI-SPECIALIST consensus, Red Team). */
|
||||
export const REVIEW_ARMY_E2E_SECTIONS = [
|
||||
'When to invoke this skill',
|
||||
'Step 0: Detect platform and base branch',
|
||||
'Step 1: Check branch',
|
||||
'Step 1.5: Scope Drift Detection',
|
||||
@@ -72,7 +70,6 @@ export const REVIEW_ARMY_E2E_SECTIONS = [
|
||||
* + the narrative report template. Global mode and Compare mode are not
|
||||
* exercised by the E2E tests and are dropped. */
|
||||
export const RETRO_E2E_SECTIONS = [
|
||||
'When to invoke this skill',
|
||||
'Step 0: Detect platform and base branch',
|
||||
'User-invocable',
|
||||
'Arguments',
|
||||
@@ -86,8 +83,7 @@ export const RETRO_E2E_SECTIONS = [
|
||||
|
||||
/** codex-review-findings E2E against the Codex host variant
|
||||
* (.agents/skills/gstack-review/SKILL.md). Same core workflow as
|
||||
* REVIEW_E2E_SECTIONS, minus "When to invoke this skill" (the Codex host
|
||||
* adapter does not emit that section). */
|
||||
* REVIEW_E2E_SECTIONS. */
|
||||
export const CODEX_REVIEW_E2E_SECTIONS = [
|
||||
'Step 0: Detect platform and base branch',
|
||||
'Step 1: Check branch',
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Unit tests for the --plan-mode=off flag (#3168).
|
||||
*
|
||||
* `--plan-mode=off` drops the "## Plan Mode Safe Operations" +
|
||||
* "## Skill Invocation During Plan Mode" preamble block from generated
|
||||
* skills, for hosts and users who never enter plan mode. Default builds
|
||||
* (planMode undefined or true) keep the block byte-identically.
|
||||
*
|
||||
* Pure-function tests against the resolvers — fast, free, no subprocess.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import type { TemplateContext } from '../scripts/resolvers/types';
|
||||
import { generatePlanModeInfo, generateCompletionStatus } from '../scripts/resolvers/preamble/generate-completion-status';
|
||||
import { generatePreamble } from '../scripts/resolvers/preamble';
|
||||
|
||||
function makeCtx(planMode?: boolean, tier: number = 4): TemplateContext {
|
||||
return {
|
||||
skillName: 'test-skill',
|
||||
tmplPath: '/tmp/test/SKILL.md.tmpl',
|
||||
host: 'claude',
|
||||
paths: {
|
||||
skillRoot: '~/.claude/skills/gstack',
|
||||
localSkillRoot: '.claude/skills',
|
||||
binDir: '~/.claude/skills/gstack/bin',
|
||||
browseDir: '~/.claude/skills/gstack/browse/dist',
|
||||
designDir: '~/.claude/skills/gstack/design/dist',
|
||||
makePdfDir: '~/.claude/skills/gstack/make-pdf/dist',
|
||||
},
|
||||
preambleTier: tier,
|
||||
planMode,
|
||||
};
|
||||
}
|
||||
|
||||
describe('plan-mode off — generatePlanModeInfo', () => {
|
||||
test('planMode undefined: emits both plan-mode sections (default behavior)', () => {
|
||||
const out = generatePlanModeInfo(makeCtx(undefined));
|
||||
expect(out).toContain('## Plan Mode Safe Operations');
|
||||
expect(out).toContain('## Skill Invocation During Plan Mode');
|
||||
});
|
||||
|
||||
test('planMode true: emits both plan-mode sections', () => {
|
||||
const out = generatePlanModeInfo(makeCtx(true));
|
||||
expect(out).toContain('## Plan Mode Safe Operations');
|
||||
expect(out).toContain('## Skill Invocation During Plan Mode');
|
||||
});
|
||||
|
||||
test('planMode false: returns empty string', () => {
|
||||
expect(generatePlanModeInfo(makeCtx(false))).toBe('');
|
||||
});
|
||||
|
||||
test('planMode false: no plan-mode heading survives', () => {
|
||||
const out = generatePlanModeInfo(makeCtx(false));
|
||||
expect(out).not.toContain('Plan Mode Safe Operations');
|
||||
expect(out).not.toContain('Skill Invocation During Plan Mode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('plan-mode off — generatePreamble integration', () => {
|
||||
test('default tier-2 preamble includes the plan-mode block', () => {
|
||||
const out = generatePreamble(makeCtx(undefined, 2));
|
||||
expect(out).toContain('## Plan Mode Safe Operations');
|
||||
expect(out).toContain('## Skill Invocation During Plan Mode');
|
||||
});
|
||||
|
||||
test('planMode false tier-2 preamble drops the plan-mode block', () => {
|
||||
const out = generatePreamble(makeCtx(false, 2));
|
||||
expect(out).not.toContain('## Plan Mode Safe Operations');
|
||||
expect(out).not.toContain('## Skill Invocation During Plan Mode');
|
||||
});
|
||||
|
||||
test('planMode false preamble is measurably smaller than default', () => {
|
||||
const defaultLen = generatePreamble(makeCtx(undefined, 2)).length;
|
||||
const offLen = generatePreamble(makeCtx(false, 2)).length;
|
||||
// The plan-mode block is ~1.8 KB; assert at least 1 KB saved.
|
||||
expect(defaultLen - offLen).toBeGreaterThan(1024);
|
||||
});
|
||||
|
||||
test('planMode undefined preamble is byte-identical to planMode true', () => {
|
||||
expect(generatePreamble(makeCtx(undefined, 2))).toBe(generatePreamble(makeCtx(true, 2)));
|
||||
});
|
||||
|
||||
test('planMode false: PLAN MODE EXCEPTION markers inside skill bodies still apply (completion status keeps its telemetry exception)', () => {
|
||||
const out = generateCompletionStatus(makeCtx(false));
|
||||
expect(out).toContain('PLAN MODE EXCEPTION — ALWAYS RUN');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* --plan-mode=off end to end through the generator, Claude host (#3168).
|
||||
*
|
||||
* Default output must stay byte-identical to the committed SKILL.md files.
|
||||
* With planMode false, every skill that carries the preamble's
|
||||
* "## Plan Mode Safe Operations" and "## Skill Invocation During Plan Mode"
|
||||
* sections loses exactly those two sections and nothing else; skills
|
||||
* without them are unchanged.
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { runGeneration } from '../scripts/gen-skill-docs';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const SAFE = '## Plan Mode Safe Operations\n';
|
||||
const INVOCATION = '## Skill Invocation During Plan Mode\n\n';
|
||||
|
||||
let tmp: string;
|
||||
let skills: string[];
|
||||
|
||||
beforeAll(async () => {
|
||||
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-plan-mode-gen-'));
|
||||
const on = await runGeneration({ host: 'claude', outputRoot: path.join(tmp, 'on'), contentLinkRoot: null });
|
||||
const off = await runGeneration({ host: 'claude', outputRoot: path.join(tmp, 'off'), contentLinkRoot: null, planMode: false });
|
||||
expect(on.exitCode).toBe(0);
|
||||
expect(off.exitCode).toBe(0);
|
||||
skills = on.artifacts.filter(a => a.kind === 'skill').map(a => a.relativePath);
|
||||
});
|
||||
|
||||
afterAll(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
||||
|
||||
const read = (root: string, rel: string) => fs.readFileSync(path.join(root, rel), 'utf8');
|
||||
|
||||
/** The committed text with the two plan-mode sections (each heading plus one paragraph) cut out. */
|
||||
function withoutPlanMode(text: string): string {
|
||||
const start = text.indexOf(SAFE);
|
||||
const invocation = text.indexOf(INVOCATION, start);
|
||||
expect(invocation).toBeGreaterThan(start);
|
||||
const end = text.indexOf('\n\n', invocation + INVOCATION.length) + 2;
|
||||
return text.slice(0, start) + text.slice(end);
|
||||
}
|
||||
|
||||
describe('gen-skill-docs --plan-mode=off (claude host)', () => {
|
||||
test('default output is byte-identical to every committed SKILL.md', () => {
|
||||
expect(skills.length).toBeGreaterThan(40);
|
||||
for (const rel of skills) expect(read(path.join(tmp, 'on'), rel), rel).toBe(read(ROOT, rel));
|
||||
});
|
||||
|
||||
test('off drops exactly the two plan-mode sections and leaves other skills untouched', () => {
|
||||
let stripped = 0;
|
||||
for (const rel of skills) {
|
||||
const committed = read(ROOT, rel);
|
||||
const off = read(path.join(tmp, 'off'), rel);
|
||||
expect(off, rel).not.toContain(SAFE);
|
||||
expect(off, rel).not.toContain('## Skill Invocation During Plan Mode');
|
||||
if (!committed.includes(SAFE)) {
|
||||
expect(off, rel).toBe(committed);
|
||||
continue;
|
||||
}
|
||||
expect(off, rel).toBe(withoutPlanMode(committed));
|
||||
stripped++;
|
||||
}
|
||||
expect(stripped).toBeGreaterThan(40);
|
||||
expect(read(path.join(tmp, 'off'), 'SKILL.md')).toContain('PLAN MODE EXCEPTION');
|
||||
});
|
||||
|
||||
test('the CLI flag parses: --plan-mode=off renders without the sections, an unknown value is refused', () => {
|
||||
const cli = (...args: string[]) => spawnSync('bun', ['run', 'scripts/gen-skill-docs.ts', '--host', 'claude', ...args], {
|
||||
cwd: ROOT, encoding: 'utf8', timeout: 120_000,
|
||||
});
|
||||
const out = path.join(tmp, 'cli');
|
||||
const ok = cli('--out-dir', out, '--plan-mode=off');
|
||||
expect(ok.status, ok.stderr).toBe(0);
|
||||
expect(read(out, 'SKILL.md')).toBe(read(path.join(tmp, 'off'), 'SKILL.md'));
|
||||
expect(read(out, 'review/SKILL.md')).not.toContain(SAFE);
|
||||
const bad = cli('--out-dir', path.join(tmp, 'bad'), '--plan-mode=maybe');
|
||||
expect(bad.status).not.toBe(0);
|
||||
expect(bad.stderr).toContain("Unknown plan mode: maybe. Use 'on' or 'off'.");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* gstack-config plan_mode_preamble (#3168): on | off, default on.
|
||||
*
|
||||
* off makes ./setup render every installed skill without the plan-mode
|
||||
* preamble (--plan-mode=off). The key is a closed domain like explain_level:
|
||||
* an unknown value warns and stores on. Setting it outside ./setup says the
|
||||
* change lands on the next setup run and prints the command that applies it.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { runBin } from './helpers/run-bin';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN_CONFIG = path.join(ROOT, 'bin', 'gstack-config');
|
||||
|
||||
let tmpHome: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-plan-mode-cfg-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function run(args: string[], env: Record<string, string | undefined> = { GSTACK_SETUP_RUNNING: undefined }) {
|
||||
return runBin(BIN_CONFIG, args, { gstackHome: tmpHome, cwd: ROOT, trim: true, env });
|
||||
}
|
||||
|
||||
describe('gstack-config plan_mode_preamble', () => {
|
||||
test('defaults to on, in get, list and defaults', () => {
|
||||
expect(run(['get', 'plan_mode_preamble']).stdout).toBe('on');
|
||||
expect(run(['defaults']).stdout).toMatch(/plan_mode_preamble:\s+on$/m);
|
||||
expect(run(['list']).stdout).toMatch(/plan_mode_preamble:\s+on \(default\)$/m);
|
||||
});
|
||||
|
||||
test('accepts off and on', () => {
|
||||
expect(run(['set', 'plan_mode_preamble', 'off']).status).toBe(0);
|
||||
expect(run(['get', 'plan_mode_preamble']).stdout).toBe('off');
|
||||
expect(run(['list']).stdout).toMatch(/plan_mode_preamble:\s+off \(set\)$/m);
|
||||
expect(run(['set', 'plan_mode_preamble', 'on']).status).toBe(0);
|
||||
expect(run(['get', 'plan_mode_preamble']).stdout).toBe('on');
|
||||
});
|
||||
|
||||
test('an unknown value warns and stores on', () => {
|
||||
run(['set', 'plan_mode_preamble', 'off']);
|
||||
const junk = run(['set', 'plan_mode_preamble', 'false']);
|
||||
expect(junk.status).toBe(0);
|
||||
expect(junk.stderr).toContain("plan_mode_preamble 'false' not recognized. Valid values: on, off. Using on.");
|
||||
expect(run(['get', 'plan_mode_preamble']).stdout).toBe('on');
|
||||
});
|
||||
|
||||
test('outside setup, set says it applies on the next ./setup run and gives the command', () => {
|
||||
const r = run(['set', 'plan_mode_preamble', 'off']);
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).toBe(`gstack-config: plan_mode_preamble=off applies on the next ./setup run. Apply now: cd ${ROOT} && ./setup --refresh-registered`);
|
||||
});
|
||||
|
||||
test('inside setup, set prints nothing', () => {
|
||||
const r = run(['set', 'plan_mode_preamble', 'off'], { GSTACK_SETUP_RUNNING: '1' });
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).toBe('');
|
||||
});
|
||||
|
||||
test('the config header documents the key and its values', () => {
|
||||
run(['set', 'plan_mode_preamble', 'on']);
|
||||
const cfg = fs.readFileSync(path.join(tmpHome, 'config.yaml'), 'utf-8');
|
||||
expect(cfg).toContain('# plan_mode_preamble: on');
|
||||
expect(cfg).toContain('# off = rendered skills omit the "Plan Mode Safe');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* plan_mode_preamble=off reaches every installed render (#3168).
|
||||
*
|
||||
* Installed gstack never runs the generator by hand, so the key only works if
|
||||
* every render ./setup and the bin/ helpers run carries --plan-mode=off. The
|
||||
* default Claude install serves the committed SKILL.md files (which carry the
|
||||
* preamble) with no render at all, so with the key off it must get a render,
|
||||
* and turning the key back on must drop that render even where a detector
|
||||
* failure (gbrain=unknown) would otherwise keep it.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const PLAN_ARG = '${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}';
|
||||
const GENERATOR = /(?:\bbun|\bbun_cmd|_gstack_render_bun) run (?:gen:skill-docs(?::user)?|scripts\/gen-skill-docs\.ts) --[a-z]/;
|
||||
const CONFIG_READ = '[ "$("$GSTACK_CONFIG" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"';
|
||||
|
||||
/** Generator invocations in a file: not comments, and not echoed or logged instructions. */
|
||||
function renders(rel: string): string[] {
|
||||
return fs.readFileSync(path.join(ROOT, rel), 'utf8').split('\n')
|
||||
.filter(line => GENERATOR.test(line) && !/^\s*(#|echo\b|log\b)/.test(line));
|
||||
}
|
||||
|
||||
describe('every installed-skill render carries the plan-mode arg', () => {
|
||||
test('setup: every gen:skill-docs call site, with the key read once', () => {
|
||||
const lines = renders('setup');
|
||||
expect(lines.length).toBeGreaterThanOrEqual(16);
|
||||
for (const line of lines) expect(line, line).toContain(PLAN_ARG);
|
||||
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf8');
|
||||
expect(setup.split(CONFIG_READ).length - 1).toBe(1);
|
||||
expect(setup.indexOf(CONFIG_READ)).toBeLessThan(setup.indexOf('bun_cmd run gen:skill-docs --host factory'));
|
||||
});
|
||||
|
||||
test('the Claude render helper, gbrain-refresh and the relink router render', () => {
|
||||
for (const rel of ['bin/gstack-render-claude.sh', 'bin/gstack-config', 'bin/gstack-relink']) {
|
||||
const lines = renders(rel);
|
||||
expect(lines.length, rel).toBeGreaterThanOrEqual(1);
|
||||
for (const line of lines) expect(line, `${rel}: ${line}`).toContain(PLAN_ARG);
|
||||
}
|
||||
expect(fs.readFileSync(path.join(ROOT, 'bin/gstack-relink'), 'utf8')).toContain(CONFIG_READ);
|
||||
expect(fs.readFileSync(path.join(ROOT, 'bin/gstack-config'), 'utf8'))
|
||||
.toContain('[ "$("$0" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"');
|
||||
});
|
||||
|
||||
test('setup: a build (which renders without the arg) does not skip the host re-renders when the key is off', () => {
|
||||
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf8');
|
||||
const guards = setup.split('\n').filter(line => /^if \[ "\$INSTALL_[A-Z]+" -eq 1 \] && .*NEEDS_BUILD/.test(line));
|
||||
expect(guards.length).toBe(5);
|
||||
for (const line of guards) expect(line, line).toContain('{ [ "$NEEDS_BUILD" -eq 0 ] || [ -n "$_PLAN_MODE_ARG" ]; }');
|
||||
});
|
||||
|
||||
test('setup: with the key off the gbrain-absent branch leaves the render for the plain re-render (a failed render keeps it)', () => {
|
||||
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf8');
|
||||
const removal = setup.split('\n').filter(line => /if \[ -d "\$_GSTACK_RENDER_DIR" \].*\$_GSTACK_OVERLAY" = claude/.test(line));
|
||||
expect(removal.length).toBe(1);
|
||||
expect(removal[0]).toContain('[ -z "$_PLAN_MODE_ARG" ]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-relink router re-render', () => {
|
||||
const BINS = ['gstack-config', 'gstack-relink', 'gstack-patch-names', 'gstack-state-root.sh', 'gstack-install-registry.sh'];
|
||||
const SAFE = '## Plan Mode Safe Operations';
|
||||
|
||||
function relinkFixture() {
|
||||
const install = path.join(tmp, 'gstack');
|
||||
fs.mkdirSync(path.join(install, 'bin'), { recursive: true });
|
||||
for (const b of BINS) {
|
||||
fs.copyFileSync(path.join(ROOT, 'bin', b), path.join(install, 'bin', b));
|
||||
fs.chmodSync(path.join(install, 'bin', b), 0o755);
|
||||
}
|
||||
fs.symlinkSync(path.join(ROOT, 'scripts'), path.join(install, 'scripts'));
|
||||
fs.copyFileSync(path.join(ROOT, 'SKILL.md'), path.join(install, 'SKILL.md'));
|
||||
fs.mkdirSync(path.join(install, 'ship'));
|
||||
fs.writeFileSync(path.join(install, 'ship', 'SKILL.md'), '---\nname: ship\ndescription: test\n---\n# ship\n');
|
||||
const env = {
|
||||
...process.env,
|
||||
GSTACK_HOME: path.join(tmp, 'state'),
|
||||
GSTACK_STATE_ROOT: '',
|
||||
GSTACK_STATE_DIR: '',
|
||||
GSTACK_INSTALL_DIR: install,
|
||||
GSTACK_SKILLS_DIR: path.join(tmp, 'skills'),
|
||||
GSTACK_SETUP_RUNNING: '1',
|
||||
};
|
||||
const run = (bin: string, ...args: string[]) => {
|
||||
const r = spawnSync('bash', [path.join(install, 'bin', bin), ...args], { encoding: 'utf8', timeout: 60_000, env });
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
return r;
|
||||
};
|
||||
const router = () => fs.readFileSync(path.join(install, 'SKILL.md'), 'utf8');
|
||||
const alias = () => fs.readFileSync(path.join(tmp, 'skills', '_gstack-command', 'SKILL.md'), 'utf8');
|
||||
const asAlias = (text: string) => text.replace(/^name: gstack$/m, 'name: _gstack-command');
|
||||
return { run, router, alias, asAlias };
|
||||
}
|
||||
|
||||
test('off strips the router and its _gstack-command alias; on restores both to the committed bytes', () => {
|
||||
const { run, router, alias, asAlias } = relinkFixture();
|
||||
const committed = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf8');
|
||||
run('gstack-relink');
|
||||
expect(router()).toBe(committed);
|
||||
expect(alias()).toBe(asAlias(committed));
|
||||
|
||||
run('gstack-config', 'set', 'plan_mode_preamble', 'off');
|
||||
run('gstack-relink');
|
||||
expect(router()).not.toContain(SAFE);
|
||||
expect(router()).toContain('PLAN MODE EXCEPTION');
|
||||
expect(alias()).toBe(asAlias(router()));
|
||||
|
||||
run('gstack-config', 'set', 'plan_mode_preamble', 'on');
|
||||
run('gstack-relink');
|
||||
expect(router()).toBe(committed);
|
||||
expect(alias()).toBe(asAlias(committed));
|
||||
});
|
||||
|
||||
test('disabled_skills: the alias follows the re-rendered router in the same run', () => {
|
||||
const { run, router, alias, asAlias } = relinkFixture();
|
||||
run('gstack-config', 'set', 'disabled_skills', 'ship');
|
||||
run('gstack-relink');
|
||||
expect(router()).toContain('Disabled on this install (never invoke or suggest them): `/ship`.');
|
||||
expect(alias()).toBe(asAlias(router()));
|
||||
});
|
||||
});
|
||||
|
||||
let tmp: string;
|
||||
beforeEach(() => { tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-plan-mode-render-')); });
|
||||
afterEach(() => { fs.rmSync(tmp, { recursive: true, force: true }); });
|
||||
|
||||
/**
|
||||
* Source the real helper with a stub generator (bun_cmd) that logs its
|
||||
* arguments and writes one SKILL.md into --out-dir, then run SCRIPT.
|
||||
* PLAN is the caller's _PLAN_MODE_ARG; OVERLAY a pinned overlay or claude.
|
||||
*/
|
||||
function helper(script: string, plan: string, overlay = 'claude') {
|
||||
const src = path.join(tmp, 'src');
|
||||
fs.mkdirSync(src, { recursive: true });
|
||||
fs.writeFileSync(path.join(src, 'VERSION'), '0.1.2.3\n');
|
||||
const prelude = `set -euo pipefail
|
||||
. ${JSON.stringify(path.join(ROOT, 'bin/gstack-render-claude.sh'))}
|
||||
_swap_in_render() { rm -rf "$1"; mv "$2" "$1"; }
|
||||
bun_cmd() {
|
||||
printf '%s\\n' "$*" >> "$TMPROOT/bun.log"
|
||||
local out="" prev=""
|
||||
for a in "$@"; do [ "$prev" = --out-dir ] && out="$a"; prev="$a"; done
|
||||
mkdir -p "$out/autoplan" && echo rendered > "$out/autoplan/SKILL.md"
|
||||
}
|
||||
SRC="$TMPROOT/src"; R="$TMPROOT/render/claude"
|
||||
_GSTACK_OVERLAY="$OVERLAY"; _GSTACK_OVERLAY_ID="$OVERLAY"; _GSTACK_OVERLAY_GENERIC=0
|
||||
_GSTACK_OVERLAY_SOURCE=default; [ "$OVERLAY" = claude ] || _GSTACK_OVERLAY_SOURCE=explicit
|
||||
_PLAN_MODE_ARG="$PLAN"
|
||||
`;
|
||||
const r = spawnSync('bash', ['-c', prelude + script], {
|
||||
encoding: 'utf8',
|
||||
timeout: 30_000,
|
||||
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', TMPROOT: tmp, PLAN: plan, OVERLAY: overlay },
|
||||
});
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
return r.stdout;
|
||||
}
|
||||
const action = (gbrain: string, plan: string, overlay = 'claude') =>
|
||||
helper(`gstack_claude_render_action "$R" ${gbrain}; echo "$_GSTACK_RENDER_ACTION"`, plan, overlay).trim();
|
||||
const record = () => fs.readFileSync(path.join(tmp, 'render/claude.overlay'), 'utf8');
|
||||
const bunLog = () => fs.readFileSync(path.join(tmp, 'bun.log'), 'utf8');
|
||||
|
||||
describe('gstack_claude_render_action / render_plain / render_settle', () => {
|
||||
test('key on or unset: the default install keeps today\'s actions', () => {
|
||||
expect(action('unknown', '')).toBe('keep');
|
||||
expect(action('absent', '')).toBe('remove');
|
||||
expect(action('ok', '')).toBe('user');
|
||||
expect(action('absent', '', 'opus-4-7')).toBe('plain');
|
||||
});
|
||||
|
||||
test('key off: the default install renders (plain) whatever detection says, brain renders stay user', () => {
|
||||
expect(action('unknown', '--plan-mode=off')).toBe('plain');
|
||||
expect(action('absent', '--plan-mode=off')).toBe('plain');
|
||||
expect(action('ok', '--plan-mode=off')).toBe('user');
|
||||
});
|
||||
|
||||
test('key off: the plain render passes --plan-mode=off and the record says so', () => {
|
||||
const out = helper('gstack_claude_render_plain "$SRC" "$R" && gstack_claude_render_settle "$SRC" "$R" absent rendered', '--plan-mode=off');
|
||||
expect(out).toContain('Rendering Claude skills with the claude overlay and no plan-mode preamble into');
|
||||
expect(bunLog()).toMatch(/run gen:skill-docs --host claude --out-dir \S+ --link-root \S+ --model claude --plan-mode=off$/m);
|
||||
expect(fs.existsSync(path.join(tmp, 'render/claude/autoplan/SKILL.md'))).toBe(true);
|
||||
expect(record()).toBe('overlay=claude\nsource=default\nmodel=claude\nversion=0.1.2.3\ngbrain=absent\nrender=user\nplan_mode=off\n');
|
||||
expect(out).not.toContain('Claude skill overlay:');
|
||||
});
|
||||
|
||||
test('switching back on drops the stale render, including the gbrain=unknown keep branch', () => {
|
||||
helper('gstack_claude_render_plain "$SRC" "$R" && gstack_claude_render_settle "$SRC" "$R" unknown rendered', '--plan-mode=off');
|
||||
expect(record()).toContain('plan_mode=off');
|
||||
expect(action('unknown', '')).toBe('remove');
|
||||
expect(action('absent', '')).toBe('remove');
|
||||
helper('rm -rf "$R"; gstack_claude_render_settle "$SRC" "$R" unknown removed', '');
|
||||
expect(fs.existsSync(path.join(tmp, 'render/claude.overlay'))).toBe(false);
|
||||
expect(action('unknown', '')).toBe('keep');
|
||||
});
|
||||
|
||||
test('a brain render made with the key on is still kept on gbrain=unknown', () => {
|
||||
helper('mkdir -p "$R" && gstack_claude_render_settle "$SRC" "$R" ok rendered', '');
|
||||
expect(record()).not.toContain('plan_mode');
|
||||
expect(action('unknown', '')).toBe('keep');
|
||||
});
|
||||
|
||||
test('key on: a pinned render and its record are byte-for-byte unchanged', () => {
|
||||
helper('gstack_claude_render_plain "$SRC" "$R" && gstack_claude_render_settle "$SRC" "$R" absent rendered', '', 'opus-4-7');
|
||||
expect(bunLog()).toMatch(/run gen:skill-docs --host claude --out-dir \S+ --link-root \S+ --model opus-4-7$/m);
|
||||
expect(record()).toBe('overlay=opus-4-7\nsource=explicit\nmodel=opus-4-7\nversion=0.1.2.3\ngbrain=absent\nrender=user\n');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* plan_mode_preamble through the real ./setup on a default Claude install (#3168).
|
||||
*
|
||||
* The default install serves the committed SKILL.md files with no render, so
|
||||
* with the key off setup must render the skills without the plan-mode
|
||||
* preamble and serve that render (router included, via gstack-relink); with
|
||||
* the key back on, the render goes away, including when the gbrain detector
|
||||
* fails (the gbrain=unknown branch that otherwise keeps a render).
|
||||
* Fixture: test/helpers/install-fixture.ts, overlay helpers:
|
||||
* test/helpers/claude-overlay-fixture.ts.
|
||||
*/
|
||||
import { afterAll, afterEach, describe, expect, test } from 'bun:test';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { config, defaultRender, failableBun, fakeDetect, record, served, setup } from './helpers/claude-overlay-fixture';
|
||||
import { rmSync } from 'node:fs';
|
||||
import { ROOT, cleanupFixtures, cleanupSeed, makeFixture, makeSource, put } from './helpers/install-fixture';
|
||||
|
||||
afterEach(cleanupFixtures);
|
||||
afterAll(cleanupSeed);
|
||||
|
||||
const SAFE = '## Plan Mode Safe Operations';
|
||||
const alias = (skills: string) => readFileSync(join(skills, '_gstack-command/SKILL.md'), 'utf8');
|
||||
const asAlias = (router: string) => router.replace(/^name: gstack$/m, 'name: _gstack-command');
|
||||
|
||||
describe.skipIf(process.platform === 'win32')('./setup with plan_mode_preamble', () => {
|
||||
test('off renders the default install without the preamble and keeps it through a failed render; on (detector failing) serves the committed files again', () => {
|
||||
const f = makeFixture();
|
||||
failableBun(f);
|
||||
const src = makeSource(f, join(f.home, '.claude/skills/gstack'));
|
||||
fakeDetect(src);
|
||||
const skills = join(f.home, '.claude/skills');
|
||||
const committedSkill = readFileSync(join(ROOT, 'autoplan/SKILL.md'), 'utf8');
|
||||
const committedRouter = readFileSync(join(ROOT, 'SKILL.md'), 'utf8');
|
||||
expect(committedSkill).toContain(SAFE);
|
||||
|
||||
expect(config(f, src, 'set', 'plan_mode_preamble', 'off').status).toBe(0);
|
||||
const off = setup(f, src);
|
||||
expect(off.status, off.out).toBe(0);
|
||||
expect(off.out).toContain('and no plan-mode preamble');
|
||||
expect(record(defaultRender(f))).toMatchObject({ overlay: 'claude', source: 'default', gbrain: 'absent', render: 'user', plan_mode: 'off' });
|
||||
expect(served(skills)).not.toContain(SAFE);
|
||||
expect(served(skills)).not.toContain('## Skill Invocation During Plan Mode');
|
||||
expect(readFileSync(join(src, 'SKILL.md'), 'utf8')).not.toContain(SAFE);
|
||||
expect(off.out).not.toContain('Claude skill overlay:');
|
||||
expect(alias(skills)).toBe(asAlias(readFileSync(join(src, 'SKILL.md'), 'utf8')));
|
||||
|
||||
put(join(f.home, 'fail-render'), '1');
|
||||
const failed = setup(f, src);
|
||||
expect(failed.status, failed.out).toBe(0);
|
||||
expect(record(defaultRender(f))).toMatchObject({ plan_mode: 'off', render: 'user' });
|
||||
expect(served(skills)).not.toContain(SAFE);
|
||||
rmSync(join(f.home, 'fail-render'));
|
||||
|
||||
expect(config(f, src, 'set', 'plan_mode_preamble', 'on').status).toBe(0);
|
||||
put(join(f.home, 'gbrain-mode'), 'fail');
|
||||
const on = setup(f, src);
|
||||
expect(on.status, on.out).toBe(0);
|
||||
expect(on.out).toContain('gstack-gbrain-detect failed');
|
||||
expect(existsSync(defaultRender(f))).toBe(false);
|
||||
expect(existsSync(`${defaultRender(f)}.overlay`)).toBe(false);
|
||||
expect(served(skills)).toBe(readFileSync(join(src, 'autoplan/SKILL.md'), 'utf8'));
|
||||
expect(served(skills)).toBe(committedSkill);
|
||||
expect(readFileSync(join(src, 'SKILL.md'), 'utf8')).toBe(committedRouter);
|
||||
expect(alias(skills)).toBe(asAlias(committedRouter));
|
||||
}, 240_000);
|
||||
});
|
||||
@@ -23,7 +23,6 @@ const evalCollector = createEvalCollector('e2e-retro');
|
||||
// still an extraction, not a full-file copy (sections ARE the minimal
|
||||
// on-demand units; same pattern as skill-e2e-review-army.test.ts).
|
||||
const RETRO_SKELETON_SECTIONS = [
|
||||
'When to invoke this skill',
|
||||
'Step 0: Detect platform and base branch',
|
||||
'User-invocable',
|
||||
'Arguments',
|
||||
|
||||
@@ -1779,14 +1779,13 @@ describe('Skill trigger phrases', () => {
|
||||
];
|
||||
|
||||
for (const skill of SKILLS_REQUIRING_TRIGGERS) {
|
||||
test(`${skill}/SKILL.md has "Use when" trigger phrases`, () => {
|
||||
const skillPath = path.join(ROOT, skill, 'SKILL.md');
|
||||
test(`${skill}/SKILL.md.tmpl has "Use when" trigger phrases`, () => {
|
||||
const skillPath = path.join(ROOT, skill, 'SKILL.md.tmpl');
|
||||
if (!fs.existsSync(skillPath)) return;
|
||||
const content = fs.readFileSync(skillPath, 'utf-8');
|
||||
// v1.45.0.0 catalog trim moved trigger prose out of frontmatter into a
|
||||
// body "## When to invoke" section. Search the full file content, not
|
||||
// just frontmatter. The trigger phrase must still appear somewhere in
|
||||
// the skill so agents can match user requests to the skill.
|
||||
// The catalog trim drops trigger prose from the Claude SKILL.md, so check
|
||||
// the template description: --catalog-mode=full and the other hosts
|
||||
// still route on it.
|
||||
expect(content).toMatch(/Use when/i);
|
||||
});
|
||||
}
|
||||
@@ -1799,11 +1798,11 @@ describe('Skill trigger phrases', () => {
|
||||
];
|
||||
|
||||
for (const skill of SKILLS_REQUIRING_PROACTIVE) {
|
||||
test(`${skill}/SKILL.md has proactive routing phrase`, () => {
|
||||
const skillPath = path.join(ROOT, skill, 'SKILL.md');
|
||||
test(`${skill}/SKILL.md.tmpl has proactive routing phrase`, () => {
|
||||
const skillPath = path.join(ROOT, skill, 'SKILL.md.tmpl');
|
||||
if (!fs.existsSync(skillPath)) return;
|
||||
const content = fs.readFileSync(skillPath, 'utf-8');
|
||||
// Same catalog-trim consideration — search the full file content.
|
||||
// Same catalog-trim consideration — check the template description.
|
||||
expect(content).toMatch(/Proactively (suggest|invoke)/i);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,13 +13,6 @@ allowed-tools:
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
|
||||
## When to invoke this skill
|
||||
|
||||
Use when you want to widen edit scope without ending the session.
|
||||
Use when asked to "unfreeze", "unlock edits", "remove freeze", or
|
||||
"allow all edits".
|
||||
|
||||
# /unfreeze — Clear Freeze Boundary
|
||||
|
||||
Remove the edit restriction set by `/freeze`, allowing edits to all directories.
|
||||
|
||||
Reference in new issue
Block a user