@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.
- package/README.md +23 -0
- package/bin/m3l-groundwork.mjs +10 -0
- package/dist/assets.d.ts +20 -0
- package/dist/assets.js +79 -0
- package/dist/caps.d.ts +25 -0
- package/dist/caps.js +69 -0
- package/dist/conflicts.d.ts +12 -0
- package/dist/conflicts.js +77 -0
- package/dist/emit.d.ts +7 -0
- package/dist/emit.js +42 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.js +9 -0
- package/dist/harness/conformance.d.ts +20 -0
- package/dist/harness/conformance.js +18 -0
- package/dist/harness/frontmatter.d.ts +38 -0
- package/dist/harness/frontmatter.js +204 -0
- package/dist/harness/grade.d.ts +4 -0
- package/dist/harness/grade.js +105 -0
- package/dist/harness/rules.d.ts +55 -0
- package/dist/harness/rules.js +580 -0
- package/dist/harness/types.d.ts +32 -0
- package/dist/harness/types.js +9 -0
- package/dist/inventory.d.ts +63 -0
- package/dist/inventory.js +66 -0
- package/dist/jsonc.d.ts +14 -0
- package/dist/jsonc.js +83 -0
- package/dist/main.d.ts +24 -0
- package/dist/main.js +297 -0
- package/dist/merge-json.d.ts +74 -0
- package/dist/merge-json.js +135 -0
- package/dist/mode.d.ts +19 -0
- package/dist/mode.js +53 -0
- package/dist/packs.d.ts +61 -0
- package/dist/packs.js +186 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.js +79 -0
- package/dist/report.d.ts +4 -0
- package/dist/report.js +323 -0
- package/dist/survey/fs-walk.d.ts +14 -0
- package/dist/survey/fs-walk.js +60 -0
- package/dist/survey/survey-docs.d.ts +4 -0
- package/dist/survey/survey-docs.js +69 -0
- package/dist/survey/survey-harness.d.ts +4 -0
- package/dist/survey/survey-harness.js +121 -0
- package/dist/survey/survey-shape.d.ts +4 -0
- package/dist/survey/survey-shape.js +182 -0
- package/dist/survey/survey-toolchain.d.ts +4 -0
- package/dist/survey/survey-toolchain.js +217 -0
- package/dist/survey/survey.d.ts +5 -0
- package/dist/survey/survey.js +21 -0
- package/dist/survey/types.d.ts +117 -0
- package/dist/survey/types.js +8 -0
- package/dist/tokens.d.ts +13 -0
- package/dist/tokens.js +13 -0
- package/dist/toolchain/conformance.d.ts +20 -0
- package/dist/toolchain/conformance.js +30 -0
- package/dist/toolchain/grade.d.ts +4 -0
- package/dist/toolchain/grade.js +244 -0
- package/dist/toolchain/rules.d.ts +118 -0
- package/dist/toolchain/rules.js +706 -0
- package/dist/toolchain/tsconfig-chain.d.ts +36 -0
- package/dist/toolchain/tsconfig-chain.js +116 -0
- package/dist/toolchain/types.d.ts +27 -0
- package/dist/toolchain/types.js +9 -0
- package/package.json +59 -0
- package/plugin/skills/customize/SKILL.md +305 -0
- package/plugin/src/domain-map.ts +134 -0
- package/plugin/src/index.ts +4 -0
- package/plugin/src/kind-facet-map.ts +174 -0
- package/plugin/src/pack-map.ts +65 -0
- package/templates/core/.claude/agents/Explore.md +43 -0
- package/templates/core/.claude/agents/code-implementer.md +258 -0
- package/templates/core/.claude/agents/code-reviewer.md +163 -0
- package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
- package/templates/core/.claude/agents/test-author.md +211 -0
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
- package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
- package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
- package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
- package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
- package/templates/core/.claude/rules/agent-dispatch.md +121 -0
- package/templates/core/.claude/rules/refactoring.md +52 -0
- package/templates/core/.claude/rules/src.md +114 -0
- package/templates/core/.claude/rules/tests.md +129 -0
- package/templates/core/.claude/settings.json +111 -0
- package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
- package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
- package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
- package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
- package/templates/core/.github/workflows/ci.yml +123 -0
- package/templates/core/.github/workflows/dependency-review.yml +26 -0
- package/templates/core/.github/workflows/security-audit.yml +54 -0
- package/templates/core/.node-version +1 -0
- package/templates/core/.prettierignore +5 -0
- package/templates/core/.prettierrc.json +4 -0
- package/templates/core/CLAUDE.md +127 -0
- package/templates/core/README.md +24 -0
- package/templates/core/_gitignore +19 -0
- package/templates/core/_npmrc +1 -0
- package/templates/core/bin/check-exports.mjs +92 -0
- package/templates/core/bin/check-harness.mjs +27 -0
- package/templates/core/bin/check-node-version.mjs +51 -0
- package/templates/core/bin/check-toolchain.mjs +20 -0
- package/templates/core/bin/lib/agent-roster.mjs +8 -0
- package/templates/core/bin/lib/frontmatter.mjs +210 -0
- package/templates/core/bin/lib/harness-rules.mjs +916 -0
- package/templates/core/bin/lib/protected-paths.mjs +23 -0
- package/templates/core/bin/lib/report.mjs +56 -0
- package/templates/core/bin/lib/signed-range.mjs +178 -0
- package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
- package/templates/core/bin/lib/verify-steps.mjs +131 -0
- package/templates/core/bin/lib/verify-steps.packs.json +1 -0
- package/templates/core/bin/lint-commit.mjs +50 -0
- package/templates/core/bin/strip-claude-trailers.mjs +25 -0
- package/templates/core/bin/verify.mjs +64 -0
- package/templates/core/commitlint.config.js +11 -0
- package/templates/core/docs/research/harness-refresh.md +27 -0
- package/templates/core/docs/research/typescript-refresh.md +32 -0
- package/templates/core/eslint.config.js +105 -0
- package/templates/core/knip.json +6 -0
- package/templates/core/lefthook.yml +39 -0
- package/templates/core/package.json +58 -0
- package/templates/core/pnpm-workspace.yaml +13 -0
- package/templates/core/src/index.ts +12 -0
- package/templates/core/tests/index.test.ts +8 -0
- package/templates/core/tsconfig.base.json +36 -0
- package/templates/core/tsconfig.build.json +10 -0
- package/templates/core/tsconfig.json +11 -0
- package/templates/core/vitest.config.ts +32 -0
- package/templates/packs/README.md +81 -0
- package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
- package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
- package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
- package/templates/packs/harness-extras/pack.json +65 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
- package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
- 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.
|