@monte3l/groundwork 0.0.0

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 (151) hide show
  1. package/README.md +23 -0
  2. package/bin/m3l-groundwork.mjs +10 -0
  3. package/dist/assets.d.ts +20 -0
  4. package/dist/assets.js +79 -0
  5. package/dist/caps.d.ts +25 -0
  6. package/dist/caps.js +69 -0
  7. package/dist/conflicts.d.ts +12 -0
  8. package/dist/conflicts.js +77 -0
  9. package/dist/emit.d.ts +7 -0
  10. package/dist/emit.js +42 -0
  11. package/dist/git.d.ts +3 -0
  12. package/dist/git.js +9 -0
  13. package/dist/harness/conformance.d.ts +20 -0
  14. package/dist/harness/conformance.js +18 -0
  15. package/dist/harness/frontmatter.d.ts +38 -0
  16. package/dist/harness/frontmatter.js +204 -0
  17. package/dist/harness/grade.d.ts +4 -0
  18. package/dist/harness/grade.js +105 -0
  19. package/dist/harness/rules.d.ts +55 -0
  20. package/dist/harness/rules.js +580 -0
  21. package/dist/harness/types.d.ts +32 -0
  22. package/dist/harness/types.js +9 -0
  23. package/dist/inventory.d.ts +63 -0
  24. package/dist/inventory.js +66 -0
  25. package/dist/jsonc.d.ts +14 -0
  26. package/dist/jsonc.js +83 -0
  27. package/dist/main.d.ts +24 -0
  28. package/dist/main.js +297 -0
  29. package/dist/merge-json.d.ts +74 -0
  30. package/dist/merge-json.js +135 -0
  31. package/dist/mode.d.ts +19 -0
  32. package/dist/mode.js +53 -0
  33. package/dist/packs.d.ts +61 -0
  34. package/dist/packs.js +186 -0
  35. package/dist/plugin.d.ts +23 -0
  36. package/dist/plugin.js +79 -0
  37. package/dist/report.d.ts +4 -0
  38. package/dist/report.js +323 -0
  39. package/dist/survey/fs-walk.d.ts +14 -0
  40. package/dist/survey/fs-walk.js +60 -0
  41. package/dist/survey/survey-docs.d.ts +4 -0
  42. package/dist/survey/survey-docs.js +69 -0
  43. package/dist/survey/survey-harness.d.ts +4 -0
  44. package/dist/survey/survey-harness.js +121 -0
  45. package/dist/survey/survey-shape.d.ts +4 -0
  46. package/dist/survey/survey-shape.js +182 -0
  47. package/dist/survey/survey-toolchain.d.ts +4 -0
  48. package/dist/survey/survey-toolchain.js +217 -0
  49. package/dist/survey/survey.d.ts +5 -0
  50. package/dist/survey/survey.js +21 -0
  51. package/dist/survey/types.d.ts +117 -0
  52. package/dist/survey/types.js +8 -0
  53. package/dist/tokens.d.ts +13 -0
  54. package/dist/tokens.js +13 -0
  55. package/dist/toolchain/conformance.d.ts +20 -0
  56. package/dist/toolchain/conformance.js +30 -0
  57. package/dist/toolchain/grade.d.ts +4 -0
  58. package/dist/toolchain/grade.js +244 -0
  59. package/dist/toolchain/rules.d.ts +118 -0
  60. package/dist/toolchain/rules.js +706 -0
  61. package/dist/toolchain/tsconfig-chain.d.ts +36 -0
  62. package/dist/toolchain/tsconfig-chain.js +116 -0
  63. package/dist/toolchain/types.d.ts +27 -0
  64. package/dist/toolchain/types.js +9 -0
  65. package/package.json +59 -0
  66. package/plugin/skills/customize/SKILL.md +305 -0
  67. package/plugin/src/domain-map.ts +134 -0
  68. package/plugin/src/index.ts +4 -0
  69. package/plugin/src/kind-facet-map.ts +174 -0
  70. package/plugin/src/pack-map.ts +65 -0
  71. package/templates/core/.claude/agents/Explore.md +43 -0
  72. package/templates/core/.claude/agents/code-implementer.md +258 -0
  73. package/templates/core/.claude/agents/code-reviewer.md +163 -0
  74. package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
  75. package/templates/core/.claude/agents/test-author.md +211 -0
  76. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
  77. package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
  78. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
  79. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
  80. package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
  81. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
  82. package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
  83. package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
  84. package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
  85. package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
  86. package/templates/core/.claude/rules/agent-dispatch.md +121 -0
  87. package/templates/core/.claude/rules/refactoring.md +52 -0
  88. package/templates/core/.claude/rules/src.md +114 -0
  89. package/templates/core/.claude/rules/tests.md +129 -0
  90. package/templates/core/.claude/settings.json +111 -0
  91. package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
  92. package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
  93. package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
  94. package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
  95. package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
  96. package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
  97. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
  98. package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
  99. package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
  100. package/templates/core/.github/workflows/ci.yml +123 -0
  101. package/templates/core/.github/workflows/dependency-review.yml +26 -0
  102. package/templates/core/.github/workflows/security-audit.yml +54 -0
  103. package/templates/core/.node-version +1 -0
  104. package/templates/core/.prettierignore +5 -0
  105. package/templates/core/.prettierrc.json +4 -0
  106. package/templates/core/CLAUDE.md +127 -0
  107. package/templates/core/README.md +24 -0
  108. package/templates/core/_gitignore +19 -0
  109. package/templates/core/_npmrc +1 -0
  110. package/templates/core/bin/check-exports.mjs +92 -0
  111. package/templates/core/bin/check-harness.mjs +27 -0
  112. package/templates/core/bin/check-node-version.mjs +51 -0
  113. package/templates/core/bin/check-toolchain.mjs +20 -0
  114. package/templates/core/bin/lib/agent-roster.mjs +8 -0
  115. package/templates/core/bin/lib/frontmatter.mjs +210 -0
  116. package/templates/core/bin/lib/harness-rules.mjs +916 -0
  117. package/templates/core/bin/lib/protected-paths.mjs +23 -0
  118. package/templates/core/bin/lib/report.mjs +56 -0
  119. package/templates/core/bin/lib/signed-range.mjs +178 -0
  120. package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
  121. package/templates/core/bin/lib/verify-steps.mjs +131 -0
  122. package/templates/core/bin/lib/verify-steps.packs.json +1 -0
  123. package/templates/core/bin/lint-commit.mjs +50 -0
  124. package/templates/core/bin/strip-claude-trailers.mjs +25 -0
  125. package/templates/core/bin/verify.mjs +64 -0
  126. package/templates/core/commitlint.config.js +11 -0
  127. package/templates/core/docs/research/harness-refresh.md +27 -0
  128. package/templates/core/docs/research/typescript-refresh.md +32 -0
  129. package/templates/core/eslint.config.js +105 -0
  130. package/templates/core/knip.json +6 -0
  131. package/templates/core/lefthook.yml +39 -0
  132. package/templates/core/package.json +58 -0
  133. package/templates/core/pnpm-workspace.yaml +13 -0
  134. package/templates/core/src/index.ts +12 -0
  135. package/templates/core/tests/index.test.ts +8 -0
  136. package/templates/core/tsconfig.base.json +36 -0
  137. package/templates/core/tsconfig.build.json +10 -0
  138. package/templates/core/tsconfig.json +11 -0
  139. package/templates/core/vitest.config.ts +32 -0
  140. package/templates/packs/README.md +81 -0
  141. package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
  142. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
  143. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
  144. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
  145. package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
  146. package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
  147. package/templates/packs/harness-extras/pack.json +65 -0
  148. package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
  149. package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
  150. package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
  151. package/templates/packs/statusline/pack.json +31 -0
