@monte3l/groundwork 0.1.0-next.1 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +16 -8
  2. package/bin/m3l-groundwork.mjs +36 -2
  3. package/dist/assets.d.ts +10 -0
  4. package/dist/assets.js +14 -0
  5. package/dist/baseline-stage.d.ts +173 -0
  6. package/dist/baseline-stage.js +215 -0
  7. package/dist/caps.d.ts +4 -1
  8. package/dist/caps.js +17 -3
  9. package/dist/conflicts.d.ts +23 -2
  10. package/dist/conflicts.js +89 -11
  11. package/dist/customize-paths.d.ts +92 -0
  12. package/dist/customize-paths.js +115 -0
  13. package/dist/emit.d.ts +50 -1
  14. package/dist/emit.js +121 -13
  15. package/dist/fatal.d.ts +70 -0
  16. package/dist/fatal.js +132 -0
  17. package/dist/format-error.d.ts +113 -0
  18. package/dist/format-error.js +553 -0
  19. package/dist/fs-guard.d.ts +142 -0
  20. package/dist/fs-guard.js +222 -0
  21. package/dist/git.js +10 -1
  22. package/dist/harness/conformance.js +2 -0
  23. package/dist/harness/frontmatter.js +2 -13
  24. package/dist/harness/grade.js +37 -8
  25. package/dist/harness/rules.d.ts +20 -3
  26. package/dist/harness/rules.js +108 -6
  27. package/dist/harness/types.d.ts +13 -0
  28. package/dist/harness/types.js +3 -0
  29. package/dist/inventory.d.ts +77 -3
  30. package/dist/inventory.js +103 -12
  31. package/dist/jsonc.d.ts +49 -2
  32. package/dist/jsonc.js +140 -7
  33. package/dist/main.d.ts +62 -3
  34. package/dist/main.js +538 -67
  35. package/dist/merge-json.d.ts +47 -4
  36. package/dist/merge-json.js +120 -8
  37. package/dist/mode.js +12 -2
  38. package/dist/pack-stage.d.ts +210 -0
  39. package/dist/pack-stage.js +287 -0
  40. package/dist/packs.d.ts +19 -13
  41. package/dist/packs.js +231 -28
  42. package/dist/palette.d.ts +23 -0
  43. package/dist/palette.js +22 -0
  44. package/dist/plugin.d.ts +122 -6
  45. package/dist/plugin.js +687 -47
  46. package/dist/report.d.ts +21 -2
  47. package/dist/report.js +93 -5
  48. package/dist/staging.d.ts +176 -0
  49. package/dist/staging.js +375 -0
  50. package/dist/survey/fs-walk.d.ts +34 -2
  51. package/dist/survey/fs-walk.js +70 -5
  52. package/dist/survey/internal/blocked-path.d.ts +27 -0
  53. package/dist/survey/internal/blocked-path.js +129 -0
  54. package/dist/survey/internal/package-json.d.ts +13 -0
  55. package/dist/survey/internal/package-json.js +37 -0
  56. package/dist/survey/internal/read-guard.d.ts +178 -0
  57. package/dist/survey/internal/read-guard.js +276 -0
  58. package/dist/survey/survey-docs.d.ts +17 -2
  59. package/dist/survey/survey-docs.js +61 -21
  60. package/dist/survey/survey-harness.d.ts +20 -2
  61. package/dist/survey/survey-harness.js +87 -46
  62. package/dist/survey/survey-shape.d.ts +17 -2
  63. package/dist/survey/survey-shape.js +60 -46
  64. package/dist/survey/survey-toolchain.d.ts +16 -1
  65. package/dist/survey/survey-toolchain.js +69 -66
  66. package/dist/survey/survey.js +6 -4
  67. package/dist/survey/types.d.ts +79 -0
  68. package/dist/survey/types.js +2 -6
  69. package/dist/term.d.ts +80 -0
  70. package/dist/term.js +145 -0
  71. package/dist/tokens.js +2 -0
  72. package/dist/toolchain/conformance.js +2 -0
  73. package/dist/toolchain/grade.js +26 -8
  74. package/dist/toolchain/rules.d.ts +13 -4
  75. package/dist/toolchain/rules.js +16 -0
  76. package/dist/toolchain/tsconfig-chain.d.ts +2 -0
  77. package/dist/toolchain/tsconfig-chain.js +32 -8
  78. package/dist/toolchain/types.d.ts +16 -4
  79. package/dist/toolchain/types.js +3 -0
  80. package/package.json +4 -3
  81. package/plugin/skills/customize/SKILL.md +292 -18
  82. package/plugin/src/domain-map.ts +39 -14
  83. package/plugin/src/index.ts +5 -1
  84. package/plugin/src/kind-facet-map.ts +25 -8
  85. package/plugin/src/pack-map.ts +147 -25
  86. package/plugin/src/plugin-map.ts +236 -0
  87. package/templates/core/.claude/agents/Explore.md +0 -1
  88. package/templates/core/.claude/agents/code-implementer.md +4 -4
  89. package/templates/core/.claude/agents/code-reviewer.md +4 -4
  90. package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
  91. package/templates/core/.claude/agents/test-author.md +7 -5
  92. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
  93. package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
  94. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
  95. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
  96. package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
  97. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
  98. package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
  99. package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
  100. package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
  101. package/templates/core/.claude/rules/agent-dispatch.md +7 -0
  102. package/templates/core/.claude/rules/tests.md +2 -2
  103. package/templates/core/.claude/settings.json +5 -0
  104. package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
  105. package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
  106. package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
  107. package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
  108. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
  109. package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
  110. package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
  111. package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
  112. package/templates/core/.github/dependabot.yml +18 -0
  113. package/templates/core/.github/workflows/ci.yml +15 -15
  114. package/templates/core/.github/workflows/dependency-review.yml +2 -2
  115. package/templates/core/.github/workflows/security-audit.yml +10 -4
  116. package/templates/core/.prettierignore +4 -0
  117. package/templates/core/CLAUDE.md +53 -3
  118. package/templates/core/README.md +30 -10
  119. package/templates/core/_gitignore +17 -0
  120. package/templates/core/bin/check-exports.mjs +11 -5
  121. package/templates/core/bin/lib/agent-roster.mjs +1 -1
  122. package/templates/core/bin/lib/frontmatter.mjs +4 -2
  123. package/templates/core/bin/lib/harness-rules.mjs +169 -20
  124. package/templates/core/bin/lib/protected-paths.mjs +93 -5
  125. package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
  126. package/templates/core/bin/lib/verify-steps.mjs +29 -11
  127. package/templates/core/bin/verify.mjs +12 -0
  128. package/templates/core/eslint.config.js +5 -0
  129. package/templates/core/package.json +7 -7
  130. package/templates/core/vitest.config.ts +8 -1
  131. package/templates/packs/README.md +35 -24
  132. package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
  133. package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
  134. package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
  135. package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
  136. package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
  137. package/templates/packs/github/pack.json +19 -0
  138. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
  139. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
  140. package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
  141. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
  142. package/templates/packs/harness-extras/pack.json +18 -13
  143. package/templates/packs/publishing/files/.changeset/README.md +25 -0
  144. package/templates/packs/publishing/files/.changeset/config.json +7 -0
  145. package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
  146. package/templates/packs/publishing/files/.github/workflows/release.yml +293 -0
  147. package/templates/packs/publishing/files/REUSE.toml +28 -0
  148. package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
  149. package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
  150. package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
  151. package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
  152. package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
  153. package/templates/packs/publishing/pack.json +35 -0
  154. package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
  155. package/templates/packs/quality/pack.json +29 -0
  156. package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
  157. package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
  158. package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
  159. package/templates/packs/supply-chain/pack.json +19 -0
  160. package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
  161. package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
  162. package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
  163. package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
  164. package/templates/packs/worktrees/files/.worktreeinclude +11 -0
  165. package/templates/packs/worktrees/pack.json +64 -0
  166. package/templates/packs/statusline/pack.json +0 -31
  167. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
  168. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
  169. /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
  170. /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
