create-agent-rig 0.3.0 → 0.3.1
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/CHANGELOG.md +36 -0
- package/README.md +9 -1
- package/package.json +1 -1
- package/packages/cli/dist/commands/init.js +73 -18
- package/packages/cli/dist/index.js +11 -1
- package/packages/cli/dist/lib/init-settings.js +52 -0
- package/packages/cli/dist/templates.js +8 -0
- package/templates/agent-os/init/CLAUDE.md +133 -0
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +1 -1
- package/templates/agent-os/universal/.claude/rules/invariants.md +12 -11
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ the generator.
|
|
|
7
7
|
Versions are published to npm as [`create-agent-rig`](https://www.npmjs.com/package/create-agent-rig);
|
|
8
8
|
`npx github:serhii-baksheiev/create-agent-rig` keeps working for either path.
|
|
9
9
|
|
|
10
|
+
## 0.3.1
|
|
11
|
+
|
|
12
|
+
`create-agent-rig init` shipped a rig that looked installed and enforced
|
|
13
|
+
nothing. Everything below is that one failure, in its four parts — a repo
|
|
14
|
+
`init`ed with 0.3.0 should be re-run with this version (`--force` to replace the
|
|
15
|
+
CLAUDE.md it wrote).
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **The hooks are wired.** `init` laid the hook files down and stopped there: no
|
|
20
|
+
`.claude/settings.json` meant `guard-bash`, `block-no-verify`, `gate-stop-dod`
|
|
21
|
+
and `inject-rules` were never called, while the installed `CLAUDE.md` claimed
|
|
22
|
+
they were enforced at the tool layer. The wiring is now _derived_ from the
|
|
23
|
+
shipped settings, so it names exactly the hooks that travelled — never one that
|
|
24
|
+
did not. Where the repo already has a `settings.json`, `init` keeps it and
|
|
25
|
+
prints the entries to merge rather than failing silently.
|
|
26
|
+
- **The kill switch works.** `init` copied templates byte-for-byte, leaving
|
|
27
|
+
`__PROJECT_NAME__` in six places — including `stop-flag.mjs`, so the brake
|
|
28
|
+
looked for `~/.claude/__PROJECT_NAME__-loop-STOP` while the operator, following
|
|
29
|
+
the instructions in the same install, created `~/.claude/<repo>-loop-STOP`. It
|
|
30
|
+
never fired, and never said so.
|
|
31
|
+
- **The installed `CLAUDE.md` describes the repo it landed in.** It used to be
|
|
32
|
+
the generated monorepo's map — `packages/core/`, `apps/web/`, links to an
|
|
33
|
+
`architecture.md` and two guards that `init` deliberately does not install. It
|
|
34
|
+
is now its own document: what was installed, what was not, and that the
|
|
35
|
+
architecture rules are yours to write.
|
|
36
|
+
- **The elevated-path block names paths that exist.** It seeded
|
|
37
|
+
`packages/db/src/` into repos that have no such directory, so the Tier-2 gate
|
|
38
|
+
sweep reported "clean" while looking at nothing.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- A template test that fails if anything `init` installs references a `.claude`
|
|
43
|
+
file `init` does not install — the drift that produced three of the four
|
|
44
|
+
findings above, now mechanical.
|
|
45
|
+
|
|
10
46
|
## 0.3.0
|
|
11
47
|
|
|
12
48
|
The factory extraction: a scaffolded project now arrives with a working
|
package/README.md
CHANGED
|
@@ -22,7 +22,15 @@ npx create-agent-rig init --dry-run # print the plan, write nothing
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
`init` drops in the autonomy tiers, stop rules, workflow, and the enforcement
|
|
25
|
-
hooks
|
|
25
|
+
hooks — **wired**, in a `.claude/settings.json` that names exactly the hooks it
|
|
26
|
+
installed — plus a `CLAUDE.md` that describes that rig rather than the generated
|
|
27
|
+
monorepo. It refuses to clobber an existing `CLAUDE.md`; if the repo already has
|
|
28
|
+
a `.claude/settings.json`, it keeps it and prints the entries to merge, because a
|
|
29
|
+
hook nothing calls is not enforcement.
|
|
30
|
+
|
|
31
|
+
Two things it deliberately leaves to you, and says so in the installed
|
|
32
|
+
`CLAUDE.md`: the Definition-of-Done gate has no `dod-checks.json` (it cannot know
|
|
33
|
+
your commands), and the elevated-path list names only what every repo has.
|
|
26
34
|
|
|
27
35
|
## What you get
|
|
28
36
|
|
package/package.json
CHANGED
|
@@ -1,37 +1,93 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { settingsForInstalledHooks } from '../lib/init-settings.js';
|
|
4
|
+
import { substituteContent } from '../lib/substitute.js';
|
|
5
|
+
import { agentOsInitDir, agentOsUniversalDir } from '../templates.js';
|
|
4
6
|
/** A user-facing failure: message is printed as-is, no stack trace. */
|
|
5
7
|
export class InitError extends Error {
|
|
6
8
|
}
|
|
9
|
+
const SETTINGS = '.claude/settings.json';
|
|
7
10
|
async function loadManifest() {
|
|
8
11
|
const raw = await readFile(path.join(agentOsUniversalDir(), 'layers.json'), 'utf8');
|
|
9
12
|
return JSON.parse(raw);
|
|
10
13
|
}
|
|
14
|
+
async function exists(p) {
|
|
15
|
+
try {
|
|
16
|
+
await access(p);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The name this repo is known by inside the rig. It ends up in a *filename* —
|
|
25
|
+
* `~/.claude/<name>-loop-STOP`, the kill switch — so it is reduced to
|
|
26
|
+
* characters an operator can type into a shell without quoting.
|
|
27
|
+
*/
|
|
28
|
+
export function projectNameFor(repoDir) {
|
|
29
|
+
const base = path.basename(path.resolve(repoDir));
|
|
30
|
+
const slug = base
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
33
|
+
.replace(/^[-.]+|[-.]+$/g, '');
|
|
34
|
+
return slug === '' ? 'project' : slug;
|
|
35
|
+
}
|
|
11
36
|
/**
|
|
12
37
|
* `init` installs only the PROCESS layer (hooks-and-reach brief §3/§4): rules
|
|
13
38
|
* that assume nothing about the codebase shape. Architecture rules reference
|
|
14
39
|
* `packages/core` and friends — installing them into an arbitrary repo would
|
|
15
40
|
* describe a structure that does not exist, which is worse than no rule.
|
|
16
41
|
*
|
|
17
|
-
*
|
|
42
|
+
* It also installs two things the process manifest does not name, because both
|
|
43
|
+
* are meaningless in the generated shape and load-bearing here:
|
|
44
|
+
*
|
|
45
|
+
* - `CLAUDE.md` — the map, taken from the init override layer, which describes
|
|
46
|
+
* the rig this command installs rather than the generated monorepo;
|
|
47
|
+
* - `.claude/settings.json` — the wiring, derived from the shipped settings so
|
|
48
|
+
* it names exactly the hooks that travelled.
|
|
18
49
|
*/
|
|
19
|
-
async function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
export async function initManifest() {
|
|
51
|
+
const manifest = await loadManifest();
|
|
52
|
+
const universal = agentOsUniversalDir();
|
|
53
|
+
const override = agentOsInitDir();
|
|
54
|
+
const files = [];
|
|
55
|
+
for (const rel of [...manifest.process, 'CLAUDE.md']) {
|
|
56
|
+
const overridden = path.join(override, rel);
|
|
57
|
+
files.push({
|
|
58
|
+
rel,
|
|
59
|
+
source: (await exists(overridden)) ? overridden : path.join(universal, rel),
|
|
60
|
+
});
|
|
27
61
|
}
|
|
28
|
-
|
|
29
|
-
|
|
62
|
+
files.push({ rel: SETTINGS, source: null });
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Exactly the bytes `init` would write, keyed by destination path — the single
|
|
67
|
+
* source the plan, the install and the template tests all read.
|
|
68
|
+
*
|
|
69
|
+
* Every file the process layer carries is text (asserted by a template test),
|
|
70
|
+
* so substitution can be applied unconditionally: an unsubstituted
|
|
71
|
+
* `__PROJECT_NAME__` in `stop-flag.mjs` is a kill switch that silently never
|
|
72
|
+
* fires.
|
|
73
|
+
*/
|
|
74
|
+
export async function initFileContents(repoDir) {
|
|
75
|
+
const projectName = projectNameFor(repoDir);
|
|
76
|
+
const ctx = { projectName, projectScope: projectName, region: '' };
|
|
77
|
+
const files = await initManifest();
|
|
78
|
+
const contents = new Map();
|
|
79
|
+
for (const { rel, source } of files) {
|
|
80
|
+
if (source === null)
|
|
81
|
+
continue;
|
|
82
|
+
contents.set(rel, substituteContent(await readFile(source, 'utf8'), ctx));
|
|
30
83
|
}
|
|
84
|
+
const installedHooks = new Set(files.map((f) => f.rel).filter((rel) => rel.startsWith('.claude/hooks/')));
|
|
85
|
+
const shipped = JSON.parse(await readFile(path.join(agentOsUniversalDir(), SETTINGS), 'utf8'));
|
|
86
|
+
contents.set(SETTINGS, `${JSON.stringify(settingsForInstalledHooks(shipped, installedHooks), null, 2)}\n`);
|
|
87
|
+
return contents;
|
|
31
88
|
}
|
|
32
89
|
export async function planInit(repoDir) {
|
|
33
|
-
const
|
|
34
|
-
const files = await processFiles(manifest);
|
|
90
|
+
const files = (await initManifest()).map((f) => f.rel);
|
|
35
91
|
const conflicts = [];
|
|
36
92
|
for (const rel of files) {
|
|
37
93
|
if (await exists(path.join(repoDir, rel)))
|
|
@@ -40,9 +96,7 @@ export async function planInit(repoDir) {
|
|
|
40
96
|
return { files: files.map((p) => ({ path: p })), conflicts };
|
|
41
97
|
}
|
|
42
98
|
export async function initProject(repoDir, options) {
|
|
43
|
-
const
|
|
44
|
-
const files = await processFiles(manifest);
|
|
45
|
-
const universal = agentOsUniversalDir();
|
|
99
|
+
const files = (await initManifest()).map((f) => f.rel);
|
|
46
100
|
// Refuse to clobber an existing CLAUDE.md unless forced — init edits
|
|
47
101
|
// someone's working repository (brief §4, non-negotiable).
|
|
48
102
|
if (!options.force && files.includes('CLAUDE.md')) {
|
|
@@ -51,6 +105,7 @@ export async function initProject(repoDir, options) {
|
|
|
51
105
|
'Merge the agent-os map in by hand, or re-run with --force to replace it.');
|
|
52
106
|
}
|
|
53
107
|
}
|
|
108
|
+
const contents = await initFileContents(repoDir);
|
|
54
109
|
const written = [];
|
|
55
110
|
const skipped = [];
|
|
56
111
|
const plannedCount = files.length;
|
|
@@ -65,7 +120,7 @@ export async function initProject(repoDir, options) {
|
|
|
65
120
|
if (options.dryRun)
|
|
66
121
|
continue;
|
|
67
122
|
await mkdir(path.dirname(dest), { recursive: true });
|
|
68
|
-
await writeFile(dest,
|
|
123
|
+
await writeFile(dest, contents.get(rel) ?? '');
|
|
69
124
|
written.push(rel);
|
|
70
125
|
}
|
|
71
126
|
return { written, skipped, plannedCount };
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { CreateError, createProject } from './commands/create.js';
|
|
7
|
-
import { InitError, initProject, planInit } from './commands/init.js';
|
|
7
|
+
import { InitError, initFileContents, initProject, planInit } from './commands/init.js';
|
|
8
8
|
import { makePalette } from './lib/colors.js';
|
|
9
9
|
import { promptTarget } from './lib/prompts.js';
|
|
10
10
|
import { collectGovernance, renderSummary } from './lib/summary.js';
|
|
@@ -65,6 +65,16 @@ async function runInit(rawArgs) {
|
|
|
65
65
|
process.stdout.write(`\nInstalled ${result.written.length} files` +
|
|
66
66
|
(result.skipped.length ? `, kept ${result.skipped.length} existing` : '') +
|
|
67
67
|
'.\n');
|
|
68
|
+
// The one kept file that silently disables everything else: without this
|
|
69
|
+
// wiring the hooks sit on disk and are never called, while the rules claim
|
|
70
|
+
// they are enforced. Say so loudly, and hand over the exact entries.
|
|
71
|
+
if (result.skipped.includes('.claude/settings.json')) {
|
|
72
|
+
const wiring = (await initFileContents(cwd)).get('.claude/settings.json') ?? '';
|
|
73
|
+
process.stdout.write(`\n! .claude/settings.json already exists — it was kept, so the rig's hooks are NOT wired.\n` +
|
|
74
|
+
` Until you merge these entries into it, nothing enforces the rules:\n\n` +
|
|
75
|
+
wiring.replace(/^/gm, ' ') +
|
|
76
|
+
'\n');
|
|
77
|
+
}
|
|
68
78
|
return 0;
|
|
69
79
|
}
|
|
70
80
|
async function main() {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `init` installs the PROCESS layer only — a subset of the hooks the generated
|
|
3
|
+
* shape gets. The wiring it writes has to match that subset exactly:
|
|
4
|
+
*
|
|
5
|
+
* - wiring a hook file that was not installed makes every matching tool call
|
|
6
|
+
* fail on a missing module;
|
|
7
|
+
* - wiring nothing at all is worse and quieter — the hooks sit on disk, the
|
|
8
|
+
* rules claim they are enforced, and nothing ever calls them.
|
|
9
|
+
*
|
|
10
|
+
* So the wiring is *derived* from the shipped settings.json rather than
|
|
11
|
+
* maintained as a second copy: add a process hook and wire it once, upstream,
|
|
12
|
+
* and init picks it up.
|
|
13
|
+
*/
|
|
14
|
+
/** Matches the hook file a wired command runs, e.g. `.claude/hooks/guard-bash.mjs`. */
|
|
15
|
+
const HOOK_REFERENCE = /\.claude\/hooks\/[A-Za-z0-9._-]+\.mjs/;
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
/** An entry survives unless it names a hook file that is not installed. */
|
|
20
|
+
function keepEntry(entry, installed) {
|
|
21
|
+
if (!isRecord(entry) || typeof entry.command !== 'string')
|
|
22
|
+
return true;
|
|
23
|
+
const referenced = HOOK_REFERENCE.exec(entry.command);
|
|
24
|
+
return referenced === null || installed.has(referenced[0]);
|
|
25
|
+
}
|
|
26
|
+
/** A group survives only with at least one entry left — never as an empty shell. */
|
|
27
|
+
function keepGroup(group, installed) {
|
|
28
|
+
if (!isRecord(group) || !Array.isArray(group.hooks))
|
|
29
|
+
return group;
|
|
30
|
+
const hooks = group.hooks.filter((entry) => keepEntry(entry, installed));
|
|
31
|
+
return hooks.length === 0 ? null : { ...group, hooks };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The shipped settings, narrowed to the hooks actually installed. Shapes this
|
|
35
|
+
* function does not understand are passed through untouched — it filters, it
|
|
36
|
+
* never rewrites.
|
|
37
|
+
*/
|
|
38
|
+
export function settingsForInstalledHooks(settings, installed) {
|
|
39
|
+
if (!isRecord(settings) || !isRecord(settings.hooks))
|
|
40
|
+
return settings;
|
|
41
|
+
const events = {};
|
|
42
|
+
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
43
|
+
if (!Array.isArray(groups)) {
|
|
44
|
+
events[event] = groups;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const kept = groups.map((group) => keepGroup(group, installed)).filter((g) => g !== null);
|
|
48
|
+
if (kept.length > 0)
|
|
49
|
+
events[event] = kept;
|
|
50
|
+
}
|
|
51
|
+
return { ...settings, hooks: events };
|
|
52
|
+
}
|
|
@@ -19,3 +19,11 @@ export function agentOsUniversalDir() {
|
|
|
19
19
|
export function agentOsStackDir(stack) {
|
|
20
20
|
return path.join(templatesRoot(), 'agent-os', 'stack', stack);
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Overrides `init` applies on top of the universal layer. A file here replaces
|
|
24
|
+
* its universal namesake when the rig is installed into an existing repo whose
|
|
25
|
+
* shape the generator knows nothing about. `create` never reads this directory.
|
|
26
|
+
*/
|
|
27
|
+
export function agentOsInitDir() {
|
|
28
|
+
return path.join(templatesRoot(), 'agent-os', 'init');
|
|
29
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
> **Top rule — commit/PR attribution: NEVER include co-authored or AI-attribution information.**
|
|
4
|
+
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By: Claude …`), `Generated with Claude Code`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
|
|
5
|
+
|
|
6
|
+
This repository runs under an agent operating system. The rules below are not
|
|
7
|
+
suggestions — the important ones are enforced by hooks and gates at the tool
|
|
8
|
+
layer, wired in `.claude/settings.json`.
|
|
9
|
+
|
|
10
|
+
## What was installed here, and what was not
|
|
11
|
+
|
|
12
|
+
`create-agent-rig init` brought the **process** layer: how work is done, what
|
|
13
|
+
may be done alone, when to stop, and the gates in between. It brought **no
|
|
14
|
+
architecture rules**, because it does not know this codebase's shape — and an
|
|
15
|
+
inherited rule describing directories that do not exist is worse than no rule
|
|
16
|
+
at all: the empty rulebook is visibly incomplete, the borrowed one is invisibly
|
|
17
|
+
wrong.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
.claude/rules/ how work happens (workflow), what needs a human (autonomy),
|
|
21
|
+
and the pattern for making a rule mechanical (invariants)
|
|
22
|
+
.claude/hooks/ the checks that refuse a violation at the tool layer
|
|
23
|
+
.claude/agents/ the review gates: test-writer, code-reviewer, security-scanner
|
|
24
|
+
.claude/skills/ the drivers: loop, pr-ship, worktree-task, new-invariant
|
|
25
|
+
.claude/scripts/ the queue adapter, the preflight, the out-of-band sweeps
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**The architecture rules of this project are yours to write.** When this repo
|
|
29
|
+
has a boundary worth stating — a layer that must not import another, a module
|
|
30
|
+
that owns an SDK, a directory that stays pure — state it in a new file under
|
|
31
|
+
`.claude/rules/`, name it from this section, and if it is worth enforcing, give
|
|
32
|
+
it a hook via the `new-invariant` skill.
|
|
33
|
+
|
|
34
|
+
## If you read only three sections, read these
|
|
35
|
+
|
|
36
|
+
1. **Autonomy tiers** — what you may do alone vs. propose first:
|
|
37
|
+
`.claude/rules/autonomy.md` ("Tiers")
|
|
38
|
+
2. **Stop rules** — when stopping with a diagnosis is the correct move:
|
|
39
|
+
`.claude/rules/autonomy.md` ("Stop rules")
|
|
40
|
+
3. **Definition of Done** — the checklist a change must pass:
|
|
41
|
+
`.claude/rules/workflow.md` ("Definition of Done")
|
|
42
|
+
|
|
43
|
+
## How work happens here
|
|
44
|
+
|
|
45
|
+
- **TDD, without exception.** The failing test comes first — use the
|
|
46
|
+
`test-writer` agent for it. See `.claude/rules/workflow.md`.
|
|
47
|
+
- **One task, one branch — and merge via PR.** Every unit of work gets its own
|
|
48
|
+
short-lived branch; the default branch is never committed to directly. Once
|
|
49
|
+
the project has a remote and CI, changes reach it through the PR flow (local
|
|
50
|
+
checks → reviewer fan-out → merge on an explicit criterion). See
|
|
51
|
+
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
52
|
+
session may touch this repo at the same time, the branch lives in its own
|
|
53
|
+
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
54
|
+
- **Gates.** `code-reviewer` runs before every PR; `security-scanner` runs when
|
|
55
|
+
a change touches auth, secrets, parsing, or outbound calls. Blocking findings
|
|
56
|
+
are resolved, not argued with. The `pr-ship` skill drives the gate.
|
|
57
|
+
- **Enforcement is mechanical.** `block-no-verify` refuses pre-commit bypasses;
|
|
58
|
+
`guard-bash` refuses the "Never" tier — force-pushing a shared branch, a
|
|
59
|
+
production deploy, a filesystem wipe — and carries the kill switch;
|
|
60
|
+
`gate-stop-dod` refuses to end the session while a Definition-of-Done check
|
|
61
|
+
fails; `inject-rules` puts the autonomy rules back in front of the agent at
|
|
62
|
+
the start of every session. If a hook blocks you, fix the cause; never route
|
|
63
|
+
around a hook.
|
|
64
|
+
- **Enforcement is a pattern you can apply again.** Each of those hooks is one
|
|
65
|
+
stated invariant + one mechanical check + one test — the pattern is written
|
|
66
|
+
down in `.claude/rules/invariants.md`, and the `new-invariant` skill walks you
|
|
67
|
+
through adding one. The hooks that ship here are **examples, not laws**: if the
|
|
68
|
+
invariant they guard is not load-bearing in this project, delete it and spend
|
|
69
|
+
the slot on one that is.
|
|
70
|
+
- **There is a brake, and it is a real file.** `touch
|
|
71
|
+
~/.claude/__PROJECT_NAME__-loop-STOP` and `guard-bash` denies every merge
|
|
72
|
+
until it is removed. Everything short of the merge stays allowed on purpose:
|
|
73
|
+
finish the task, push the branch, open the PR, write the journal, stop.
|
|
74
|
+
Stopping cleanly never means losing the work.
|
|
75
|
+
- **Work comes from the queue, through an adapter.** The `loop` skill selects via
|
|
76
|
+
`.claude/scripts/queue/index.mjs`, which reads whichever queue
|
|
77
|
+
`.claude/queue.json` names — the Agent queue in `PLAN.md` by default, issues in
|
|
78
|
+
this repository once it has a remote. An empty queue **ends the session**; it is
|
|
79
|
+
never a cue to invent work, and the agent never files its own work items.
|
|
80
|
+
|
|
81
|
+
## Two things this install left for you to finish
|
|
82
|
+
|
|
83
|
+
Both are one-liners, and both are inert until you do them.
|
|
84
|
+
|
|
85
|
+
1. **The Definition-of-Done gate has nothing to run.** `gate-stop-dod` executes
|
|
86
|
+
the commands listed in `.claude/hooks/dod-checks.json`, and `init` ships no
|
|
87
|
+
such file because it cannot know this project's commands. Until you write one
|
|
88
|
+
— a JSON array like `["npm test", "npm run lint"]` — the stop gate is a
|
|
89
|
+
no-op, and the Definition of Done is back to being a wish.
|
|
90
|
+
2. **The elevated-path list below is a seed, not a survey.** It names only what
|
|
91
|
+
every repo has. Everything else is yours to add.
|
|
92
|
+
|
|
93
|
+
## The elevated paths of this project
|
|
94
|
+
|
|
95
|
+
Tier 2 in `.claude/rules/autonomy.md` names *kinds* of change. This block names
|
|
96
|
+
the **paths** in this repository where those kinds live, and
|
|
97
|
+
`.claude/scripts/detect-missed-gate.mjs` reads it — so a path that is not declared
|
|
98
|
+
is a path the gate sweep cannot see.
|
|
99
|
+
|
|
100
|
+
```elevated-paths
|
|
101
|
+
.claude/
|
|
102
|
+
.github/workflows/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
They are there because they are what *disarms* the rest: a merge that rewrites
|
|
106
|
+
the Never tier, unwires a hook or edits what CI runs should never pass
|
|
107
|
+
unreviewed.
|
|
108
|
+
|
|
109
|
+
**Extend this list the same day you write the code it covers** — a real project
|
|
110
|
+
accumulates more (auth handlers, billing, a credentials module, a migration
|
|
111
|
+
directory, the deployment configuration). The gap between adding the code and
|
|
112
|
+
declaring the path is exactly the window in which a change slips through
|
|
113
|
+
unreviewed. And a path declared over a directory this project does not have is
|
|
114
|
+
worse than an omission: the sweep reports "clean" while looking nowhere.
|
|
115
|
+
|
|
116
|
+
The declaration is **composed, not centralised**: the sweep unions this block
|
|
117
|
+
with every `elevated-paths` block in `.claude/rules/`, so a rule file can
|
|
118
|
+
declare the paths that belong to it.
|
|
119
|
+
|
|
120
|
+
Nothing about this list is retroactive. Installing the sweep into a repo with
|
|
121
|
+
history means passing `--epoch <the day you installed it>` once, or the first run
|
|
122
|
+
reports every merge that predates the gate.
|
|
123
|
+
|
|
124
|
+
## Foot-guns
|
|
125
|
+
|
|
126
|
+
- Don't weaken a failing test to get green — a red check is information, and
|
|
127
|
+
test integrity is a blocking review finding.
|
|
128
|
+
- Don't answer "is this repo healthy?" from a green CI run alone: after a
|
|
129
|
+
deploy, verify the running surface and on regression revert first
|
|
130
|
+
(`.claude/rules/autonomy.md`, "Post-deploy verification").
|
|
131
|
+
- Don't extend the rulebook by writing more prose. A rule that keeps being
|
|
132
|
+
broken wants a hook and a test, not a longer paragraph — that is what
|
|
133
|
+
`.claude/rules/invariants.md` is for.
|
|
@@ -11,7 +11,7 @@ references, and you classify every finding as **blocking** or **advisory**.
|
|
|
11
11
|
|
|
12
12
|
1. **Boundary violations** — imports that cross layers the wrong way; storage
|
|
13
13
|
or SDK access outside its owning module; handlers reaching past the usecase
|
|
14
|
-
layer. See `.claude/rules
|
|
14
|
+
layer. See the architecture rules in `.claude/rules/`.
|
|
15
15
|
2. **Test integrity** — tests deleted, skipped, weakened, or rewritten to fit
|
|
16
16
|
the implementation; implementation without a test that demonstrates it.
|
|
17
17
|
3. **Error handling** — swallowed errors, bare catch-and-continue, failure
|
|
@@ -125,27 +125,28 @@ Two rules that follow from it:
|
|
|
125
125
|
|
|
126
126
|
## The worked example — and it is one project's answer, not a law
|
|
127
127
|
|
|
128
|
-
`.claude/hooks/
|
|
128
|
+
`.claude/hooks/block-no-verify.mjs` is this pattern, filled in:
|
|
129
129
|
|
|
130
130
|
| Part | Where |
|
|
131
131
|
| --- | --- |
|
|
132
|
-
| the invariant | `.claude/rules/
|
|
133
|
-
| the check | `.claude/hooks/
|
|
132
|
+
| the invariant | `.claude/rules/autonomy.md`, "Never" — pre-commit is never bypassed |
|
|
133
|
+
| the check | `.claude/hooks/block-no-verify.mjs` |
|
|
134
134
|
| the test | the hook's blocking behaviour, under test |
|
|
135
135
|
|
|
136
|
-
**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
**Every one of them is an example, not a truth.** Take the rule a generated
|
|
137
|
+
project ships as `guard-core-purity` — "the domain core is pure". It is a good
|
|
138
|
+
rule for the shape that project was generated in; it is not a law of software.
|
|
139
|
+
Where a project has no pure core — a thin CRUD service, a CLI, a data pipeline —
|
|
140
|
+
the right move is to **delete the hook, the rule and its test**, and spend the
|
|
141
|
+
slot on the invariant that project actually has. An inherited rule nobody chose
|
|
142
|
+
is worse than an empty rule file: the empty one is visibly incomplete, the
|
|
143
|
+
inherited one is invisibly wrong.
|
|
143
144
|
|
|
144
145
|
The invariants worth your slots are the ones you can finish this sentence about:
|
|
145
146
|
*"the last time this went wrong, it cost us ___."* If you cannot finish it, you
|
|
146
147
|
are guessing, and a guessed invariant is the one that will fire on honest work.
|
|
147
148
|
|
|
148
|
-
## About the
|
|
149
|
+
## About the hooks you were given
|
|
149
150
|
|
|
150
151
|
They arrive with their tests **in the generator that produced this project**, not
|
|
151
152
|
in this repository — so by the rule above, as they sit here, they are checks
|