From ed107918db445ce474f4cbab41f648e958c47639 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 31 Oct 2026 18:25:15 -0700 Subject: [PATCH] v1.92.6.0 feat: proactive core routes only the four core skills, and they carry their trigger phrases (#3770) * proactive core: the CLAUDE.md offer and descriptions follow core; core skills carry their trigger phrases (#3446, #2118) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (core routing line keeps in-skill handoffs) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep document-release's pre-merge cue and the ship catalog-mode pin after the description edits Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: plain sentence break in the fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 16 ++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- autoplan/SKILL.md.tmpl | 4 +- bin/gstack-skill-start | 20 +++- design-consultation/SKILL.md.tmpl | 4 +- design-html/SKILL.md.tmpl | 3 +- design-review/SKILL.md.tmpl | 4 +- design-shotgun/SKILL.md.tmpl | 4 +- devex-review/SKILL.md.tmpl | 2 +- docs/proactive.md | 30 +++--- docs/voice-input.md | 7 +- document-release/SKILL.md.tmpl | 2 +- gstack/llms.txt | 6 +- investigate/SKILL.md.tmpl | 5 +- learn/SKILL.md.tmpl | 2 - package.json | 2 +- plan-ceo-review/SKILL.md.tmpl | 4 +- plan-design-review/SKILL.md.tmpl | 4 +- plan-devex-review/SKILL.md.tmpl | 4 +- plan-eng-review/SKILL.md.tmpl | 4 +- plan-tune/SKILL.md.tmpl | 3 - qa-only/SKILL.md.tmpl | 3 +- qa/SKILL.md | 2 +- qa/SKILL.md.tmpl | 5 +- retro/SKILL.md.tmpl | 3 +- review/SKILL.md | 2 +- review/SKILL.md.tmpl | 2 +- ship/SKILL.md | 2 +- ship/SKILL.md.tmpl | 4 +- ...toplan-preconfigured-onboarding-ar.test.ts | 3 +- test/catalog-mode-full.test.ts | 2 +- test/fixtures/golden/agy-ship-SKILL.md | 4 +- test/fixtures/golden/claude-ship-SKILL.md | 2 +- test/fixtures/golden/codex-ship-SKILL.md | 4 +- test/fixtures/golden/factory-ship-SKILL.md | 4 +- test/fixtures/skill-md-size-allowlist.json | 92 +++++++++---------- test/proactive-core-set.test.ts | 37 ++++++++ test/proactive-skill-start.test.ts | 34 ++++++- .../ship/gsta225-core-routing-triggers.json | 4 + test/skill-validation.test.ts | 24 +++-- 41 files changed, 231 insertions(+), 136 deletions(-) create mode 100644 test/size-ledger/ship/gsta225-core-routing-triggers.json diff --git a/CHANGELOG.md b/CHANGELOG.md index dbeb1f9fe..53ace22f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.92.6.0] - 2026-11-01 + +**Choosing "core" now means core everywhere gstack routes, and the four core skills carry their plain-English trigger phrases where hosts read them.** Under the default `proactive: core`, the CLAUDE.md routing section gstack offered still listed all 13 routing rules, and 16 non-core skills still told Codex and the other hosts to "proactively suggest" them (#3446). Separately, phrases like "test the site" or "ship it" lived only in each skill's `triggers:` metadata, which no host reads, so they never reached the list the agent picks skills from (#2118). The always-loaded Claude catalog does not grow. It measures 5,682 bytes before and 5,678 after (1,421 → 1,420 token-equivalents against the unchanged 1,423 ceiling). + +### Changed +- **The CLAUDE.md routing offer follows the setting.** Under `core`, `bin/gstack-skill-start` offers the four core rules (/office-hours, /qa, /review, /ship) and one line saying the other skills are not suggested: they run when you type them or when a running skill hands off to them (so /ship still dispatches /document-release). Under `all` it offers the same 13 rules as before. Sections already in a project's CLAUDE.md are left alone; docs/proactive.md says how to trim one. +- **Only the core skills invite proactive suggestions.** The "Proactively suggest/invoke ..." sentence is gone from the descriptions of /autoplan, /design-consultation, /design-html, /design-review, /design-shotgun, /devex-review, /document-release, /investigate, /learn, /plan-ceo-review, /plan-design-review, /plan-devex-review, /plan-eng-review, /plan-tune, /qa-only and /retro. Their "Use when asked to ..." sentences stay. On Codex and the other hosts that keep full descriptions, `proactive: all` no longer gets that hint from those 16 skills. +- **The core skills' descriptions carry their trigger phrases.** /qa's catalog entry is now "Test the site, API or CLI and fix the bugs it finds.", /review's "Review this PR's diff.", and /ship's "Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, push and create a PR." Every `triggers:` phrase of the four core skills now appears in its full description. + +### Tests +- `test/proactive-skill-start.test.ts`: runs `gstack-skill-start` under `core`, `all` and the legacy `true`; core offers exactly the four core rules plus the scoping line, and all offers the shipped 13-rule section. +- `test/proactive-core-set.test.ts`: each core skill's full description carries every `triggers:` phrase, its catalog entry carries at least one, and no other skill's catalog entry carries that phrase. +- `test/skill-validation.test.ts`: no non-core template description says "Proactively suggest/invoke"; the proactive-phrase check now covers the four core skills. +- All five new cases fail on main at 2d1b55d (office-hours already passed there). +- Size ratchets move down only: 46 generated-SKILL.md pins in `test/fixtures/skill-md-size-allowlist.json` drop by the removed sentences, and /ship's size ledger records -21 bytes. The four ship goldens are regenerated for the new description. + ## [1.92.5.0] - 2026-10-31 **Updates no longer stall in silence when the project around your gstack install has committed gstack's files (#1022).** If gstack lives inside another git project (for example `/.claude/skills/gstack`) and that project committed gstack's files as ordinary files, a stash, reset or branch switch in the project put the old files back. VERSION stayed frozen at an old release while updates reported success. Once this release is on disk, every update path stops before it changes anything and prints the exact fix. An install the project has already reverted is still running the project's old copies of these scripts, so it needs the one-time repair in `docs/troubleshooting.md#auto-update-tracked-by-project` first. diff --git a/VERSION b/VERSION index 6da18df07..b5362af17 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.92.5.0 +1.92.6.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 3ef8a82b3..b9ead66a9 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.92.5.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.92.6.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, diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index 9b8d31310..6fa40c659 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -8,9 +8,7 @@ description: | 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. (gstack) + automatically", or "make the decisions for me". (gstack) codex_default_prompt: "Use $gstack-autoplan to run the CEO, design, DX and eng reviews over this plan in one pass, decide the routine questions itself, and stop at a single approval gate." voice-triggers: - "auto plan" diff --git a/bin/gstack-skill-start b/bin/gstack-skill-start index 8562708b7..caa694aeb 100755 --- a/bin/gstack-skill-start +++ b/bin/gstack-skill-start @@ -609,8 +609,26 @@ fi # is a quoted heredoc (markdown backticks must stay literal), so the resolved # bin path is substituted via sed on the __BIN__ placeholder. Under off the # routing rules are the auto-invocation the user turned off, so no offer. +# Under core the heredoc's rule lines are replaced by the core rules below, so +# the offered section routes only the four core skills. +_CORE_ROUTING_RULES=$(cat <<'EOR' +- Product ideas/brainstorming → invoke /office-hours +- QA/testing site behavior → invoke /qa +- Code review/diff check → invoke /review +- Ship/deploy/PR → invoke /ship + +Do not suggest or auto-invoke other gstack skills: they run when the user types them, or when a running skill hands off to them (`gstack-config set proactive all` routes every skill). +EOR +) +_routing_offer() { + if [ "$_PROACTIVE" = "core" ]; then + _GSTACK_CORE_RULES="$_CORE_ROUTING_RULES" awk '/^- .* → invoke \// { if (!done) { print ENVIRON["_GSTACK_CORE_RULES"]; done = 1 } next } { print }' + else + cat + fi | sed "s|__BIN__|$_BIN|g" +} if [ "$_HAS_ROUTING" = "no" ] && [ "$_ROUTING_DECLINED" = "false" ] && [ "$_PROACTIVE_PROMPTED" = "yes" ] && [ "$_PROACTIVE_OFF" = "no" ]; then - sed "s|__BIN__|$_BIN|g" <<'EOI' | _emit_block routing-injection + _routing_offer <<'EOI' | _emit_block routing-injection gstack works best when the project CLAUDE.md includes skill routing rules. If no CLAUDE.md exists in the project root, create it. Use AskUserQuestion: > gstack works best when your project's CLAUDE.md includes skill routing rules. diff --git a/design-consultation/SKILL.md.tmpl b/design-consultation/SKILL.md.tmpl index 64d1e4c57..90d42033b 100644 --- a/design-consultation/SKILL.md.tmpl +++ b/design-consultation/SKILL.md.tmpl @@ -7,9 +7,7 @@ description: | complete design system (aesthetic, typography, color, layout, spacing, motion), and generates font+color preview pages. 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. (gstack) + Use when asked to "design system", "brand guidelines", or "create DESIGN.md". (gstack) allowed-tools: - Bash - Read diff --git a/design-html/SKILL.md.tmpl b/design-html/SKILL.md.tmpl index bf5314f5b..889c37269 100644 --- a/design-html/SKILL.md.tmpl +++ b/design-html/SKILL.md.tmpl @@ -9,8 +9,7 @@ description: | 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. (gstack) + "build me a page", "implement this design", or after any planning skill. (gstack) voice-triggers: - "build the design" - "code the mockup" diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index 3c3212f37..41dc7620f 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -7,9 +7,7 @@ description: | AI slop patterns, and slow interactions — then fixes them. 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. (gstack) + Use when asked to "audit the design", "visual QA", "check if it looks good", or "design polish". (gstack) allowed-tools: - Bash - Read diff --git a/design-shotgun/SKILL.md.tmpl b/design-shotgun/SKILL.md.tmpl index ac85e05c4..1c5a9b6e5 100644 --- a/design-shotgun/SKILL.md.tmpl +++ b/design-shotgun/SKILL.md.tmpl @@ -6,9 +6,7 @@ description: | Design shotgun: generate multiple AI design variants, open a comparison board, collect structured feedback, and iterate. 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. (gstack) + "visual brainstorm", or "I don't like how this looks". (gstack) triggers: - explore design variants - show me design options diff --git a/devex-review/SKILL.md.tmpl b/devex-review/SKILL.md.tmpl index 80a350971..37406b105 100644 --- a/devex-review/SKILL.md.tmpl +++ b/devex-review/SKILL.md.tmpl @@ -9,7 +9,7 @@ description: | 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. (gstack) + onboarding". (gstack) voice-triggers: - "dx audit" - "test the developer experience" diff --git a/docs/proactive.md b/docs/proactive.md index c2e7dba96..350984656 100644 --- a/docs/proactive.md +++ b/docs/proactive.md @@ -34,21 +34,23 @@ needed, because every skill reads `PROACTIVE` from `gstack-skill-start` at the start of each run and the router (`/gstack`) applies it to its routing rules. A value gstack does not know (a hand-edited `proactive: maybe`) reads as `core`. -Two surfaces follow the setting only in part. The first is the `## Skill routing` -section gstack offers to append to your project's CLAUDE.md. Under `off` the offer is -not made, because those rules are the auto-invocation you turned off (a section -already in CLAUDE.md stays; delete it yourself). Under `core` the offered -section still lists every routing rule and tells the host to invoke any -matching skill. If you accepted it before the `core` default, trim that section -to the four core rules (or delete it and let gstack offer it again); making the -offered section follow `core` is tracked in #3446. +The `## Skill routing` section gstack offers to append to your project's +CLAUDE.md follows the setting too. Under `core` it lists only the four core +rules and says the other skills run when you type them or a running skill +hands off to them; under `all` it lists +every routing rule; under `off` it is not offered, because those rules are the +auto-invocation you turned off. gstack offers the section once per project and +never edits it afterwards. So a section you accepted earlier, or under another +setting, stays as it is: trim it to match (the core four are /office-hours, +/review, /qa and /ship), or delete it and let gstack offer it again. -The second is the full skill descriptions, which a `--catalog-mode=full` build -and the non-Claude hosts keep. Non-core skills still say "Proactively suggest -when ..." there, and that text does not change with the setting, so a host -that picks skills from those descriptions can still reach them under `core`. -The default Claude build uses one-line descriptions without that sentence. -#3446 tracks that too. +Skill descriptions cannot change with the setting, so they are written for the +`core` default. Only the four core skills say "Proactively suggest ..." in the +full descriptions that a `--catalog-mode=full` build and the non-Claude hosts +keep. The other skills keep their "Use when asked to ..." sentences, which +describe asks. On those hosts, `all` therefore loses the proactive hint the +non-core descriptions used to carry; the router's routing rules still apply when +`/gstack` runs. gstack asks once, after the telemetry question, which of the three you want; if you stored `off` before that, it takes the answer as given and does not ask. diff --git a/docs/voice-input.md b/docs/voice-input.md index 12a771bed..3401077f6 100644 --- a/docs/voice-input.md +++ b/docs/voice-input.md @@ -11,4 +11,9 @@ on. The separate `triggers:` list in a skill's frontmatter is gstack metadata, not a matching surface. Claude Code and Codex choose a skill from its name and description, so a phrase that appears only under `triggers:` does not activate -the skill (Codex output drops the field; the GBrain host keeps it). +the skill (Codex output drops the field; the GBrain host keeps it). The four core +skills (/office-hours, /review, /qa, /ship) carry their `triggers:` phrases in +the description itself: every phrase in the full description, and at least one +in the one-line catalog entry Claude Code reads ("test the site" for /qa, "ship +it" for /ship). The other skills do not, because under the `core` default they +wait for you to type them (docs/proactive.md). diff --git a/document-release/SKILL.md.tmpl b/document-release/SKILL.md.tmpl index 0de95321d..c568872ca 100644 --- a/document-release/SKILL.md.tmpl +++ b/document-release/SKILL.md.tmpl @@ -9,7 +9,7 @@ description: | 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. (gstack) + or "post-ship docs". Runs before merge. (gstack) allowed-tools: - Bash - Read diff --git a/gstack/llms.txt b/gstack/llms.txt index c53668753..eddaf4208 100644 --- a/gstack/llms.txt +++ b/gstack/llms.txt @@ -56,15 +56,15 @@ Conventions: - [/plan-devex-review](plan-devex-review/SKILL.md): Interactive developer experience plan review. - [/plan-eng-review](plan-eng-review/SKILL.md): Eng manager-mode plan review. - [/plan-tune](plan-tune/SKILL.md): Self-tuning question sensitivity + developer psychographic for gstack (v1: observational). -- [/qa](qa/SKILL.md): Fix browser/API/CLI/job/worker/webhook bugs. +- [/qa](qa/SKILL.md): Test the site, API or CLI and fix the bugs it finds. - [/qa-only](qa-only/SKILL.md): Report browser/API/CLI/job/worker/webhook bugs. - [/retro](retro/SKILL.md): Weekly engineering retrospective. -- [/review](review/SKILL.md): Pre-landing PR review. +- [/review](review/SKILL.md): Review this PR's diff. - [/scrape](scrape/SKILL.md): Pull data from a web page through the Aside browser — your real, already signed-in sessions. - [/setup-browser-cookies](setup-browser-cookies/SKILL.md): Import cookies from your real Chromium browser into the headless browse session. - [/setup-deploy](setup-deploy/SKILL.md): Configure deployment settings for /land-and-deploy. - [/setup-gbrain](setup-gbrain/SKILL.md): Set up gbrain for this coding agent: install the CLI, initialize a local PGLite or Supabase brain, register MCP, capture per-remote trust policy. -- [/ship](ship/SKILL.md): Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. +- [/ship](ship/SKILL.md): Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, push and create a PR. - [/skillify](skillify/SKILL.md): Codify the most recent successful /scrape flow into a permanent browser-skill on disk. - [/spec](spec/SKILL.md): Turn vague intent into a precise, executable spec in five phases. - [/sync-gbrain](sync-gbrain/SKILL.md): Keep gbrain current with this repo's code and refresh agent search guidance in CLAUDE.md. diff --git a/investigate/SKILL.md.tmpl b/investigate/SKILL.md.tmpl index 122201bfa..4a9b36f36 100644 --- a/investigate/SKILL.md.tmpl +++ b/investigate/SKILL.md.tmpl @@ -6,10 +6,7 @@ description: | Systematic debugging with root cause investigation. 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. (gstack) + "investigate this error", or "root cause analysis". (gstack) allowed-tools: - Bash - Read diff --git a/learn/SKILL.md.tmpl b/learn/SKILL.md.tmpl index 187beaab1..1aa14c464 100644 --- a/learn/SKILL.md.tmpl +++ b/learn/SKILL.md.tmpl @@ -6,8 +6,6 @@ description: | Manage project learnings. 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?" triggers: - show learnings - what have we learned diff --git a/package.json b/package.json index ecf88e66a..7de1c0bb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.92.5", + "version": "1.92.6", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/plan-ceo-review/SKILL.md.tmpl b/plan-ceo-review/SKILL.md.tmpl index 9a8a0f47b..934863593 100644 --- a/plan-ceo-review/SKILL.md.tmpl +++ b/plan-ceo-review/SKILL.md.tmpl @@ -9,9 +9,7 @@ description: | 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. (gstack) + or "is this ambitious enough". (gstack) benefits-from: [office-hours] allowed-tools: - Read diff --git a/plan-design-review/SKILL.md.tmpl b/plan-design-review/SKILL.md.tmpl index 52ce41d51..36abe29f7 100644 --- a/plan-design-review/SKILL.md.tmpl +++ b/plan-design-review/SKILL.md.tmpl @@ -8,9 +8,7 @@ description: | 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. (gstack) + or "design critique". (gstack) allowed-tools: - Read - Edit diff --git a/plan-devex-review/SKILL.md.tmpl b/plan-devex-review/SKILL.md.tmpl index 572890826..18a919793 100644 --- a/plan-devex-review/SKILL.md.tmpl +++ b/plan-devex-review/SKILL.md.tmpl @@ -9,9 +9,7 @@ description: | 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). (gstack) + or "API design review". (gstack) voice-triggers: - "dx review" - "developer experience review" diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index 3bda37347..1b299b289 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -7,9 +7,7 @@ description: | Eng manager-mode plan review. 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. (gstack) + "review the architecture", "engineering review", or "lock in the plan". (gstack) voice-triggers: - "tech review" - "technical review" diff --git a/plan-tune/SKILL.md.tmpl b/plan-tune/SKILL.md.tmpl index b88d59bed..748dfad62 100644 --- a/plan-tune/SKILL.md.tmpl +++ b/plan-tune/SKILL.md.tmpl @@ -12,9 +12,6 @@ description: | 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". (gstack) - - 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. triggers: - tune questions - stop asking me that diff --git a/qa-only/SKILL.md.tmpl b/qa-only/SKILL.md.tmpl index aacc16cc9..a172c11b4 100644 --- a/qa-only/SKILL.md.tmpl +++ b/qa-only/SKILL.md.tmpl @@ -6,8 +6,7 @@ description: | Report browser/API/CLI/job/worker/webhook bugs. 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. (gstack) + "test but don't fix". For the full test-fix-verify loop, use /qa instead. (gstack) voice-triggers: - "bug report" - "just check for bugs" diff --git a/qa/SKILL.md b/qa/SKILL.md index cd3983c92..cdb56114f 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -2,7 +2,7 @@ name: qa preamble-tier: 4 version: 2.0.0 -description: Fix browser/API/CLI/job/worker/webhook bugs. (gstack) +description: Test the site, API or CLI and fix the bugs it finds. (gstack) allowed-tools: - Bash - Read diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 347afed67..dfa2352a8 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -3,8 +3,9 @@ name: qa preamble-tier: 4 version: 2.0.0 description: | - Fix browser/API/CLI/job/worker/webhook bugs. - Commit verified fixes atomically. Use when asked to "qa", "QA", "test this site", "find bugs", + Test the site, API or CLI and fix the bugs it finds. + Covers browser/API/CLI/job/worker/webhook bugs and commits verified fixes atomically. + Use when asked to "qa", "QA", "qa test this", "test this site", "find bugs", "find bugs on site", "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), diff --git a/retro/SKILL.md.tmpl b/retro/SKILL.md.tmpl index 44ab0dd0f..4dbc9bf97 100644 --- a/retro/SKILL.md.tmpl +++ b/retro/SKILL.md.tmpl @@ -6,8 +6,7 @@ description: | Weekly engineering retrospective. 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. (gstack) + Use when asked to "weekly retro", "what did we ship", or "engineering retrospective". (gstack) allowed-tools: - Bash - Read diff --git a/review/SKILL.md b/review/SKILL.md index d8aba2c97..8ae904514 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -2,7 +2,7 @@ name: review preamble-tier: 4 version: 1.0.0 -description: Pre-landing PR review. (gstack) +description: Review this PR's diff. (gstack) allowed-tools: - Bash - Read diff --git a/review/SKILL.md.tmpl b/review/SKILL.md.tmpl index b22eaf442..8b2514193 100644 --- a/review/SKILL.md.tmpl +++ b/review/SKILL.md.tmpl @@ -3,7 +3,7 @@ name: review preamble-tier: 4 version: 1.0.0 description: | - Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust + Review this PR's diff. Checks it 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. (gstack) diff --git a/ship/SKILL.md b/ship/SKILL.md index 128892e8e..307938c82 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -2,7 +2,7 @@ name: ship preamble-tier: 4 version: 1.0.0 -description: "Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. (gstack)" +description: "Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, push and create a PR. (gstack)" allowed-tools: - Bash - Read diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 035e0e452..84614fe0a 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -3,8 +3,8 @@ name: ship preamble-tier: 4 version: 1.0.0 description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, - update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, + push and create a PR. Use when asked to "ship", "deploy", "deploy this", "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. (gstack) diff --git a/test/autoplan-preconfigured-onboarding-ar.test.ts b/test/autoplan-preconfigured-onboarding-ar.test.ts index 0320e22bf..54d43d392 100644 --- a/test/autoplan-preconfigured-onboarding-ar.test.ts +++ b/test/autoplan-preconfigured-onboarding-ar.test.ts @@ -18,7 +18,8 @@ function fixture(plan = original) { writeFileSync(planFile, plan); execFileSync('git', ['init', '-q', '-b', 'main'], { cwd }); // This free test controls unrelated startup side effects, not the seeded repo. - writeFileSync(join(state, 'config.yaml'), 'update_check: false\nartifacts_sync: off\ntelemetry: off\n'); + // proactive: all, because the seeded CLAUDE.md is the every-rule section that setting offers. + writeFileSync(join(state, 'config.yaml'), 'update_check: false\nartifacts_sync: off\ntelemetry: off\nproactive: all\n'); writeFileSync(join(state, '.proactive-prompted'), ''); const env = { PATH: process.env.PATH!, HOME: home, GSTACK_HOME: state, GSTACK_STATE_ROOT: state, GSTACK_LEASE_DIR: join(state, 'gstack-leases') }; const start = () => execFileSync(join(root, 'bin/gstack-skill-start'), ['--skill', 'autoplan'], diff --git a/test/catalog-mode-full.test.ts b/test/catalog-mode-full.test.ts index 1f5432c0e..014c488d8 100644 --- a/test/catalog-mode-full.test.ts +++ b/test/catalog-mode-full.test.ts @@ -38,7 +38,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)).toMatch(/^description: "?Ship it:[^\n]*\(gstack\)"?$/m); expect(frontmatter(defaultContent)).not.toMatch(/Use when asked to/i); expect(defaultContent).not.toContain('## When to invoke this skill'); }); diff --git a/test/fixtures/golden/agy-ship-SKILL.md b/test/fixtures/golden/agy-ship-SKILL.md index 24e2c0e9b..49706f47c 100644 --- a/test/fixtures/golden/agy-ship-SKILL.md +++ b/test/fixtures/golden/agy-ship-SKILL.md @@ -1,8 +1,8 @@ --- name: gstack-ship description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, - update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, + push and create a PR. Use when asked to "ship", "deploy", "deploy this", "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. (gstack) diff --git a/test/fixtures/golden/claude-ship-SKILL.md b/test/fixtures/golden/claude-ship-SKILL.md index 128892e8e..307938c82 100644 --- a/test/fixtures/golden/claude-ship-SKILL.md +++ b/test/fixtures/golden/claude-ship-SKILL.md @@ -2,7 +2,7 @@ name: ship preamble-tier: 4 version: 1.0.0 -description: "Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. (gstack)" +description: "Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, push and create a PR. (gstack)" allowed-tools: - Bash - Read diff --git a/test/fixtures/golden/codex-ship-SKILL.md b/test/fixtures/golden/codex-ship-SKILL.md index 7edbf9d56..06a95feae 100644 --- a/test/fixtures/golden/codex-ship-SKILL.md +++ b/test/fixtures/golden/codex-ship-SKILL.md @@ -1,8 +1,8 @@ --- name: ship description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, - update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, + push and create a PR. Use when asked to "ship", "deploy", "deploy this", "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. (gstack) diff --git a/test/fixtures/golden/factory-ship-SKILL.md b/test/fixtures/golden/factory-ship-SKILL.md index 30543432a..4d4da1a16 100644 --- a/test/fixtures/golden/factory-ship-SKILL.md +++ b/test/fixtures/golden/factory-ship-SKILL.md @@ -1,8 +1,8 @@ --- name: ship description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, - update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG, + push and create a PR. Use when asked to "ship", "deploy", "deploy this", "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. (gstack) diff --git a/test/fixtures/skill-md-size-allowlist.json b/test/fixtures/skill-md-size-allowlist.json index a1841bb5c..78fb4e85b 100644 --- a/test/fixtures/skill-md-size-allowlist.json +++ b/test/fixtures/skill-md-size-allowlist.json @@ -2,51 +2,51 @@ "_comment": "Generated SKILL.md files over the limit, pinned at their exact character count (#3199). test/skill-md-size-guard.test.ts fails when a pinned skill grows past its pin or shrinks below it: lower the pin when a skill shrinks and delete it once the skill is within the limit. Raising a pin is an owner decision. The shared-preamble hoist (GSTA-96) moved every tier 2+ skill's shared preamble into sections/preamble.md; the pins below are what remains.", "limit": 100000, "pins": { - "agy/gstack-autoplan": 109810, - "agy/gstack-plan-design-review": 110044, - "agy/gstack-plan-devex-review": 116496, - "agy/gstack-plan-eng-review": 144907, - "agy/gstack-review": 104280, - "codex/gstack-autoplan": 108553, - "codex/gstack-plan-design-review": 111116, - "codex/gstack-plan-devex-review": 118004, - "codex/gstack-plan-eng-review": 146072, - "codex/gstack-review": 103803, - "copilot/gstack-autoplan": 109253, - "copilot/gstack-plan-design-review": 110355, - "copilot/gstack-plan-devex-review": 115958, - "copilot/gstack-plan-eng-review": 145072, - "copilot/gstack-review": 103861, - "cursor/gstack-autoplan": 108485, - "cursor/gstack-plan-design-review": 108900, - "cursor/gstack-plan-devex-review": 115228, - "cursor/gstack-plan-eng-review": 143662, - "cursor/gstack-review": 122151, - "factory/gstack-autoplan": 110763, - "factory/gstack-plan-design-review": 111675, - "factory/gstack-plan-devex-review": 118591, - "factory/gstack-plan-eng-review": 146770, - "factory/gstack-review": 125924, - "gbrain/gstack-autoplan": 108892, - "gbrain/gstack-plan-eng-review": 126289, - "hermes/gstack-autoplan": 108891, - "hermes/gstack-plan-eng-review": 126321, - "kiro/gstack-autoplan": 108259, - "kiro/gstack-plan-design-review": 108752, - "kiro/gstack-plan-devex-review": 115080, - "kiro/gstack-plan-eng-review": 143524, - "kiro/gstack-review": 121975, - "openclaw/gstack-autoplan": 108715, - "openclaw/gstack-plan-eng-review": 124804, - "opencode/gstack-autoplan": 109234, - "opencode/gstack-plan-design-review": 110187, - "opencode/gstack-plan-devex-review": 115836, - "opencode/gstack-plan-eng-review": 144931, - "opencode/gstack-review": 122773, - "slate/gstack-autoplan": 108372, - "slate/gstack-plan-design-review": 108826, - "slate/gstack-plan-devex-review": 115154, - "slate/gstack-plan-eng-review": 143593, - "slate/gstack-review": 122063 + "agy/gstack-autoplan": 109666, + "agy/gstack-plan-design-review": 109926, + "agy/gstack-plan-devex-review": 116369, + "agy/gstack-plan-eng-review": 144761, + "agy/gstack-review": 104276, + "codex/gstack-autoplan": 108409, + "codex/gstack-plan-design-review": 110998, + "codex/gstack-plan-devex-review": 117877, + "codex/gstack-plan-eng-review": 145926, + "codex/gstack-review": 103799, + "copilot/gstack-autoplan": 109109, + "copilot/gstack-plan-design-review": 110237, + "copilot/gstack-plan-devex-review": 115831, + "copilot/gstack-plan-eng-review": 144926, + "copilot/gstack-review": 103857, + "cursor/gstack-autoplan": 108341, + "cursor/gstack-plan-design-review": 108782, + "cursor/gstack-plan-devex-review": 115101, + "cursor/gstack-plan-eng-review": 143516, + "cursor/gstack-review": 122147, + "factory/gstack-autoplan": 110619, + "factory/gstack-plan-design-review": 111557, + "factory/gstack-plan-devex-review": 118464, + "factory/gstack-plan-eng-review": 146624, + "factory/gstack-review": 125920, + "gbrain/gstack-autoplan": 108748, + "gbrain/gstack-plan-eng-review": 126143, + "hermes/gstack-autoplan": 108751, + "hermes/gstack-plan-eng-review": 126179, + "kiro/gstack-autoplan": 108115, + "kiro/gstack-plan-design-review": 108634, + "kiro/gstack-plan-devex-review": 114953, + "kiro/gstack-plan-eng-review": 143378, + "kiro/gstack-review": 121971, + "openclaw/gstack-autoplan": 108571, + "openclaw/gstack-plan-eng-review": 124658, + "opencode/gstack-autoplan": 109090, + "opencode/gstack-plan-design-review": 110069, + "opencode/gstack-plan-devex-review": 115709, + "opencode/gstack-plan-eng-review": 144785, + "opencode/gstack-review": 122769, + "slate/gstack-autoplan": 108228, + "slate/gstack-plan-design-review": 108708, + "slate/gstack-plan-devex-review": 115027, + "slate/gstack-plan-eng-review": 143447, + "slate/gstack-review": 122059 } } diff --git a/test/proactive-core-set.test.ts b/test/proactive-core-set.test.ts index a54503e5d..578e09137 100644 --- a/test/proactive-core-set.test.ts +++ b/test/proactive-core-set.test.ts @@ -9,6 +9,7 @@ import { describe, expect, test } from 'bun:test'; import * as fs from 'fs'; import * as path from 'path'; +import { frontmatterList } from '../lib/skill-triggers'; import { QUESTIONS } from '../scripts/question-registry'; import { generateUpgradeCheck } from '../scripts/resolvers/preamble/generate-upgrade-check'; @@ -151,3 +152,39 @@ describe('proactive core set: every surface names the same four skills and three expect(routerTmpl.replace(/\s+/g, ' ')).toContain('`gstack-config set proactive core` restores the default'); }); }); + +/** + * Value: protects=the four core skills, the ones gstack routes by plain speech + * under the core default, carry their own `triggers:` phrases where hosts read + * them: at least one in the Claude catalog one-liner, every one in the full + * description other hosts keep, and no core one-liner phrase in another + * skill's one-liner; fails_when=a core lead or description loses its trigger + * phrases, or a trigger phrase also matches another skill's catalog entry; + * why_new=#2118: `triggers:` was metadata no host reads, and no test tied it + * to a description; seam=none; no_claim=how a host ranks or picks skills + * (static text, no model run) + */ +describe('proactive core set: core skills carry their trigger phrases (#2118)', () => { + const norm = (s: string) => s.replace(/\s+/g, ' ').toLowerCase(); + const description = (md: string): string => { + const fm = md.slice(0, md.indexOf('\n---', 4)); + const block = fm.match(/^description:\s*\|\s*\n((?:[ \t]+.*\n|\n)+)/m)?.[1]; + return norm(block ?? fm.match(/^description:\s*(.+)$/m)?.[1]?.replace(/^"|"$/g, '') ?? ''); + }; + const skillDirs = fs.readdirSync(ROOT).filter(dir => fs.existsSync(path.join(ROOT, dir, 'SKILL.md.tmpl')) && fs.existsSync(path.join(ROOT, dir, 'SKILL.md'))); + const catalog = Object.fromEntries(skillDirs.map(dir => [dir, description(fs.readFileSync(path.join(ROOT, dir, 'SKILL.md'), 'utf-8'))])); + + test.each(CORE.map(s => s.slice(1)))('%s: every trigger phrase is in its full description, and one is in its catalog one-liner that no other one-liner carries', (skill) => { + const tmpl = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md.tmpl'), 'utf-8'); + const triggers = frontmatterList(tmpl, 'triggers').map(norm); + expect(triggers.length).toBeGreaterThan(0); + const full = description(tmpl); + for (const phrase of triggers) expect(full, `${skill} full description lacks "${phrase}"`).toContain(phrase); + const inCatalog = triggers.filter(phrase => catalog[skill].includes(phrase)); + expect(inCatalog.length, `${skill} one-liner "${catalog[skill]}" carries none of ${triggers.join(', ')}`).toBeGreaterThan(0); + for (const phrase of inCatalog) { + const others = skillDirs.filter(dir => dir !== skill && catalog[dir].includes(phrase)); + expect(others, `"${phrase}" also in`).toEqual([]); + } + }); +}); diff --git a/test/proactive-skill-start.test.ts b/test/proactive-skill-start.test.ts index 75ada2f4e..4b5384598 100644 --- a/test/proactive-skill-start.test.ts +++ b/test/proactive-skill-start.test.ts @@ -10,7 +10,8 @@ * not know, and the config default when gstack-config is missing beside the * script; and the off-only surfaces (the first-run and first-loop tips, the * CLAUDE.md routing offer) fire under core and not under off, by running the - * script; fails_when=gstack-skill-start stops consulting gstack-config, the + * script, and the offered routing section is the core four under core and every + * rule under all; fails_when=gstack-skill-start stops consulting gstack-config, the * default moves back to true, a legacy or junk value leaks into STATUS, the * missing-config fallback literal drifts from lookup_default, or a tip or the * routing offer fires under off; why_new=proactive-config.test.ts exercises @@ -28,6 +29,7 @@ import * as os from 'os'; import * as path from 'path'; import { runBin } from './helpers/run-bin'; +import { readShippedSkillRouting } from './helpers/shipped-skill-routing'; const ROOT = path.resolve(import.meta.dir, '..'); const BIN = path.join(ROOT, 'bin'); @@ -115,6 +117,36 @@ describe('gstack-skill-start PROACTIVE status line', () => { } }); + test('the offered section follows the setting: core offers the four core rules and the scoping line, all offers every rule (#3446)', () => { + const offered = (config: string): string => { + const state = freshState(config); + fs.writeFileSync(path.join(state, '.proactive-prompted'), ''); + try { + const out = runStart(path.join(BIN, 'gstack-skill-start'), state); + const block = out.slice(out.indexOf('GSTACK_INSTRUCTION_BEGIN: routing-injection ')); + return block.slice(block.indexOf('## Skill routing\n'), block.indexOf('\nIf B:')).trim(); + } finally { + fs.rmSync(state, { recursive: true, force: true }); + } + }; + const rulesOf = (section: string) => section.split('\n').filter(line => /^- .+→ invoke \//.test(line)); + const shipped = readShippedSkillRouting(); + const core = offered('update_check: false\n'); + expect(rulesOf(core)).toEqual([ + '- Product ideas/brainstorming → invoke /office-hours', + '- QA/testing site behavior → invoke /qa', + '- Code review/diff check → invoke /review', + '- Ship/deploy/PR → invoke /ship', + ]); + expect(core).toContain('Do not suggest or auto-invoke other gstack skills: they run when the user types them, or when a running skill hands off to them (`gstack-config set proactive all` routes every skill).'); + expect(core.split('\n').filter(line => line.includes('→'))).toEqual(rulesOf(core)); + expect(core.startsWith(shipped.instruction)).toBe(true); + for (const config of ['update_check: false\nproactive: all\n', 'update_check: false\nproactive: true\n']) { + expect(offered(config), config).toBe(shipped.section); + } + expect(rulesOf(shipped.section).length).toBeGreaterThan(4); + }, 60_000); + test('a stored off answers the one-time proactive prompt: no prompt block, marker written; core still asks once telemetry was answered', () => { const cases: Array<[string, boolean]> = [ ['update_check: false\ntelemetry: off\n', true], diff --git a/test/size-ledger/ship/gsta225-core-routing-triggers.json b/test/size-ledger/ship/gsta225-core-routing-triggers.json new file mode 100644 index 000000000..5842b1d13 --- /dev/null +++ b/test/size-ledger/ship/gsta225-core-routing-triggers.json @@ -0,0 +1,4 @@ +{ + "bytes": -21, + "why": "GSTA-225 (#3446/#2118): ship's catalog lead names its trigger phrases (ship it, create a PR) in fewer words" +} diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index e4feb7a26..f9d902e1c 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -1794,22 +1794,28 @@ describe('Skill trigger phrases', () => { }); } - // Skills with proactive triggers should have "Proactively suggest" somewhere in the skill. - const SKILLS_REQUIRING_PROACTIVE = [ - 'qa', 'qa-only', 'ship', 'review', 'investigate', 'office-hours', - 'plan-ceo-review', 'plan-eng-review', 'plan-design-review', - 'design-review', 'design-consultation', 'retro', 'document-release', - ]; + // Only the four core skills (the proactive: core default) may invite unasked + // suggestions; a non-core description that says "Proactively suggest" reaches + // every host that keeps full descriptions, under core too (#3446). + const SKILLS_REQUIRING_PROACTIVE = ['qa', 'ship', 'review', 'office-hours']; for (const skill of SKILLS_REQUIRING_PROACTIVE) { 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'); + const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md.tmpl'), 'utf-8'); // Same catalog-trim consideration — check the template description. expect(content).toMatch(/Proactively (suggest|invoke)/i); }); } + + test('no non-core skill description invites proactive suggestions (#3446)', () => { + const offenders = fs.readdirSync(ROOT) + .filter(dir => !SKILLS_REQUIRING_PROACTIVE.includes(dir) && fs.existsSync(path.join(ROOT, dir, 'SKILL.md.tmpl'))) + .filter(dir => { + const tmpl = fs.readFileSync(path.join(ROOT, dir, 'SKILL.md.tmpl'), 'utf-8'); + return /Proactively (suggest|invoke)/i.test(tmpl.slice(0, tmpl.indexOf('\n---', 4))); + }); + expect(offenders).toEqual([]); + }); }); // ─── Private-path leak detector ──────────────────────────────