@@ -0,0 +1,129 @@
1
+ ---
2
+ paths:
3
+ - "**/tests/**"
4
+ - "**/*.test.ts"
5
+ ---
6
+
7
+ # Testing rules (`tests/**`, `*.test.ts`)
8
+
9
+ > This file is the terse checklist that auto-loads when you edit a test.
10
+
11
+ - **Assert the named behavior, not a proxy** — not `length > 0`, and not
12
+ merely that the call "doesn't throw". A positive and a negative claim
13
+ BOTH met by nothing happening need the positive one asserted explicitly
14
+ too.
15
+ - **A test that claims to guard something must be mutation-tested before you
16
+ believe it guards anything** — delete the guard clause, invert the flag,
17
+ drop a wrapper, and confirm the test fails.
18
+ - **A surviving mutant is a question, not automatically a defect — and a
19
+ mutation that never applied is not a survivor at all.** An _equivalent_
20
+ mutant (both branches agree on every reachable input under a
21
+ runtime-enforced invariant) needs a note, not a new test; verify a
22
+ scripted mutation actually changed the file before trusting a "survivor".
23
+ - **A check whose two sides come from ONE source can never fail** — a fake
24
+ store that ECHOES the value under test passes either way. Pin at least
25
+ one side by hand.
26
+ - **A mutation-tested guard can go vacuous LATER** — it proves teeth only at
27
+ the moment you run it. When a change adds a consumer of a signal a test
28
+ observes INDIRECTLY (a property read, a call count), re-mutate the tests
29
+ watching it.
30
+ - **Never make a test double wait by counting event-loop turns** — a
31
+ `setImmediate` retry-N-times loop is a latency guess passing locally and
32
+ failing under CI load. Anchor the emit to a structural guarantee instead.
33
+ - **Rebuild before trusting a cross-package result.** If tests resolve a
34
+ sibling package through its build output rather than its source, run the
35
+ build first — a stale `dist/` fails tests in an untouched package after an
36
+ export changes, and a `src/` edit never reaches a consumer's suite until
37
+ rebuilt.
38
+ - **A test naming a precedence, ordering, or "every X" guarantee must make
39
+ every arm reachable in its own setup** — exactly right yet prove nothing
40
+ if the discriminating precondition never fires. Enumerate the set
41
+ (`test.each`), not one member of it.
42
+ - **Never mock the behavior the test exists to validate** — a stub echoing
43
+ back the outcome under question asserts the stub, not the code, while
44
+ still reading as coverage. Exercise the real collaborator at least once.
45
+ - **No network; real filesystem only inside a per-test `mkdtemp` sandbox**,
46
+ torn down in the same test. An integration-test directory that genuinely
47
+ needs the real network is the one deliberate exception — mark it clearly
48
+ and keep it out of the default unit run.
49
+ - **A type-only `expectTypeOf` test still executes its expression at
50
+ runtime** — if it invokes a fallible async method, resolve the mock to a
51
+ valid value first, or a rejecting un-awaited promise surfaces despite the
52
+ type assertion passing.
53
+ - **A gate failing outside your change's blast radius is presumed
54
+ pre-existing until disambiguated** — `git diff origin/main -- <path>`
55
+ settles it in seconds. Not licence to retry blind: an unexplained green
56
+ re-run is itself a flake to diagnose and file.
57
+ - **Mock an SDK package the same way once it mixes class and data
58
+ exports** — a plain `vi.mock("pkg", () => ({...}))` object literal
59
+ silently omits unlisted exports, harmless for a type-only import but
60
+ fatal once a value import (a data-only enum) resolves to `undefined` at
61
+ module-load time. Default to an `importOriginal`-preserving async factory.
62
+ - **A dynamic-`import()`-only step module can mock with a plain `const
63
+ stepMock = vi.fn()`; once production code adds a _static_ import from that
64
+ module, move the mock to `vi.hoisted(() => vi.fn())`** — a plain `const`
65
+ initializes after `vi.mock` calls are hoisted.
66
+ - **Mock a port with generic methods by inference, not `extends`** — a
67
+ generic method (`select<Value>(...)`) can't be mocked via `interface Mock
68
+ extends Port { ... }` (TS2430). Let the factory return the inferred
69
+ `vi.fn()` object instead.
70
+ - **Test-first, not test-after** — write tests from the documented contract,
71
+ watch them fail for the right reason, then implement — don't backfill a
72
+ test that just mirrors code you already wrote.
73
+ - **Justify intentional `eslint-disable` on the error channel** — a test
74
+ proving normalization throws non-`Error` values on purpose, tripping
75
+ `only-throw-error`. Disable narrowly with a `--` rationale:
76
+
77
+ ```ts
78
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- intentional non-Error to verify the unknown channel
79
+ throw "a string";
80
+ ```
81
+
82
+ - **Assemble a secret-shaped fixture at runtime, never as a single source
83
+ literal**, if this repo runs a secret scanner over source text —
84
+ concatenate two substrings instead of writing the shape whole.
85
+
86
+ ### Test-tooling gotchas
87
+
88
+ - **`not.toHaveProperty` cannot prove own-key absence** — chai falls back to
89
+ `"key" in Object(obj)` and walks the prototype chain. Assert
90
+ `Object.hasOwn(result, "f")` instead, and restore a polluted prototype in
91
+ an **unconditional** `afterEach` (`Reflect.deleteProperty`,
92
+ `configurable: true`).
93
+ - **Runtime-green ≠ typecheck-green** — Vitest transforms without
94
+ type-checking, so run `pnpm typecheck` as its own gate on every test file
95
+ you touch.
96
+ - **`pnpm build` is a distinct gate from `pnpm typecheck`, not a slower
97
+ version of it** — `isolatedDeclarations` (the `tsconfig.build.json`
98
+ project only) makes an additive `as const satisfies` pass `typecheck` and
99
+ fail `build` with TS9010. Any exported-type change needs both.
100
+ - **A test that deliberately avoids importing from `src` can strand an
101
+ export and fail `pnpm knip`** — keep both a hand-authored table and an
102
+ import for projection identity. `knip` is not gated in `pre-push` by
103
+ default — run it yourself after touching any export.
104
+ - **eslint runs in-loop** (prettier → eslint → typecheck → vitest) —
105
+ resolve findings as you write, don't defer to a later `pnpm lint` pass.
106
+ - **Thread `now` as an injectable parameter on a time-dependent guard**
107
+ rather than defaulting to `Date.now()` inside it — a sibling function's
108
+ fixed-timestamp fixtures are the tell.
109
+ - **Read coverage from `coverage/coverage-final.json`, not the
110
+ `pnpm test:coverage` text table.** The v8 text reporter omits files that
111
+ are 100% on every metric, so an absent file in the table is not an
112
+ uncovered file.
113
+ - **A fix round adding branches isn't done until the _gated_ run passes** —
114
+ per-file thresholds run only under `test:coverage`, never a scoped
115
+ `vitest` call. Trace the gap from `coverage-final.json`'s uncovered-line
116
+ list and cover any new ternary's non-`Error` arm in the same edit.
117
+ - **A suite failing while a spoke fan-out is running may be contention, not
118
+ a regression — re-run it alone first.**
119
+ - Use `pnpm exec vitest`; a bare `npx vitest` can fail to resolve
120
+ `@vitest/coverage-v8` under pnpm.
121
+ - **Brace void-union handler bodies** — a handler typed `void |
122
+ Promise<void>` whose arrow body returns a value fails typecheck (TS2322);
123
+ the leniency applies only to a return type of _exactly_ `void`. Wrap the
124
+ body: `() => { arr.push(v); }`.
125
+ - **Never explicitly parameterize `vi.spyOn<T, S>`'s return type** — an
126
+ explicit type argument resolves against the first overload regardless of
127
+ which one the call matches, failing a method spy with a `never`-constraint
128
+ error though the runtime call is correct. Let TypeScript infer it from the
129
+ `return` statement instead.
@@ -0,0 +1,111 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "hooks": {
4
+ "UserPromptSubmit": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/inject-decision-gate.mjs\"",
10
+ "timeout": 30
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PreToolUse": [
16
+ {
17
+ "matcher": "Bash",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-git-push-signed.mjs\"",
22
+ "timeout": 30
23
+ },
24
+ {
25
+ "type": "command",
26
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-double-background.mjs\"",
27
+ "timeout": 30
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "matcher": "Write|Edit",
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-js-extension.mjs\"",
37
+ "timeout": 30
38
+ },
39
+ {
40
+ "type": "command",
41
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-no-commonjs.mjs\"",
42
+ "timeout": 30
43
+ },
44
+ {
45
+ "type": "command",
46
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-protected-paths.mjs\"",
47
+ "timeout": 30
48
+ },
49
+ {
50
+ "type": "command",
51
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-branch-isolation.mjs\"",
52
+ "timeout": 30
53
+ },
54
+ {
55
+ "type": "command",
56
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-hub-src-writes.mjs\"",
57
+ "timeout": 30
58
+ },
59
+ {
60
+ "type": "command",
61
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-secret-writes.mjs\"",
62
+ "timeout": 30
63
+ }
64
+ ]
65
+ }
66
+ ],
67
+ "PostToolUse": [
68
+ {
69
+ "matcher": "Write|Edit",
70
+ "hooks": [
71
+ {
72
+ "type": "command",
73
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
74
+ "if": "Write(*.ts)",
75
+ "timeout": 180
76
+ },
77
+ {
78
+ "type": "command",
79
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
80
+ "if": "Edit(*.ts)",
81
+ "timeout": 180
82
+ },
83
+ {
84
+ "type": "command",
85
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
86
+ "if": "Write(*.mts)",
87
+ "timeout": 180
88
+ },
89
+ {
90
+ "type": "command",
91
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
92
+ "if": "Edit(*.mts)",
93
+ "timeout": 180
94
+ },
95
+ {
96
+ "type": "command",
97
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
98
+ "if": "Write(*.cts)",
99
+ "timeout": 180
100
+ },
101
+ {
102
+ "type": "command",
103
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit-verify.mjs\"",
104
+ "if": "Edit(*.cts)",
105
+ "timeout": 180
106
+ }
107
+ ]
108
+ }
109
+ ]
110
+ }
111
+ }
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: creating-prs
3
+ description: >-
4
+ Verify quality gates, push the branch, open a PR with a Conventional Commit
5
+ title and body from commit history, then decide and execute its merge
6
+ path. Use for /creating-prs, "open a PR", "create a pull request", "ship
7
+ this for review", "get this merged", or after finishing a fix. Requires gh
8
+ CLI auth.
9
+ ---
10
+
11
+ # creating-prs
12
+
13
+ Takes a branch with committed work from "ready" to "opened, gated, and
14
+ merged (or left for human review)". Assumes `starting-work` already put you
15
+ on the right branch.
16
+
17
+ ## Steps
18
+
19
+ ### 1 — Preflight
20
+
21
+ Confirm you're not on `main` and the tree is clean (`git status --porcelain`
22
+ empty, or everything intentionally staged). If dirty, resolve before
23
+ continuing — an uncommitted file left behind silently ships in the next
24
+ commit on this branch.
25
+
26
+ ### 2 — Resync with `origin/main`
27
+
28
+ ```bash
29
+ git fetch origin
30
+ git rebase origin/main
31
+ ```
32
+
33
+ A branch that has drifted from `main` over a long session risks a conflict
34
+ surfacing at merge time instead of now, when it's cheaper to resolve.
35
+
36
+ ### 3 — Run the full quality gate
37
+
38
+ ```bash
39
+ pnpm verify
40
+ ```
41
+
42
+ This is the one command that reproduces what CI runs. Fix everything it
43
+ reports before pushing — a red `pnpm verify` becomes a red CI run and a
44
+ round-trip that costs more than fixing it now.
45
+
46
+ ### 4 — Pre-push review (optional but recommended)
47
+
48
+ For anything beyond a trivial change, dispatch `code-reviewer` (and
49
+ `silent-failure-hunter` if the diff has error-handling paths) over the diff
50
+ before pushing. Catching a Must-fix here is strictly cheaper than catching
51
+ it after a human reviewer has already looked.
52
+
53
+ ### 5 — Push
54
+
55
+ ```bash
56
+ git push -u origin <branch>
57
+ ```
58
+
59
+ Never `git push --force` a shared branch — if history was rewritten
60
+ (a rebase), use `--force-with-lease` and only when you're certain no one
61
+ else has pushed to this branch.
62
+
63
+ ### 6 — Gather commits since `main`
64
+
65
+ ```bash
66
+ git log origin/main..HEAD --oneline
67
+ ```
68
+
69
+ This is the raw material for the PR title and body — read every commit
70
+ message, don't just count them.
71
+
72
+ ### 7 — Title
73
+
74
+ A Conventional Commit–shaped title summarizing the PR as a whole (not just
75
+ the first commit): `<type>: <subject>`, same rules as an individual commit
76
+ subject (imperative, ≤70 chars, lowercase after the colon). If the PR
77
+ contains a `feat!:` commit, the title carries `!` too.
78
+
79
+ ### 8 — Body
80
+
81
+ Structure:
82
+
83
+ ```
84
+ ## Summary
85
+ <1-3 sentences: what this PR does and why>
86
+
87
+ ## Changes
88
+ - <bullet per meaningfully distinct change, not per commit>
89
+
90
+ ## Semver impact
91
+ <one sentence: what changed in the public surface, or "none">
92
+
93
+ ## Test plan
94
+ <how this was verified: `pnpm verify` passing, plus anything manual>
95
+ ```
96
+
97
+ ### 9 — Submit
98
+
99
+ ```bash
100
+ gh pr create --title "<title>" --body "$(cat <<'EOF'
101
+ <body>
102
+ EOF
103
+ )"
104
+ ```
105
+
106
+ ### 10 — Confirm mergeability
107
+
108
+ ```bash
109
+ gh pr view --json mergeable,mergeStateStatus
110
+ ```
111
+
112
+ If `mergeable: "CONFLICTING"`, resolve the conflict before proceeding to the
113
+ next step — don't arm auto-merge on a PR that can't merge.
114
+
115
+ ### 11 — Decide the merge path
116
+
117
+ - **CI is required and passing, and the change is low-risk** (docs, a
118
+ mechanical chore, a well-reviewed small fix): arm auto-merge
119
+ (`gh pr merge --auto --squash`) and move on — `finishing-work` picks up
120
+ once it actually merges.
121
+ - **The change is substantive** (a new public symbol, a behavior change, a
122
+ breaking change): leave the PR open for human review. Report the PR URL
123
+ and stop here.
124
+ - **CI is still running and the change is low-risk**: arm auto-merge anyway
125
+ — it fires the moment checks pass, no need to poll.
126
+
127
+ ## Notes
128
+
129
+ - Use the `gh` CLI for every GitHub operation in this skill (issue/PR reads,
130
+ mutations, checks) rather than the raw REST API.
131
+ - If the project has a PR template (`.github/pull_request_template.md`),
132
+ read it first and follow its structure instead of the generic shape above.
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: finishing-work
3
+ description: >-
4
+ Runs the post-merge close-out tail creating-prs doesn't: verifies the PR
5
+ actually merged, returns to main and pulls, deletes the merged local
6
+ branches, prunes stale remote refs, and prompts for a work log. Use for
7
+ /finishing-work, "clean up after this PR", "the PR merged, wrap this up",
8
+ "delete the merged branches", "prune stale remote refs", or when a merged
9
+ branch or stale refs linger -- even when it sounds like a one-line git
10
+ command, because deleting a branch that never merged loses work and this
11
+ skill checks first. GitHub stance: gh CLI.
12
+ ---
13
+
14
+ # finishing-work
15
+
16
+ `creating-prs` ends with "decide the merge path" — it owns the merge itself,
17
+ but nothing checks whether that merge actually happened, let alone cleans up
18
+ afterward. Left undone, that residue accumulates silently: a stale local
19
+ branch, stale remote-tracking refs, an orphaned dispatch journal in the
20
+ scratchpad. This skill is that missing owner.
21
+
22
+ ## Steps
23
+
24
+ ### 1 — Confirm the PR actually merged
25
+
26
+ Don't assume "the user said it merged" is enough — verify:
27
+
28
+ ```bash
29
+ gh pr view --json state,mergedAt,headRefName,baseRefName
30
+ ```
31
+
32
+ - `state: "MERGED"` with a non-null `mergedAt` → proceed.
33
+ - `state: "OPEN"` → stop; the merge decision hasn't been made yet. Point back
34
+ at `creating-prs`'s merge-path step.
35
+ - `state: "CLOSED"` with a null `mergedAt` → stop; the PR was closed without
36
+ merging. Ask whether the branch should still be cleaned up (abandoned work)
37
+ or left alone.
38
+
39
+ Record `headRefName` — every later step operates on this branch, not
40
+ whatever the user typed.
41
+
42
+ ### 2 — Return to `main` and pull
43
+
44
+ ```bash
45
+ git checkout main
46
+ git pull
47
+ ```
48
+
49
+ Skip this if already on `main` with nothing to pull.
50
+
51
+ ### 3 — Delete the merged branch
52
+
53
+ **Before removing anything, confirm no backgrounded command (a `git push`,
54
+ a verify run, or similar) is still running against the branch you're about
55
+ to delete.** Once a PR has GitHub auto-merge armed, a `git push` updating it
56
+ after opening is racing the merge, not safely queued behind it. If a
57
+ follow-up commit must land in the _same_ PR, verify the push landed and the
58
+ PR still shows it as HEAD _before_ proceeding, or accept it may need a
59
+ follow-up PR instead.
60
+
61
+ Squash-merged branch commits are never ancestors of `main`, so "the PR
62
+ merged" does not mean every commit on the branch landed. Run `git log
63
+ <branch> ^origin/main --oneline` before any branch-deleting cleanup — a
64
+ non-empty result is a commit about to be abandoned, not noise.
65
+
66
+ ```bash
67
+ git branch -d <headRefName>
68
+ ```
69
+
70
+ If `git branch -d` refuses (not merged into its base by ancestry — expected
71
+ after a squash merge), don't force-delete without asking: confirm the merge
72
+ really landed via `gh pr view` above, then use `git branch -D <headRefName>`
73
+ only with the user's go-ahead.
74
+
75
+ ### 4 — Prune stale remote-tracking refs
76
+
77
+ ```bash
78
+ git fetch --prune
79
+ ```
80
+
81
+ Cheap and safe regardless of the branch outcome above — clears the
82
+ `[deleted]` marker for this and any other already-merged branch's remote
83
+ ref.
84
+
85
+ ### 5 — Work log check
86
+
87
+ If the project keeps work logs (check whether a `docs/logs/` directory or
88
+ equivalent convention exists), apply a substance test, not a commit-type
89
+ filter: skip silently for a mechanical merge with no narrative (a dependency
90
+ bump, a formatting sweep). Otherwise, ask whether one should be written now
91
+ before moving on — real-time context degrades fast once the session that did
92
+ the work is gone.
93
+
94
+ **If a log is written here, commit and land it immediately** (its own small
95
+ `docs:` commit via `writing-commits`) before moving on to any other task,
96
+ rather than leaving it as an uncommitted file. **"Commit it" does not mean
97
+ commit directly to `main`** — branch first (`git switch -c docs/<slug>-log`),
98
+ commit there, push, and open a PR, even for a trivial docs-only change, if
99
+ the project requires a PR for every change to `main`.
100
+
101
+ ### 6 — Orphaned journal sweep
102
+
103
+ Check the scratchpad directory for any writer-spoke dispatch journal older
104
+ than the current task that has no corresponding open work — ask before
105
+ deleting, since a file from a different, still-in-progress task can look
106
+ identical to a genuine orphan.
107
+
108
+ ### 7 — Report
109
+
110
+ One-line summary: branch deleted (or kept, with why), refs pruned, work log
111
+ present/written/skipped, journals swept/left.
112
+
113
+ ## Notes
114
+
115
+ 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.
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: harness-guidance
3
+ description: >-
4
+ Dual-mode Claude Code harness guidance skill. `research` mode answers a
5
+ single Claude Code / Anthropic-guidance question from official sources
6
+ only (anthropic.com, claude.com, code.claude.com, docs.claude.com, the
7
+ Claude Code CHANGELOG). `refresh` mode sweeps this project's whole
8
+ `.claude/` surface — settings, hooks, agents, skills, rules — against a
9
+ living tracker and produces a remediation plan. Use for
10
+ /harness-guidance, "what does Anthropic recommend for X", "is our harness
11
+ up to date with Anthropic", "model pins current". Not how this project's
12
+ harness is wired today — that's CLAUDE.md and the `.claude/` files
13
+ themselves.
14
+ ---
15
+
16
+ # harness-guidance
17
+
18
+ One skill, two modes, sharing one allowlist
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`.
22
+
23
+ **Must only run in the main (hub) agent, never inside a subagent** — it ends
24
+ in `EnterPlanMode` (refresh) or dispatches other agents (either mode), which
25
+ a subagent cannot do (`disallowedTools: Agent`).
26
+
27
+ **No files are written by this skill itself** in research mode by default;
28
+ refresh mode writes to exactly one file, the tracker, in Step 5.
29
+
30
+ ## Authority (read this before either mode)
31
+
32
+ This skill has authority over the **whole `.claude/` surface** —
33
+ `settings.json` and its hook wiring, every hook, every agent (frontmatter,
34
+ model tiering, tool grants), every skill, every rule, and the emitted
35
+ `CLAUDE.md`. It is **not** kind-scoped the way the interview's other answers
36
+ are: the harness a project needs does not vary by whether it's a library or
37
+ a frontend app, with one deliberate exception below. An interview-derived
38
+ emphasis (from `/customize`'s kind-to-facet table) tells this skill which
39
+ facet to research **most deeply**, never which facets it may or may not
40
+ touch.
41
+
42
+ **The one kind-keyed exception:** a `frontend`/web-app project's reviewer
43
+ patterns genuinely differ (visual verification of a UI is a real, distinct
44
+ concern) — every other facet below is identical regardless of project kind.
45
+
46
+ ## Research mode
47
+
48
+ 1. **Scope the topic.** Read the topic from the invocation or the
49
+ surrounding task; at most **one** clarifying question, otherwise infer
50
+ and proceed. Derive **3–5 orthogonal facets** — one per Explore agent.
51
+ Derive a kebab-case slug for the optional Step 5 snapshot.
52
+ 2. **Fan out.** Read `references/official-sources.md` first, then spawn
53
+ **all agents in a single message**. Each brief carries: one facet; the
54
+ allowlist + GitHub caveat pasted verbatim; today's date; "do not stop at
55
+ the first matching source — fetch every distinct one"; "reject any
56
+ non-allowlisted domain outright and say so"; "you hold no write tool —
57
+ findings travel only in your response"; the findings format below; and a
58
+ ~8,000-character (~2,000-token) return cap. Always `subagent_type:
59
+ "Explore"`, breadth `"very thorough"`.
60
+
61
+ Findings format, one block per source:
62
+
63
+ ```
64
+ SOURCE: <URL>
65
+ CLAIM: <the specific claim, quoted or tightly paraphrased>
66
+ CONFLICT-WITH: <another SOURCE, if this claim contradicts it — omit if none>
67
+ ```
68
+
69
+ 3. **Aggregate & synthesize.** Read every agent's full inline findings —
70
+ digests are for triage, not synthesis. Assign `S1, S2, …` deduping; merge
71
+ agreement into single consensus points tagged with all supporting ids;
72
+ flag contradictions — current docs outrank an older blog post; a
73
+ model-specific guide outranks a general one.
74
+ 4. **Ask a clarifying question only if genuinely needed** — only when two
75
+ current, equally authoritative sources conflict in a way that changes the
76
+ invoking task.
77
+ 5. **Offer an optional snapshot.** Default is inline-only. On explicit
78
+ confirmation, write `docs/research/harness/<topic-slug>.md`, assembled
79
+ from Step 2's findings + Step 3's synthesis (not re-fetched), with a `>
80
+ **Provenance** —` header naming today's date and the sources consulted.
81
+
82
+ ## Refresh mode
83
+
84
+ 1. **Read the tracker & establish anchors.** Read
85
+ `docs/research/harness-refresh.md`, its header
86
+ `<!-- harness-refresh: last-verified=<date> claude-code-version=<version> -->`.
87
+ Missing tracker/facet → first run, `NEW` only. Then read the allowlist
88
+ file; state today's date; derive a run directory
89
+ `<scratchpad>/harness-refresh-<date>/`.
90
+ 2. **Build the delta.** `WebFetch` the Claude Code CHANGELOG, extract
91
+ entries newer than the recorded version. An unreachable source is a
92
+ coverage gap, not a blocker. Pass this delta into all five briefs below
93
+ — it is not a sixth facet.
94
+ 3. **Fan out five fixed facets in one message** — fixed, not derived per
95
+ run, so sweeps stay comparable and the tracker stays diffable:
96
+
97
+ | Facet id | Emitted surface it validates |
98
+ | ---------------------------- | -------------------------------------------------------------------- |
99
+ | `models-tiering` | agent frontmatter `model`/`effort` fields |
100
+ | `cc-features-settings` | `settings.json` shape, permissions, hook event coverage |
101
+ | `agent-subagent-design` | the 5 agents, tool grants, `disallowedTools`, the hub-and-spoke loop |
102
+ | `skills-context-engineering` | the skills, frontmatter, description length |
103
+ | `hooks-lifecycle` | the 10 hooks, event names, matchers, the exit-code contract |
104
+
105
+ Each brief carries: the facet row, Step 2's delta, the tracker's prior
106
+ 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:
109
+
110
+ ```
111
+ CLAIM: <the tracker's prior claim, or "NEW" if none existed>
112
+ VERDICT: UNCHANGED | CHANGED | GONE
113
+ NOW: <the current official position>
114
+ REPO-IMPACT: <which emitted file(s) this affects, or "none">
115
+ ```
116
+
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).
119
+
120
+ 4. **Aggregate.** Read every scratchpad file in full. Four buckets:
121
+ confirmed drift with repo impact (verify each against the cited file
122
+ itself before trusting it — an agent can misread a page), guidance
123
+ changes with no impact, dead/moved URLs, coverage gaps.
124
+ 5. **Update the tracker in place** (not a new dated file) — this skill's
125
+ only write outside plan mode. Bump the header date + version, update
126
+ every checked claim's text/URL/date, add `NEW` sources, update the
127
+ outstanding-drift table.
128
+ 6. **`EnterPlanMode`** with a remediation plan, one section per
129
+ confirmed-drift item. No drift → skip plan mode, report a clean sweep,
130
+ still update the tracker.
131
+
132
+ ## Why this exists, separately from "how is our harness wired"
133
+
134
+ Research mode answers "what does Anthropic recommend for X." Nothing else in
135
+ the baseline asks the inverse — "is what's already built still what
136
+ Anthropic currently recommends" — because a locally-passing `check:agents`/
137
+ `check:hooks`-style check only verifies internal consistency, never freshness
138
+ against the outside world. A retired model pin or a deprecated hook pattern
139
+ passes every internal-consistency check cleanly; only a live sweep against
140
+ Anthropic's own current docs surfaces it.