@@ -39,7 +39,37 @@ gh pr view --json state,mergedAt,headRefName,baseRefName
39
39
  Record `headRefName` — every later step operates on this branch, not
40
40
  whatever the user typed.
41
41
 
42
- ### 2 — Return to `main` and pull
42
+ ### 2 — Check for a linked worktree
43
+
44
+ `headRefName` may be checked out in a linked worktree (created by
45
+ `claude --worktree`, the `EnterWorktree` tool, or `git worktree add`) rather
46
+ than in the main checkout directly — check before assuming otherwise:
47
+
48
+ ```bash
49
+ git worktree list --porcelain
50
+ ```
51
+
52
+ If a worktree has `headRefName` checked out:
53
+
54
+ - **If the session entered it via `EnterWorktree`/`--worktree` and is still
55
+ there**, use the `ExitWorktree` tool (keep, don't remove — removal happens
56
+ safely in Step 4, after the merge-ancestry check) to return to the main
57
+ checkout _first_. `git checkout main` below fails outright ("already used
58
+ by worktree") because `main` is already checked out in the main worktree —
59
+ git refuses to check out the same branch in two places at once, regardless
60
+ of which branch the other worktree happens to be on. (If the session
61
+ merely `cd`'d into a plain `git worktree add` checkout — no `EnterWorktree`
62
+ involved — `ExitWorktree` has nothing to exit; just `cd` back to the main
63
+ checkout instead.)
64
+ After `ExitWorktree`, run `git config core.bare`: Claude Code's worktree
65
+ tools have been reported to leave it `true` in a normal repository's shared
66
+ config (anthropics/claude-code#58345, #69802), which breaks `git status`
67
+ here. If it prints `true`, run `git config --local core.bare false` before
68
+ going on.
69
+ - Otherwise, no action needed yet — just remember the worktree's path for
70
+ Step 4.
71
+
72
+ ### 3 — Return to `main` and pull
43
73
 
44
74
  ```bash
45
75
  git checkout main
@@ -48,7 +78,7 @@ git pull
48
78
 
49
79
  Skip this if already on `main` with nothing to pull.
50
80
 
51
- ### 3 — Delete the merged branch
81
+ ### 4 — Delete the merged branch
52
82
 
53
83
  **Before removing anything, confirm no backgrounded command (a `git push`,
54
84
  a verify run, or similar) is still running against the branch you're about
@@ -63,6 +93,24 @@ merged" does not mean every commit on the branch landed. Run `git log
63
93
  <branch> ^origin/main --oneline` before any branch-deleting cleanup — a
64
94
  non-empty result is a commit about to be abandoned, not noise.
65
95
 
96
+ **If Step 2 found a linked worktree for this branch, remove it before
97
+ deleting the branch** — `git branch -d`/`-D` refuses a branch that's still
98
+ checked out anywhere. `status --porcelain` only reports tracked and
99
+ untracked-but-not-ignored changes — it says nothing about a gitignored file
100
+ that only exists in this worktree (a `.env`, an in-progress scratch file),
101
+ so **confirm with the user before running `remove`**, the same as `unlock`,
102
+ rather than treating an empty `status --porcelain` alone as proof there's
103
+ nothing to lose:
104
+
105
+ ```bash
106
+ git -C <worktree-path> status --porcelain # must be empty; investigate first if not
107
+ git worktree unlock <worktree-path> # only if locked, and only after confirming with the user
108
+ git worktree remove <worktree-path> # confirm with the user first -- see above
109
+ git worktree prune
110
+ ```
111
+
112
+ Then delete the branch itself:
113
+
66
114
  ```bash
67
115
  git branch -d <headRefName>
68
116
  ```
@@ -72,7 +120,7 @@ after a squash merge), don't force-delete without asking: confirm the merge
72
120
  really landed via `gh pr view` above, then use `git branch -D <headRefName>`
73
121
  only with the user's go-ahead.
74
122
 
75
- ### 4 — Prune stale remote-tracking refs
123
+ ### 5 — Prune stale remote-tracking refs
76
124
 
77
125
  ```bash
78
126
  git fetch --prune
@@ -82,7 +130,7 @@ Cheap and safe regardless of the branch outcome above — clears the
82
130
  `[deleted]` marker for this and any other already-merged branch's remote
83
131
  ref.
84
132
 
85
- ### 5 — Work log check
133
+ ### 6 — Work log check
86
134
 
87
135
  If the project keeps work logs (check whether a `docs/logs/` directory or
88
136
  equivalent convention exists), apply a substance test, not a commit-type
@@ -98,20 +146,20 @@ commit directly to `main`** — branch first (`git switch -c docs/<slug>-log`),
98
146
  commit there, push, and open a PR, even for a trivial docs-only change, if
99
147
  the project requires a PR for every change to `main`.
100
148
 
101
- ### 6 — Orphaned journal sweep
149
+ ### 7 — Orphaned journal sweep
102
150
 
103
151
  Check the scratchpad directory for any writer-spoke dispatch journal older
104
152
  than the current task that has no corresponding open work — ask before
105
153
  deleting, since a file from a different, still-in-progress task can look
106
154
  identical to a genuine orphan.
107
155
 
108
- ### 7 — Report
156
+ ### 8 — Report
109
157
 
110
- One-line summary: branch deleted (or kept, with why), refs pruned, work log
111
- present/written/skipped, journals swept/left.
158
+ One-line summary: worktree removed (if any), branch deleted (or kept, with
159
+ why), refs pruned, work log present/written/skipped, journals swept/left.
112
160
 
113
161
  ## Notes
114
162
 
115
163
  This skill is read-and-confirm heavy by design — every destructive step
116
- (branch delete, journal delete) asks first rather than assuming. A cautious,
117
- always-asks tail beats no tail at all.
164
+ (worktree unlock/removal, branch delete, journal delete) asks first rather
165
+ than assuming. A cautious, always-asks tail beats no tail at all.
@@ -17,8 +17,15 @@ description: >-
17
17
 
18
18
  One skill, two modes, sharing one allowlist
19
19
  (`references/official-sources.md`) so they can't drift apart. Pick the mode
20
- from how you were invoked: a specific question → `research`; a periodic or
21
- `/customize`-driven sweep → `refresh`.
20
+ by what's actually being asked, not by surface phrasing: a question that
21
+ names one setting, one hook, one agent, or one narrow facet of `.claude/` —
22
+ even when phrased as "is X still current" — stays `research`, because
23
+ there's one thing to look up and answer. A request that names no specific
24
+ facet, that asks about the whole `.claude/` surface ("our harness", "are we
25
+ behind"), or that's periodic/`/customize`-driven, is `refresh`. When
26
+ genuinely torn between the two, default to `research` — it's cheaper and
27
+ faster — and say in the answer that a full `refresh` sweep is available if
28
+ the question turns out to implicate more than the one facet asked about.
22
29
 
23
30
  **Must only run in the main (hub) agent, never inside a subagent** — it ends
24
31
  in `EnterPlanMode` (refresh) or dispatches other agents (either mode), which
@@ -104,8 +111,7 @@ concern) — every other facet below is identical regardless of project kind.
104
111
 
105
112
  Each brief carries: the facet row, Step 2's delta, the tracker's prior
106
113
  claims for that facet, the allowlist + GitHub caveat + date anchor, the
107
- exact filename to write (`<run-dir>/<facet-id>.md`), and this verdict
108
- format per claim:
114
+ facet id, and this verdict format per claim:
109
115
 
110
116
  ```
111
117
  CLAIM: <the tracker's prior claim, or "NEW" if none existed>
@@ -114,10 +120,13 @@ concern) — every other facet below is identical regardless of project kind.
114
120
  REPO-IMPACT: <which emitted file(s) this affects, or "none">
115
121
  ```
116
122
 
117
- Return value: **write the full file, return only a compact digest**
118
- (counts per verdict + every non-"none" REPO-IMPACT line + the file path).
123
+ Return value: **the full verdict list inline, inside the ~8,000-character
124
+ cap** -- `Explore` holds no write tool, so it cannot write a file. Put
125
+ every non-"none" REPO-IMPACT claim first so a truncation drops only
126
+ the no-impact ones. The hub saves each returned report to
127
+ `<run-dir>/<facet-id>.md` itself; that is what step 4 reads.
119
128
 
120
- 4. **Aggregate.** Read every scratchpad file in full. Four buckets:
129
+ 4. **Aggregate.** Read every saved facet report in full. Four buckets:
121
130
  confirmed drift with repo impact (verify each against the cited file
122
131
  itself before trusting it — an agent can misread a page), guidance
123
132
  changes with no impact, dead/moved URLs, coverage gaps.
@@ -76,6 +76,11 @@ surface the decisions still open.
76
76
 
77
77
  ### 5 — Act on the confirmed decisions
78
78
 
79
+ If `.claude/skills/working-in-worktrees/` exists (an optional add-on, not
80
+ part of this baseline), use its start mode for this step instead of a bare
81
+ `git switch -c` — it gets the same branch, plus an isolated worktree and its
82
+ own installed dependencies in one move. Otherwise:
83
+
79
84
  ```bash
80
85
  git switch -c feat/<slug> # or fix/<slug>
81
86
  ```
@@ -70,14 +70,15 @@ the first failure, typically the last 50–100 lines before the run aborted.
70
70
 
71
71
  ### 3 — Map to the pipeline step
72
72
 
73
- `.github/workflows/ci.yml`'s lanes each run `node bin/verify.mjs --step <id>`
74
- against a step id from `bin/lib/verify-steps.mjs` — that file's `cmd` field
75
- IS the local reproduction command, so mapping a failing CI step to its local
76
- command is always: find the `## <step name>` line the log shows, match it to
77
- the step's `name` in `bin/lib/verify-steps.mjs`, and reproduce with that
78
- step's `cmd` array joined as a shell command (or just `node bin/verify.mjs
79
- --step <id>` directly). The step name usually appears verbatim in the log
80
- lines (e.g. `Run pnpm lint` or `##[error]...`).
73
+ `.github/workflows/ci.yml`'s lanes each run `node bin/verify.mjs --group <name>`
74
+ (`format`, `lint`, `typecheck`, `build`, `test`); a group runs every step
75
+ in it from `bin/lib/verify-steps.mjs`, and that file's `cmd` field IS the
76
+ local reproduction command. Find the `▶ <step name> (<id>)` line the log
77
+ prints just before the failure, then reproduce with `node bin/verify.mjs
78
+ --step <id>` (or the step's `cmd` array joined as a shell command). If no
79
+ `▶` line is visible, match the failing job's name to its group and run
80
+ `node bin/verify.mjs --group <name>`. `--step` is for local debugging only
81
+ and never appears in a workflow file.
81
82
 
82
83
  ### 4 — Report the diagnosis
83
84
 
@@ -1,34 +1,53 @@
1
1
  ---
2
2
  name: typescript-guidance
3
3
  description: >-
4
- Dual-mode TypeScript guidance skill. `research` mode answers a single
4
+ Three-mode TypeScript guidance skill. `research` mode answers a single
5
5
  TypeScript/toolchain question from owner-normative upstream sources only
6
6
  (typescriptlang.org, the devblog, microsoft/TypeScript releases,
7
7
  nodejs.org type stripping, typescript-eslint.io, attw, publint). `refresh`
8
8
  mode sweeps this project's whole TypeScript-facing surface — tsconfig,
9
- eslint, packaging, and the test config/approach — against a living
10
- tracker and produces a remediation plan. Use for /typescript-guidance,
11
- "what does the TypeScript team say about X", "are we behind on
12
- TypeScript", "is our tsconfig still current", or before changing a
13
- compiler flag. Not how this project's config is wired today — that's
14
- CLAUDE.md and the config files themselves.
9
+ eslint, packaging, the test config/approach — against a living tracker and
10
+ produces a remediation plan. `gaps` mode profiles the toolchain offline,
11
+ then researches live before recommending tooling the project does NOT have
12
+ yet, each recommendation with a source fetched that run. Use for
13
+ /typescript-guidance, "what does the TypeScript team say about X", "are we
14
+ behind on TypeScript", "is our tsconfig still current", "what tooling are
15
+ we missing", or before changing a compiler flag. Not for a general Claude
16
+ Code automation review, and not how this project's config is wired today
17
+ — that's CLAUDE.md and the config files.
15
18
  ---
16
19
 
17
20
  # typescript-guidance
18
21
 
19
- One skill, two modes, sharing one allowlist
22
+ One skill, three modes, sharing one allowlist
20
23
  (`references/typescript-sources.md`) so they can't drift apart. Pick the
21
- mode from how you were invoked: a specific question → `research`; a
22
- periodic or `/customize`-driven sweep → `refresh`.
24
+ mode by what's actually being asked, not by surface phrasing. First, is the
25
+ thing not configured at all? "What are we missing", "any gaps in our
26
+ toolchain", "should we add typed linting" are `gaps` even when they name one
27
+ tool, because absence decides it. Otherwise a question that names one flag,
28
+ one setting, or one narrow facet — even when phrased as "is X still
29
+ current" — stays `research`, because there's one thing to look up and
30
+ answer. A request that names no specific facet, that asks
31
+ about the whole TypeScript-facing surface ("our tsconfig", "our toolchain",
32
+ "are we behind"), or that's periodic/`/customize`-driven, is `refresh`. A
33
+ request about tooling the project **doesn't have at all** is `gaps`,
34
+ whereas "X is configured, is it still right" is `research` or `refresh`,
35
+ never `gaps`. When genuinely torn between `research` and `refresh`, default
36
+ to `research` — it's cheaper and faster — and say in the answer that a full
37
+ `refresh` sweep is available if the question turns out to implicate more
38
+ than the one facet asked about.
23
39
 
24
40
  **Must only run in the main (hub) agent, never inside a subagent** — it ends
25
- in `EnterPlanMode` (refresh) or an `AskUserQuestion` (either mode,
26
- occasionally), neither of which a subagent can do.
41
+ in `EnterPlanMode` (refresh) or an `AskUserQuestion` (research or refresh,
42
+ occasionally), and every mode launches parallel `Explore` research agents,
43
+ none of which a subagent can do (`disallowedTools: Agent` on every spoke).
27
44
 
28
45
  **No files are written by this skill itself** in research mode by default;
29
- refresh mode writes to exactly one file, the tracker, in Step 5.
46
+ refresh mode writes to exactly one file, the tracker, in Step 5; `gaps` mode
47
+ never writes a file — it proposes, and hands any accepted recommendation to
48
+ `starting-work` and the normal TDD pipeline.
30
49
 
31
- ## Authority (read this before either mode)
50
+ ## Authority (read this before any mode)
32
51
 
33
52
  This skill has authority over **every TypeScript-facing file in the
34
53
  project** — `tsconfig.base.json`, `tsconfig.json`, `eslint.config.js`,
@@ -40,6 +59,17 @@ its config shape), `package.json`'s TypeScript-toolchain entries, packaging
40
59
  **most deeply**, never which facets it may or may not touch. A facet with
41
60
  low priority still gets swept; it just gets less dedicated attention per run.
42
61
 
62
+ `gaps` mode never overlaps that authority. It covers what's **missing** — a
63
+ flag, script, config block or package the project doesn't have configured at
64
+ all. Drift in something that already exists is `research`'s or `refresh`'s
65
+ question and is never re-answered under `gaps`; if a `gaps` profile turns out
66
+ to be "configured but looks stale", say so and switch mode instead of
67
+ writing a recommendation. Any compiler-flag or config change `gaps`
68
+ proposes goes through `research` before it is implemented: `gaps`' live
69
+ research is scoped to "does this exist, and what's the current recommended
70
+ shape if we add it", and doesn't carry `research`'s full precedence and
71
+ conflict-resolution machinery for an existing value.
72
+
43
73
  ## Research mode
44
74
 
45
75
  1. **Scope the topic.** Read the topic from the invocation or the
@@ -108,8 +138,7 @@ low priority still gets swept; it just gets less dedicated attention per run.
108
138
 
109
139
  Each brief carries: the facet row, Step 2's delta, the tracker's prior
110
140
  claims for that facet, the allowlist + GitHub caveat + date anchor, the
111
- exact filename to write (`<run-dir>/<facet-id>.md`), and this verdict
112
- format per claim:
141
+ facet id, and this verdict format per claim:
113
142
 
114
143
  ```
115
144
  CLAIM: <the tracker's prior claim, or "NEW" if none existed>
@@ -118,10 +147,13 @@ low priority still gets swept; it just gets less dedicated attention per run.
118
147
  REPO-IMPACT: <which emitted file(s) this affects, or "none">
119
148
  ```
120
149
 
121
- Return value: **write the full file, return only a compact digest**
122
- (counts per verdict + every non-"none" REPO-IMPACT line + the file path).
150
+ Return value: **the full verdict list inline, inside the ~8,000-character
151
+ cap** -- `Explore` holds no write tool, so it cannot write a file. Put
152
+ every non-"none" REPO-IMPACT claim first so a truncation drops only
153
+ the no-impact ones. The hub saves each returned report to
154
+ `<run-dir>/<facet-id>.md` itself; that is what step 4 reads.
123
155
 
124
- 4. **Aggregate.** Read every scratchpad file in full. Four buckets:
156
+ 4. **Aggregate.** Read every saved facet report in full. Four buckets:
125
157
  confirmed drift with repo impact (verify each against the cited file
126
158
  itself before trusting it — an agent can misread a page), guidance
127
159
  changes with no impact, dead/moved URLs, coverage gaps.
@@ -133,6 +165,89 @@ low priority still gets swept; it just gets less dedicated attention per run.
133
165
  confirmed-drift item. No drift → skip plan mode, report a clean sweep,
134
166
  still update the tracker.
135
167
 
168
+ ## Gaps mode
169
+
170
+ Recommends TypeScript-ecosystem **tooling the project doesn't have yet** and
171
+ never rules on tooling it already has. Every recommendation is grounded on
172
+ official guidance fetched **live**, in this run: cross-project compatibility
173
+ state (a new TypeScript major before `typescript-eslint` supports it, a
174
+ package-manager major dropping a setting) shifts on its own schedule, so
175
+ anything baked into this file as a fixed answer would go stale within months.
176
+ This mode ships no answers, only where to look and what to ask.
177
+
178
+ 1. **Profile (offline; no network calls).** Establish what the project
179
+ already has before researching anything:
180
+ - `package.json`'s `scripts`, `dependencies`, `devDependencies`, and
181
+ `pnpm-workspace.yaml` (workspaces, `catalog`/`catalogs`) if present;
182
+ - the resolved tsconfig chain — every `tsconfig*.json` following
183
+ `extends`, using `bin/lib/toolchain-rules.mjs`'s tsconfig-chain
184
+ resolution if it exists rather than re-deriving the rules by hand;
185
+ - lint/test/hygiene config — `eslint.config.*` (or `.eslintrc.*`),
186
+ `vitest.config.ts` (or another runner's), `knip.json`;
187
+ - existing gates — run `node bin/check-toolchain.mjs` if present, and
188
+ read `.groundwork/inventory.json` if present (its `toolchainGrade` and
189
+ `toolchainConformance` are exactly this profile, already computed);
190
+ - budget — count `.claude/agents/*.md`, `.claude/skills/*/`,
191
+ `.claude/hooks/*.{mjs,js}`, `.github/workflows/*.yml` and
192
+ `package.json`'s `scripts` keys if the project states a cap on any of
193
+ them (`CLAUDE.md`, or an adoption report's cap table): with no room
194
+ left, prefer a `verify-steps` gate over a new `package.json` script;
195
+ - verify-step wiring — `bin/lib/verify-steps.mjs` /
196
+ `bin/lib/verify-steps.packs.json`, or whatever gate runner exists.
197
+
198
+ Map every signal to the areas in `references/area-catalog.md`, which names
199
+ the signals to look for and the questions to research per area and holds
200
+ no answers of its own. An area with no signal at all (no monorepo hints,
201
+ no release workflow) still gets a light pass: "you have none of this,
202
+ here is what to consider" is itself a recommendation, not silence.
203
+
204
+ 2. **Research live (mandatory).** No recommendation reaches the report
205
+ without a source fetched in this run. Read `references/tooling-sources.md`
206
+ (the ecosystem-tooling allowlist) and `references/typescript-sources.md`
207
+ (the TypeScript-owner allowlist, reused rather than duplicated). Pick the
208
+ areas from `area-catalog.md` that the profile made relevant — or only the
209
+ one category the user named. **Launch all agents in a single message**,
210
+ always `subagent_type: "Explore"`, breadth `"very thorough"`, one per
211
+ relevant area. Each brief carries: that area's signals-and-questions
212
+ verbatim; both allowlists pasted verbatim, each with its own GitHub-path
213
+ caveat kept separate (a domain allowed by one file's caveat is not
214
+ automatically allowed by the other's); today's date; "reject any
215
+ non-allowlisted domain outright and say so, rather than substituting a
216
+ blog post or an individual author's material"; "you hold no write tool —
217
+ findings travel only in your response"; research mode's findings format
218
+ above; and an ~8,000-character (~2,000-token) return cap. Read every
219
+ agent's full inline findings. A failed or empty fetch for an area is a
220
+ **coverage gap**, reported as such — never dropped, never backfilled from
221
+ an unlisted source.
222
+ 3. **Report.** In this order:
223
+ - **Codebase profile** — a short summary of what step 1 found, so the
224
+ reader can see the recommendations are grounded in this project.
225
+ - **Recommendations** — 1–2 per relevant area (3–5 for a single area the
226
+ user named). Each gives: **Why** (the specific project signal, not
227
+ "generally good practice"), **What** (the concrete config block, script
228
+ or package to add), **Source** (the URL, fetched today, with its tier),
229
+ and **Cost** (cap impact, and whether it's a `verify-steps` gate or a
230
+ `package.json` script).
231
+ - **Unverified claims** — a signal step 2 could not verify against an
232
+ allowlisted source is named as "unverified — not recommended", not
233
+ omitted.
234
+ - **No web tools at all** (offline, sandboxed): report the profile alone,
235
+ say plainly that no live research ran, and recommend nothing — an
236
+ unverified recommendation is worse than none.
237
+ - **Hand-off** — point any accepted recommendation at `starting-work`
238
+ (branch first) and the normal test-author → code-implementer →
239
+ review-spoke pipeline. This mode never writes the change itself.
240
+
241
+ **Recommendation discipline.** Never recommend adding a tool or preset that
242
+ live research shows doesn't yet support the project's current toolchain
243
+ version: recommending something that can't be installed against the
244
+ already-pinned TypeScript is worse than recommending nothing, so name the
245
+ incompatibility as a blocker instead. (Second-guessing a version the project
246
+ already pinned is `research`'s territory, not this mode's.) When the
247
+ project's scripts are at their cap, prefer a `verify-steps` gate
248
+ (`["node", "bin/check-x.mjs"]`, no `package.json` script needed) over a new
249
+ script.
250
+
136
251
  ## Why this exists, separately from "how is our config wired"
137
252
 
138
253
  Research mode answers "what does upstream say about X." Nothing else in the
@@ -140,4 +255,6 @@ baseline asks the inverse — "is what's already configured still what
140
255
  upstream recommends" — because every lint/typecheck/test gate is a closed
141
256
  loop checking the repo against its own prior decisions. A stale pin one
142
257
  major behind upstream passes every one of those gates cleanly; only a live
143
- sweep against the actual upstream surfaces it.
258
+ sweep against the actual upstream surfaces it. For the same reason, a
259
+ missing tool trips no gate at all — nothing fails when a check that was
260
+ never installed doesn't run — which is what `gaps` mode is for.
@@ -0,0 +1,135 @@
1
+ # Area catalog -- signals to detect, questions to research
2
+
3
+ For each area: what to look for in `gaps` mode's step 1 (the signal), and what to ask a
4
+ step 2 research agent (the question). **This file holds no answers** --
5
+ an answer here would be exactly the kind of baked-in claim that goes stale
6
+ within months. Pick the areas step 1's signals make relevant; don't
7
+ research every area unconditionally on every run.
8
+
9
+ ## tsconfig flags and deprecations
10
+
11
+ - **Signal**: the resolved tsconfig chain (step 1) is missing a
12
+ strict-family flag entirely, or sets no `target`/`lib` at all for the
13
+ project's actual runtime.
14
+ - **Research**: what compiler options does the current TypeScript release
15
+ recommend adding for this project's runtime target (Node-only, browser,
16
+ both), for a tsconfig that doesn't set them yet? (Whether an option the
17
+ project already sets has since been deprecated, or an already-set
18
+ `target`/`lib` is now outdated, is drift in existing config --
19
+ `research`/`refresh`'s question, not `gaps` mode's; hand it off rather
20
+ than research it here.)
21
+
22
+ ## module resolution by project kind
23
+
24
+ - **Signal**: no `moduleResolution` set anywhere in the resolved tsconfig
25
+ chain at all.
26
+ - **Research**: what does the current TypeScript Modules reference recommend
27
+ for this project's actual shipping shape (published package vs. bundled
28
+ app vs. both), for a tsconfig that doesn't set one yet? (An already-set
29
+ `moduleResolution` that doesn't match what the project actually ships --
30
+ a package with an `exports` map on `node16`/`nodenext` that should be on
31
+ `bundler`, say -- is drift in existing config, `research`/`refresh`'s
32
+ question, not `gaps` mode's.)
33
+
34
+ ## typed linting
35
+
36
+ - **Signal**: `eslint.config.js` has no `typescript-eslint` typed-linting
37
+ preset (`recommendedTypeChecked`/`strictTypeChecked`/`stylisticTypeChecked`)
38
+ configured at all.
39
+ - **Research**: what does typescript-eslint's current documentation recommend
40
+ for a project with no typed linting yet -- and, since a recommendation to
41
+ add it must actually be installable, does the version that would be added
42
+ support the project's current TypeScript version? This is exactly the kind
43
+ of cross-project compatibility gap a stale skill answer would miss (a new
44
+ TypeScript major that `typescript-eslint` doesn't support yet, say).
45
+ (Whether an _already-pinned_
46
+ `typescript-eslint` version is stale or incompatible is drift in existing
47
+ config -- `research`/`refresh`'s question, not `gaps` mode's.)
48
+
49
+ ## testing
50
+
51
+ - **Signal**: no test runner configured at all.
52
+ - **Research**: what does the current official docs of a reasonable default
53
+ test runner recommend for a TypeScript project with none configured yet --
54
+ config shape, coverage tooling, and any TypeScript-specific caveat
55
+ (type-checking test files, `expectTypeOf`-style assertions)? (A runner that
56
+ already exists but has no coverage gate, or whose config no longer matches
57
+ its own current docs, is drift in existing config -- `research`/`refresh`'s
58
+ question via its `testing-language-features` facet, not `gaps` mode's.)
59
+
60
+ ## packaging and export validation
61
+
62
+ - **Signal**: a published package (has a `bin` or `exports` field, isn't
63
+ `private: true`) with no `publint`/`arethetypeswrong` check wired into its
64
+ verify gate.
65
+ - **Research**: what do `publint.dev` and `arethetypeswrong.github.io`
66
+ currently flag as common packaging mistakes, and is either tool's current
67
+ recommended invocation different from a bare CLI call (e.g. a config
68
+ flag this project should pass)?
69
+
70
+ ## dependency hygiene
71
+
72
+ - **Signal**: no `knip` (or equivalent unused-export/unused-dependency
73
+ tool) configured at all.
74
+ - **Research**: what does `knip.dev`'s current documentation recommend for
75
+ a project shaped like this one (workspaces, multiple entry points, a CLI
76
+ `bin`) that has no such tool yet? (An existing `knip.json` that looks stale
77
+ against the project's actual entry points is drift in existing config, not
78
+ absence -- `knip.json` is a `research`/`refresh` domain file; switch mode
79
+ rather than researching a fix here.)
80
+
81
+ ## pnpm supply-chain settings
82
+
83
+ - **Signal**: `pnpm-workspace.yaml`/`.npmrc` has no supply-chain-hardening
84
+ setting configured at all -- no lifecycle-script allowlist, no
85
+ `packageManager` corepack pin in `package.json`, no audit setting.
86
+ - **Research**: what supply-chain-hardening settings does the project's
87
+ actual pnpm major currently document (lifecycle-script allowlisting,
88
+ `minimumReleaseAge`, audit settings), for a project that has none
89
+ configured yet? (Whether an existing setting has since been renamed or
90
+ removed by a newer pnpm major -- a lifecycle-script allowlist key moving
91
+ to a different file, for example -- is drift in something already
92
+ configured, not absence; hand it to `research`/`refresh` rather than
93
+ research it here, since `pnpm-workspace.yaml` is one of its own domain
94
+ files.)
95
+
96
+ ## scripts and verify gates
97
+
98
+ - **Signal**: a check this project runs by hand (or not at all) that a
99
+ comparable project would gate in CI -- a missing `check:exports`-equivalent
100
+ on a published package, no `pnpm verify`-shaped aggregator at all.
101
+ - **Research**: n/a in the live-fetch sense (this is a project-structure
102
+ question, not an upstream-guidance one) -- but check whether the project's
103
+ gate runner shape (a package script vs. a dedicated
104
+ `bin/verify.mjs`-style aggregator) matches current tooling convention for
105
+ its ecosystem before recommending which shape to add.
106
+
107
+ ## monorepo and catalogs
108
+
109
+ - **Signal**: multiple `package.json` files under one root with no
110
+ `pnpm-workspace.yaml`, or a workspace with repeated identical dependency
111
+ version pins across packages that pnpm's `catalog`/`catalogs` feature
112
+ exists to deduplicate.
113
+ - **Research**: what does pnpm's current workspace/catalog documentation
114
+ recommend for this shape, and does the pinned pnpm major actually support
115
+ the catalog syntax being recommended?
116
+
117
+ ## Node version pinning
118
+
119
+ - **Signal**: no Node version pin anywhere at all -- no `.node-version`, no
120
+ `.nvmrc`, no `engines.node`.
121
+ - **Research**: what does `nodejs.org`'s current release schedule recommend
122
+ pinning to for a new project? (A disagreement between multiple existing
123
+ pins, or a pin that's already past its documented end-of-life, is drift in
124
+ something already configured -- `research`/`refresh`'s question, not
125
+ `gaps` mode's.)
126
+
127
+ ## release automation
128
+
129
+ - **Signal**: a package that's published (not `private: true`) with no
130
+ automated release pipeline (no changesets, no equivalent version-bump
131
+ automation).
132
+ - **Research**: what does `changesets/changesets`'s current documentation
133
+ recommend for this project's publishing shape (single package vs.
134
+ workspace)? If the project already has a release workflow, that is drift
135
+ in existing config, not a gap -- hand it to `research`/`refresh`.