@nurix/etna 0.19.56 → 0.19.58
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/data/harness-index.json +1 -1
- package/data/harnesses/apollo/CLAUDE.md +1 -1
- package/data/harnesses/apollo/agents/bookkeeper.md +1 -1
- package/data/harnesses/apollo/agents/change-review.md +1 -1
- package/data/harnesses/apollo/agents/lld-mode.md +3 -1
- package/data/harnesses/apollo/agents/maintenance.md +1 -1
- package/data/harnesses/apollo/agents/performance-review.md +1 -1
- package/data/harnesses/apollo/agents/retro.md +1 -1
- package/data/harnesses/apollo/harness.json +2 -2
- package/data/harnesses/apollo/hooks/compliance.mjs +70 -2
- package/data/harnesses/apollo/rules/database.md +2 -2
- package/data/harnesses/apollo/skills/boundary/SKILL.md +4 -0
- package/data/harnesses/apollo/skills/documentation/templates/plan.md +18 -4
- package/data/harnesses/apollo/skills/schema-design/SKILL.md +0 -30
- package/data/harnesses/code/CLAUDE.md +1 -1
- package/data/harnesses/code/agents/bookkeeper.md +1 -1
- package/data/harnesses/code/agents/change-review.md +1 -1
- package/data/harnesses/code/agents/lld-mode.md +3 -1
- package/data/harnesses/code/agents/maintenance.md +1 -1
- package/data/harnesses/code/agents/performance-review.md +1 -1
- package/data/harnesses/code/agents/retro.md +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/compliance.mjs +70 -2
- package/data/harnesses/code/rules/database.md +2 -2
- package/data/harnesses/code/skills/boundary/SKILL.md +4 -0
- package/data/harnesses/code/skills/documentation/templates/plan.md +18 -4
- package/data/harnesses/code/skills/schema-design/SKILL.md +0 -30
- package/data/harnesses/delivery/CLAUDE.md +1 -1
- package/data/harnesses/delivery/agents/lld-mode.md +3 -1
- package/data/harnesses/delivery/agents/maintenance.md +1 -1
- package/data/harnesses/delivery/agents/retro.md +1 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/plan.md +18 -4
- package/data/harnesses/etna/CLAUDE.md +1 -1
- package/data/harnesses/etna/agents/change-review.md +1 -1
- package/data/harnesses/etna/agents/lld-mode.md +3 -1
- package/data/harnesses/etna/agents/maintenance.md +1 -1
- package/data/harnesses/etna/agents/performance-review.md +1 -1
- package/data/harnesses/etna/agents/retro.md +1 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/boundary/SKILL.md +4 -0
- package/data/harnesses/etna/skills/documentation/templates/plan.md +18 -4
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -39,7 +39,7 @@ Three peer modes, then a verification gate and a Completion step — enter where
|
|
|
39
39
|
|
|
40
40
|
1. **The verification gate passes first** — user-triggered: when implementation completes, report it and offer the gate; never run it unbidden. It requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row, driving the real surface before pinning tests — `.claude/rules/testing.md`. A plan with outstanding obligations stays open, whatever the code does.
|
|
41
41
|
2. Freeze the plan — `Status: Complete`, fill the Completed date. The file is now frozen.
|
|
42
|
-
3.
|
|
42
|
+
3. **Discharge the plan's Doc Obligations ledger — every row, into exactly one outcome**: **updated** (the doc now states what the code does), **owes nothing** (with the reason), or **blocked** (→ a backlog item, so the debt outlives the frozen plan). For each row's doc: flip `status: active` if it was `draft`, extend it in place with the how-it-works sections, and confirm its `owns:` globs against the actual code. A plan spanning three features discharges three rows — the ledger, not memory, says how many.
|
|
43
43
|
4. Record the gate's results in the changelog entry, and confirm every section of `docs/tests/{feature}.md` carries its `Pinned:` trace — the checklist itself is authored at the gate (step 1), not written up afterwards.
|
|
44
44
|
|
|
45
45
|
**On Brainstorm Close** (when a session ends, not when a plan completes): close + harvest is the skill's `stop` procedure — converge, a user-approved harvest manifest, apply, then a uselessness test; conclusions disperse by scope (architecture docs, decision records, backlog), the record moves from `docs/research/{topic}-brainstorm/` to `docs/history/brainstorms/{topic}/`, and the user decides whether the moved folder is deleted or committed.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: bookkeeper
|
|
3
3
|
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, marks rows resolved. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on "drain the bookkeeping queue", "dispatch the bookkeeper", "/bookkeeper".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Bookkeeper Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: change-review
|
|
3
3
|
description: Velocity + change-risk analysis. First classifies recent commits into a pipe-delimited velocity/severity CSV by dispatching parallel low-cost subagents (module, feature, impact, severity, churn), then derives a commit heatmap, risk zones (high churn in contract-heavy code), stabilization signals, and team/bus-factor distribution. Self-contained — produces its own CSV, no separate git-history step. Use when planning where to invest in review focus or rules.
|
|
4
4
|
tools: Bash, Read, Write, Glob, Grep, Task
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Change-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lld-mode
|
|
3
3
|
description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
|
|
4
4
|
tools: Read, Glob, Grep, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
skills:
|
|
7
7
|
- documentation
|
|
8
8
|
---
|
|
@@ -33,6 +33,7 @@ This tells you what already exists, what's adjacent, how detailed the LLD should
|
|
|
33
33
|
4. **Current state** — Understand how the system works today in the area being specified
|
|
34
34
|
5. **Dependencies** — Map what this feature touches and what depends on it
|
|
35
35
|
6. **Ownership conflicts** — Compare the new feature's intended code paths against existing feature docs' `owns:` globs. Report overlaps (two docs claiming the same paths at equal specificity).
|
|
36
|
+
7. **Invalidation** — a different question from conflict, and the one that gets missed. A conflict is two docs claiming one glob; an invalidation is **one existing doc whose stated behavior this feature makes false** — it may own entirely different paths and still be wrong once this ships. For every doc whose territory this feature touches or whose described flow it changes, name the specific claim that stops being true.
|
|
36
37
|
|
|
37
38
|
## What You Return
|
|
38
39
|
|
|
@@ -44,6 +45,7 @@ Structure your findings as:
|
|
|
44
45
|
- **Current behavior**: How the relevant part of the system works today
|
|
45
46
|
- **Conventions**: Naming, structure, and patterns to follow based on existing code
|
|
46
47
|
- **Ownership conflicts**: Existing feature docs whose `owns:` globs overlap with the proposed feature. Include the conflicting paths and the owning doc name.
|
|
48
|
+
- **Invalidated claims**: One row per existing doc this feature makes wrong — doc path, the claim, why it stops being true. This list seeds the plan's **Doc Obligations** ledger, so it is the research finding most likely to survive into an artifact; "none" is a real answer and says the feature is genuinely additive.
|
|
47
49
|
- **Risks or concerns**: Anything the LLD should address or call out
|
|
48
50
|
|
|
49
51
|
Use the documentation skill's style guide and conventions when structuring your findings — imperative voice, ASCII diagrams for flows, function signatures over implementations, concise.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintenance
|
|
3
3
|
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maintenance Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: performance-review
|
|
3
3
|
description: Combined dependency + code health audit. Part A audits the dependency supply chain — security CVEs, outdated packages, license compliance, abandoned packages, unwanted transitive dependencies, install size. Part B audits code-level health — bundle size, tree-shaking issues, performance anti-patterns, code-quality flags. Run weekly or before releases. Merges the former `dep-health` and `review` agents so the package tree and the code that uses it are audited together.
|
|
4
4
|
tools: Read, Glob, Grep, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Performance-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: retro
|
|
3
3
|
description: Session-distillation agent for the changelog. Reads the per-unit-of-work entries under `docs/history/changelog/` accumulated since its last run and distills the refinement delta — what user-driven iteration added that the one-shot build lacked — into a typed punch list of doc-content gaps, doc-structure moves (missing doc / mothership split / merge), skill-evolution findings backed by survived-vs-overridden counts, unpinned discoveries needing an enforced artifact (test, token, guardrail, schema), and cross-project harness rules. Proposes only — actionable items land in the backlog, never applied directly. Run on demand: before a brainstorm or LLD session, before starting a new project, or after a refinement arc. Triggers on "run retro", "distill the changelog", "refinement retro", "/retro".
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Write, Edit
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Retro — changelog distillation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "apollo",
|
|
3
3
|
"name": "apollo",
|
|
4
|
-
"version": "1.1
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
"docs/memory/main.md",
|
|
16
16
|
".mcp.json"
|
|
17
17
|
],
|
|
18
|
-
"digest": "
|
|
18
|
+
"digest": "6c0123cfa9ee"
|
|
19
19
|
}
|
|
@@ -75,6 +75,7 @@ function openGatedPlans(projectDir) {
|
|
|
75
75
|
status,
|
|
76
76
|
gates,
|
|
77
77
|
hasLedger: /^##\s+Test Obligations\b/m.test(text),
|
|
78
|
+
hasDocLedger: /^##\s+Doc Obligations\b/m.test(text),
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
return plans;
|
|
@@ -110,6 +111,12 @@ function fileGate(projectDir, rel, toolName) {
|
|
|
110
111
|
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Test Obligations\` section. Run the \`testing\` skill (plan-gate mode); its ledger is what the verification gate discharges.`
|
|
111
112
|
);
|
|
112
113
|
}
|
|
114
|
+
if (!plan.hasDocLedger) {
|
|
115
|
+
return decision(
|
|
116
|
+
"deny",
|
|
117
|
+
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Doc Obligations\` section. Resolve the Proposed Changes paths against the ownership index and fill the table yourself — it is a lookup, not a skill run. "None" is a valid row when the plan touches only unowned paths.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -301,6 +308,15 @@ function plansCompletedThisSession(projectDir, sessionId) {
|
|
|
301
308
|
return out;
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
// A completed plan whose Doc Obligations ledger was never discharged. Discharge is a judgment the
|
|
312
|
+
// hook cannot make — whether a doc was actually updated well is a human read — so what it checks is
|
|
313
|
+
// the one mechanical trace the act leaves: the Done Criteria checkbox that says every row was
|
|
314
|
+
// examined. Presence of the section is gated at the implementation-edit wall; this is the other end.
|
|
315
|
+
function docLedgerUndischarged(text) {
|
|
316
|
+
if (!/^##\s+Doc Obligations\b/m.test(text)) return false;
|
|
317
|
+
return /^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(text);
|
|
318
|
+
}
|
|
319
|
+
|
|
304
320
|
// Whether some changelog entry already accounts for this plan.
|
|
305
321
|
//
|
|
306
322
|
// COMMITTED COUNTS. The test used to be `git status --porcelain docs/history/changelog/` — i.e. "is
|
|
@@ -325,6 +341,43 @@ function planHasChangelogEntry(projectDir, planRel) {
|
|
|
325
341
|
return names.some((name) => name.endsWith(".md") && readSafe(path.join(dir, name)).includes(slug));
|
|
326
342
|
}
|
|
327
343
|
|
|
344
|
+
// Approximate the context the next turn will re-read, from the transcript's last recorded usage.
|
|
345
|
+
// Best-effort: an absent or unparseable transcript reports nothing.
|
|
346
|
+
function sessionContextTokens(transcriptPath) {
|
|
347
|
+
const text = readSafe(transcriptPath || "");
|
|
348
|
+
if (!text) return 0;
|
|
349
|
+
const lines = text.slice(-400000).split("\n");
|
|
350
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
351
|
+
if (!lines[i].includes('"usage"')) continue;
|
|
352
|
+
try {
|
|
353
|
+
const u = JSON.parse(lines[i]).message.usage;
|
|
354
|
+
return (
|
|
355
|
+
(u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0)
|
|
356
|
+
);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// the slice cut this line in half — keep scanning upward
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Context re-reads in full on every turn, so its size IS the standing per-turn floor — the one
|
|
365
|
+
// number that argues for ending a session rather than carrying it into unrelated work. The agent
|
|
366
|
+
// cannot act on this (it can neither end a session nor start one), so it is addressed to the user
|
|
367
|
+
// as a systemMessage and never blocks. Kept out of the model's context deliberately: a running
|
|
368
|
+
// token count in-context produces context anxiety, not shorter sessions.
|
|
369
|
+
const CONTEXT_ADVISORY_TOKENS = 250000;
|
|
370
|
+
|
|
371
|
+
function sessionSizeNote(payload) {
|
|
372
|
+
const tokens = sessionContextTokens(payload.transcript_path);
|
|
373
|
+
if (tokens < CONTEXT_ADVISORY_TOKENS) return null;
|
|
374
|
+
return (
|
|
375
|
+
`Session context is roughly ${Math.round(tokens / 1000)}K tokens, and every turn re-reads all of it — ` +
|
|
376
|
+
"that is the per-turn floor before anything new is produced. A fresh session starts near zero; " +
|
|
377
|
+
"worth ending this one if the next task is unrelated."
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
328
381
|
function stopGate(projectDir, payload) {
|
|
329
382
|
if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
|
|
330
383
|
|
|
@@ -344,9 +397,19 @@ function stopGate(projectDir, payload) {
|
|
|
344
397
|
};
|
|
345
398
|
}
|
|
346
399
|
|
|
347
|
-
const
|
|
348
|
-
|
|
400
|
+
const completed = plansCompletedThisSession(projectDir, payload.session_id);
|
|
401
|
+
|
|
402
|
+
const docsOpen = completed.filter((rel) =>
|
|
403
|
+
docLedgerUndischarged(readSafe(path.join(projectDir, rel)))
|
|
349
404
|
);
|
|
405
|
+
if (docsOpen.length > 0) {
|
|
406
|
+
return {
|
|
407
|
+
decision: "block",
|
|
408
|
+
reason: `Plan \`${docsOpen[0]}\` reached Status: Complete with its Doc Obligations rows undischarged. Walk each row — update the doc, or mark it owes nothing with the reason, or send it to the backlog as blocked — then tick the Done Criteria line. The feature docs are the only artifact the next session inherits.`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const undocumented = completed.filter((rel) => !planHasChangelogEntry(projectDir, rel));
|
|
350
413
|
if (undocumented.length > 0) {
|
|
351
414
|
return {
|
|
352
415
|
decision: "block",
|
|
@@ -381,6 +444,11 @@ function main() {
|
|
|
381
444
|
}
|
|
382
445
|
} else if (event === "Stop") {
|
|
383
446
|
out = stopGate(projectDir, payload);
|
|
447
|
+
// Advisory rides alongside a block rather than replacing it — both are true at once.
|
|
448
|
+
if (!payload.stop_hook_active) {
|
|
449
|
+
const note = sessionSizeNote(payload);
|
|
450
|
+
if (note) out = { ...(out || {}), systemMessage: note };
|
|
451
|
+
}
|
|
384
452
|
}
|
|
385
453
|
|
|
386
454
|
if (out) process.stdout.write(JSON.stringify(out));
|
|
@@ -7,7 +7,7 @@ Naming and the fixed column vocabulary for relational schemas in **every** Nurix
|
|
|
7
7
|
Three column roles are fixed across every table and every project:
|
|
8
8
|
|
|
9
9
|
- **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
|
|
10
|
-
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping
|
|
10
|
+
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping — copying between scopes is the `schema-design` skill. ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
|
|
11
11
|
- **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
|
|
12
12
|
|
|
13
13
|
Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
|
|
@@ -21,7 +21,7 @@ Reference columns name what they point at: `<entity>_id` for a UUID-PK reference
|
|
|
21
21
|
## Naming
|
|
22
22
|
|
|
23
23
|
- **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
|
|
24
|
-
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N
|
|
24
|
+
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N — the cardinality test that decides junction vs. column is the `schema-design` skill.
|
|
25
25
|
- **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
|
|
26
26
|
- **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
|
|
27
27
|
- **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
|
|
@@ -57,8 +57,12 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
|
|
|
57
57
|
|
|
58
58
|
## Suggested plan amendments
|
|
59
59
|
## Missing plan subsections
|
|
60
|
+
## Docs the plan's ledger missed
|
|
61
|
+
| Feature doc | Owned path in the touch list | In Doc Obligations? |
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
**The doc cross-check is free and you are the only one who can make it.** You already hold the touch list; resolving those paths against the ownership index and comparing the result to the plan's **Doc Obligations** rows costs one lookup. The plan's author wrote that ledger, so it is the one gate artifact nobody independently checked — a `None` that contradicts your touch list is the failure this section exists to catch. Report the gap; never edit the ledger yourself.
|
|
65
|
+
|
|
62
66
|
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
|
|
63
67
|
|
|
64
68
|
6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps these exact forms — implementation edits stay blocked while the segment reads `pending`. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
|
|
@@ -4,14 +4,16 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
4
4
|
|
|
5
5
|
The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
8
|
+
|
|
9
|
+
The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. The author writes `Docs: mapped YYYY-MM-DD (N docs)` when the Doc Obligations table is filled. Never hand-edit the line to skip a gate.
|
|
8
10
|
|
|
9
11
|
```
|
|
10
12
|
# {Feature Name}
|
|
11
13
|
|
|
12
14
|
**Status:** Draft | In Progress | Complete | Abandoned
|
|
13
|
-
**Gates:** Boundary: pending · Test-plan: pending
|
|
14
|
-
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
|
|
15
|
+
**Gates:** Boundary: pending · Test-plan: pending · Docs: pending
|
|
16
|
+
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
|
|
15
17
|
**Started:** YYYY-MM-DD
|
|
16
18
|
**Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
|
|
17
19
|
|
|
@@ -44,6 +46,18 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
44
46
|
- List which cross-cutting contracts this plan affects (db schema, wire envelope, auth, events, env/config, …)
|
|
45
47
|
- "None" is a valid answer and means the `boundary` check will pass cleanly
|
|
46
48
|
|
|
49
|
+
## Doc Obligations
|
|
50
|
+
|
|
51
|
+
<!-- One row per feature doc whose `owns:` globs intersect the Proposed Changes above — resolve
|
|
52
|
+
them against the injected ownership index. Hand-authored: this is a lookup, not research.
|
|
53
|
+
Amendable while the plan is open; discharged at Completion. -->
|
|
54
|
+
|
|
55
|
+
| Doc | Claim that changes | `owns:` still accurate? |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `docs/features/{feature}.md` | what it says today that this plan makes false | yes / no — new paths to add |
|
|
58
|
+
|
|
59
|
+
**"None" is valid** — a plan touching only unowned paths owes no doc update, and says so explicitly. It is not a free pass: `boundary` builds its own touch list from the same Proposed Changes and reports docs this table missed.
|
|
60
|
+
|
|
47
61
|
## Implementation Steps
|
|
48
62
|
|
|
49
63
|
1. Concrete, ordered, testable
|
|
@@ -60,7 +74,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
60
74
|
|
|
61
75
|
- [ ] Implementation complete
|
|
62
76
|
- [ ] Verification gate passed — every Test Obligations row discharged (satisfied / owes nothing / blocked → backlog), gate run date and results noted here
|
|
63
|
-
- [ ]
|
|
77
|
+
- [ ] Every Doc Obligations row discharged — **updated** / **owes nothing** (with the reason) / **blocked** (→ backlog item, so the debt outlives the frozen plan)
|
|
64
78
|
- [ ] Changelog entry created in `docs/history/changelog/`, carrying the gate's results
|
|
65
79
|
- [ ] Plan marked `Status: Complete`, **Completed:** date filled
|
|
66
80
|
|
|
@@ -85,22 +85,6 @@ One entity that exists at several scopes is **one table with a nullable scope co
|
|
|
85
85
|
- **Store the source's version at copy time** — `version` + `source_version` make "is an upgrade available" a comparison of two stored values, not a content diff.
|
|
86
86
|
- **A copy is a row, never a live read.** Nothing at the child scope reads the parent's row at runtime; propagation happens only through an explicit re-copy, so a local edit is never silently overwritten.
|
|
87
87
|
|
|
88
|
-
## Keys & the shared vocabulary
|
|
89
|
-
|
|
90
|
-
Three column roles are fixed across every table and every project:
|
|
91
|
-
|
|
92
|
-
- **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
|
|
93
|
-
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping (§ Copying rows between scopes). ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
|
|
94
|
-
- **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
|
|
95
|
-
|
|
96
|
-
Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
|
|
97
|
-
|
|
98
|
-
- **Enforced, same scope** (the referenced row must exist) → a real `FOREIGN KEY (<x>_id) REFERENCES <table>(id)`.
|
|
99
|
-
- **Cross-scope** (the reference must survive being copied) → `<x>_slug` as a plain column, **no** FK — an FK would bind the row to one scope's copy of the target, which is the remapping the slug exists to avoid.
|
|
100
|
-
- **Drift-tolerant** (a controlled-vocabulary value that may fall off-list) → a plain value column, **no** FK, paired with an `is_*` boolean recording whether the value is in the canonical set.
|
|
101
|
-
|
|
102
|
-
**Every inbound FK to a hub table declares a deliberate `ON DELETE`.** A hub (`users`, `devices`, anything several modules point at) collects edges from teams that never meet, and a single `NO ACTION` among them turns deleting the parent into a constraint error naming neither table. Decide per edge: the child dies with the row (`CASCADE`) or survives it (`SET NULL`, which needs a nullable column) — `NO ACTION` on a hub is a deferred outage, not a default.
|
|
103
|
-
|
|
104
88
|
## A junction spells many-to-many — nothing else
|
|
105
89
|
|
|
106
90
|
**A `_map` table is the schema's notation for M:N. Using it for 1:M is a category error** — the junction's own uniqueness admits many parents per child, so the "one parent" rule stops being enforced the moment it is written this way. Cardinality decides between the only two shapes:
|
|
@@ -131,14 +115,6 @@ Reference columns name what they point at: `<entity>_id` for a UUID-PK reference
|
|
|
131
115
|
- **`CASCADE` from the append-only side's own parent is still fine** — a cascade is a DELETE, so a `BEFORE UPDATE` trigger never sees it: an App's log dying with the App is legitimate, distinct from erasing a log to delete a person.
|
|
132
116
|
- **State the exemption where it exists.** A catalog whose rows must not disappear under a referencing row (a service definition, a vocabulary) keeps `NO ACTION` deliberately — that guarantee is worth one line saying so.
|
|
133
117
|
|
|
134
|
-
## Naming
|
|
135
|
-
|
|
136
|
-
- **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
|
|
137
|
-
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N (§ A junction spells many-to-many).
|
|
138
|
-
- **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
|
|
139
|
-
- **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
|
|
140
|
-
- **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
|
|
141
|
-
|
|
142
118
|
## Uniqueness with a nullable column
|
|
143
119
|
|
|
144
120
|
**`UNIQUE (a, b)` does not constrain rows where `b` is `NULL`** — SQL treats every `NULL` as distinct, so a composite unique over a nullable discriminator silently permits exactly the duplicates it was introduced to keep apart. Write one partial index per scope instead:
|
|
@@ -150,12 +126,6 @@ CREATE UNIQUE INDEX <t>_child_slug_unique ON <t> (child_id, slug) WHERE child_
|
|
|
150
126
|
|
|
151
127
|
**Prove a uniqueness claim by inserting the duplicate it forbids** — a schema review cannot tell a firing index from a silent one by reading.
|
|
152
128
|
|
|
153
|
-
## Column types (SQLite / D1)
|
|
154
|
-
|
|
155
|
-
- **Timestamps** — `created_at` + `updated_at`, both `TEXT NOT NULL DEFAULT (datetime('now'))`, on **every** table — except append-only logs, which carry only `created_at`.
|
|
156
|
-
- **Booleans** — `INTEGER` (Drizzle `integer(…, { mode: "boolean" })`); the `is_*` / `has_*` names are § Naming.
|
|
157
|
-
- **JSON** — `TEXT` (Drizzle `text(…, { mode: "json" })`).
|
|
158
|
-
|
|
159
129
|
## Vocabulary / lookup tables
|
|
160
130
|
|
|
161
131
|
A controlled vocabulary earns a **table** only when a user row FKs into it and reads its `label` / `description` at runtime — a service catalog rendered in a picker; static dev-authored config nobody references by FK stays **config** (§ What earns a table) — a `doc_type` value list. Enumerable is not tabular, and a `type`-style column validated against the constants file beats an FK to a seeded mirror. An earned table has a fixed shape: `id` (UUID) · `slug` (UNIQUE) · `label` · optional `description` · `status` · timestamps.
|
|
@@ -37,7 +37,7 @@ Three peer modes, then a verification gate and a Completion step — enter where
|
|
|
37
37
|
|
|
38
38
|
1. **The verification gate passes first** — user-triggered: when implementation completes, report it and offer the gate; never run it unbidden. It requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row, driving the real surface before pinning tests — `.claude/rules/testing.md`. A plan with outstanding obligations stays open, whatever the code does.
|
|
39
39
|
2. Freeze the plan — `Status: Complete`, fill the Completed date. The file is now frozen.
|
|
40
|
-
3.
|
|
40
|
+
3. **Discharge the plan's Doc Obligations ledger — every row, into exactly one outcome**: **updated** (the doc now states what the code does), **owes nothing** (with the reason), or **blocked** (→ a backlog item, so the debt outlives the frozen plan). For each row's doc: flip `status: active` if it was `draft`, extend it in place with the how-it-works sections, and confirm its `owns:` globs against the actual code. A plan spanning three features discharges three rows — the ledger, not memory, says how many.
|
|
41
41
|
4. Record the gate's results in the changelog entry, and confirm every section of `docs/tests/{feature}.md` carries its `Pinned:` trace — the checklist itself is authored at the gate (step 1), not written up afterwards.
|
|
42
42
|
|
|
43
43
|
**On Brainstorm Close** (when a session ends, not when a plan completes): close + harvest is the skill's `stop` procedure — converge, a user-approved harvest manifest, apply, then a uselessness test; conclusions disperse by scope (architecture docs, decision records, backlog), the record moves from `docs/research/{topic}-brainstorm/` to `docs/history/brainstorms/{topic}/`, and the user decides whether the moved folder is deleted or committed.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: bookkeeper
|
|
3
3
|
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, marks rows resolved. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on "drain the bookkeeping queue", "dispatch the bookkeeper", "/bookkeeper".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Bookkeeper Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: change-review
|
|
3
3
|
description: Velocity + change-risk analysis. First classifies recent commits into a pipe-delimited velocity/severity CSV by dispatching parallel low-cost subagents (module, feature, impact, severity, churn), then derives a commit heatmap, risk zones (high churn in contract-heavy code), stabilization signals, and team/bus-factor distribution. Self-contained — produces its own CSV, no separate git-history step. Use when planning where to invest in review focus or rules.
|
|
4
4
|
tools: Bash, Read, Write, Glob, Grep, Task
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Change-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lld-mode
|
|
3
3
|
description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
|
|
4
4
|
tools: Read, Glob, Grep, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
skills:
|
|
7
7
|
- documentation
|
|
8
8
|
---
|
|
@@ -33,6 +33,7 @@ This tells you what already exists, what's adjacent, how detailed the LLD should
|
|
|
33
33
|
4. **Current state** — Understand how the system works today in the area being specified
|
|
34
34
|
5. **Dependencies** — Map what this feature touches and what depends on it
|
|
35
35
|
6. **Ownership conflicts** — Compare the new feature's intended code paths against existing feature docs' `owns:` globs. Report overlaps (two docs claiming the same paths at equal specificity).
|
|
36
|
+
7. **Invalidation** — a different question from conflict, and the one that gets missed. A conflict is two docs claiming one glob; an invalidation is **one existing doc whose stated behavior this feature makes false** — it may own entirely different paths and still be wrong once this ships. For every doc whose territory this feature touches or whose described flow it changes, name the specific claim that stops being true.
|
|
36
37
|
|
|
37
38
|
## What You Return
|
|
38
39
|
|
|
@@ -44,6 +45,7 @@ Structure your findings as:
|
|
|
44
45
|
- **Current behavior**: How the relevant part of the system works today
|
|
45
46
|
- **Conventions**: Naming, structure, and patterns to follow based on existing code
|
|
46
47
|
- **Ownership conflicts**: Existing feature docs whose `owns:` globs overlap with the proposed feature. Include the conflicting paths and the owning doc name.
|
|
48
|
+
- **Invalidated claims**: One row per existing doc this feature makes wrong — doc path, the claim, why it stops being true. This list seeds the plan's **Doc Obligations** ledger, so it is the research finding most likely to survive into an artifact; "none" is a real answer and says the feature is genuinely additive.
|
|
47
49
|
- **Risks or concerns**: Anything the LLD should address or call out
|
|
48
50
|
|
|
49
51
|
Use the documentation skill's style guide and conventions when structuring your findings — imperative voice, ASCII diagrams for flows, function signatures over implementations, concise.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintenance
|
|
3
3
|
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maintenance Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: performance-review
|
|
3
3
|
description: Combined dependency + code health audit. Part A audits the dependency supply chain — security CVEs, outdated packages, license compliance, abandoned packages, unwanted transitive dependencies, install size. Part B audits code-level health — bundle size, tree-shaking issues, performance anti-patterns, code-quality flags. Run weekly or before releases. Merges the former `dep-health` and `review` agents so the package tree and the code that uses it are audited together.
|
|
4
4
|
tools: Read, Glob, Grep, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Performance-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: retro
|
|
3
3
|
description: Session-distillation agent for the changelog. Reads the per-unit-of-work entries under `docs/history/changelog/` accumulated since its last run and distills the refinement delta — what user-driven iteration added that the one-shot build lacked — into a typed punch list of doc-content gaps, doc-structure moves (missing doc / mothership split / merge), skill-evolution findings backed by survived-vs-overridden counts, unpinned discoveries needing an enforced artifact (test, token, guardrail, schema), and cross-project harness rules. Proposes only — actionable items land in the backlog, never applied directly. Run on demand: before a brainstorm or LLD session, before starting a new project, or after a refinement arc. Triggers on "run retro", "distill the changelog", "refinement retro", "/retro".
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Write, Edit
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Retro — changelog distillation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.7.1",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [
|
|
11
11
|
".mcp.json"
|
|
12
12
|
],
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "ecbd84f05b8e"
|
|
14
14
|
}
|
|
@@ -75,6 +75,7 @@ function openGatedPlans(projectDir) {
|
|
|
75
75
|
status,
|
|
76
76
|
gates,
|
|
77
77
|
hasLedger: /^##\s+Test Obligations\b/m.test(text),
|
|
78
|
+
hasDocLedger: /^##\s+Doc Obligations\b/m.test(text),
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
return plans;
|
|
@@ -110,6 +111,12 @@ function fileGate(projectDir, rel, toolName) {
|
|
|
110
111
|
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Test Obligations\` section. Run the \`testing\` skill (plan-gate mode); its ledger is what the verification gate discharges.`
|
|
111
112
|
);
|
|
112
113
|
}
|
|
114
|
+
if (!plan.hasDocLedger) {
|
|
115
|
+
return decision(
|
|
116
|
+
"deny",
|
|
117
|
+
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Doc Obligations\` section. Resolve the Proposed Changes paths against the ownership index and fill the table yourself — it is a lookup, not a skill run. "None" is a valid row when the plan touches only unowned paths.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -301,6 +308,15 @@ function plansCompletedThisSession(projectDir, sessionId) {
|
|
|
301
308
|
return out;
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
// A completed plan whose Doc Obligations ledger was never discharged. Discharge is a judgment the
|
|
312
|
+
// hook cannot make — whether a doc was actually updated well is a human read — so what it checks is
|
|
313
|
+
// the one mechanical trace the act leaves: the Done Criteria checkbox that says every row was
|
|
314
|
+
// examined. Presence of the section is gated at the implementation-edit wall; this is the other end.
|
|
315
|
+
function docLedgerUndischarged(text) {
|
|
316
|
+
if (!/^##\s+Doc Obligations\b/m.test(text)) return false;
|
|
317
|
+
return /^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(text);
|
|
318
|
+
}
|
|
319
|
+
|
|
304
320
|
// Whether some changelog entry already accounts for this plan.
|
|
305
321
|
//
|
|
306
322
|
// COMMITTED COUNTS. The test used to be `git status --porcelain docs/history/changelog/` — i.e. "is
|
|
@@ -325,6 +341,43 @@ function planHasChangelogEntry(projectDir, planRel) {
|
|
|
325
341
|
return names.some((name) => name.endsWith(".md") && readSafe(path.join(dir, name)).includes(slug));
|
|
326
342
|
}
|
|
327
343
|
|
|
344
|
+
// Approximate the context the next turn will re-read, from the transcript's last recorded usage.
|
|
345
|
+
// Best-effort: an absent or unparseable transcript reports nothing.
|
|
346
|
+
function sessionContextTokens(transcriptPath) {
|
|
347
|
+
const text = readSafe(transcriptPath || "");
|
|
348
|
+
if (!text) return 0;
|
|
349
|
+
const lines = text.slice(-400000).split("\n");
|
|
350
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
351
|
+
if (!lines[i].includes('"usage"')) continue;
|
|
352
|
+
try {
|
|
353
|
+
const u = JSON.parse(lines[i]).message.usage;
|
|
354
|
+
return (
|
|
355
|
+
(u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0)
|
|
356
|
+
);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// the slice cut this line in half — keep scanning upward
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Context re-reads in full on every turn, so its size IS the standing per-turn floor — the one
|
|
365
|
+
// number that argues for ending a session rather than carrying it into unrelated work. The agent
|
|
366
|
+
// cannot act on this (it can neither end a session nor start one), so it is addressed to the user
|
|
367
|
+
// as a systemMessage and never blocks. Kept out of the model's context deliberately: a running
|
|
368
|
+
// token count in-context produces context anxiety, not shorter sessions.
|
|
369
|
+
const CONTEXT_ADVISORY_TOKENS = 250000;
|
|
370
|
+
|
|
371
|
+
function sessionSizeNote(payload) {
|
|
372
|
+
const tokens = sessionContextTokens(payload.transcript_path);
|
|
373
|
+
if (tokens < CONTEXT_ADVISORY_TOKENS) return null;
|
|
374
|
+
return (
|
|
375
|
+
`Session context is roughly ${Math.round(tokens / 1000)}K tokens, and every turn re-reads all of it — ` +
|
|
376
|
+
"that is the per-turn floor before anything new is produced. A fresh session starts near zero; " +
|
|
377
|
+
"worth ending this one if the next task is unrelated."
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
328
381
|
function stopGate(projectDir, payload) {
|
|
329
382
|
if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
|
|
330
383
|
|
|
@@ -344,9 +397,19 @@ function stopGate(projectDir, payload) {
|
|
|
344
397
|
};
|
|
345
398
|
}
|
|
346
399
|
|
|
347
|
-
const
|
|
348
|
-
|
|
400
|
+
const completed = plansCompletedThisSession(projectDir, payload.session_id);
|
|
401
|
+
|
|
402
|
+
const docsOpen = completed.filter((rel) =>
|
|
403
|
+
docLedgerUndischarged(readSafe(path.join(projectDir, rel)))
|
|
349
404
|
);
|
|
405
|
+
if (docsOpen.length > 0) {
|
|
406
|
+
return {
|
|
407
|
+
decision: "block",
|
|
408
|
+
reason: `Plan \`${docsOpen[0]}\` reached Status: Complete with its Doc Obligations rows undischarged. Walk each row — update the doc, or mark it owes nothing with the reason, or send it to the backlog as blocked — then tick the Done Criteria line. The feature docs are the only artifact the next session inherits.`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const undocumented = completed.filter((rel) => !planHasChangelogEntry(projectDir, rel));
|
|
350
413
|
if (undocumented.length > 0) {
|
|
351
414
|
return {
|
|
352
415
|
decision: "block",
|
|
@@ -381,6 +444,11 @@ function main() {
|
|
|
381
444
|
}
|
|
382
445
|
} else if (event === "Stop") {
|
|
383
446
|
out = stopGate(projectDir, payload);
|
|
447
|
+
// Advisory rides alongside a block rather than replacing it — both are true at once.
|
|
448
|
+
if (!payload.stop_hook_active) {
|
|
449
|
+
const note = sessionSizeNote(payload);
|
|
450
|
+
if (note) out = { ...(out || {}), systemMessage: note };
|
|
451
|
+
}
|
|
384
452
|
}
|
|
385
453
|
|
|
386
454
|
if (out) process.stdout.write(JSON.stringify(out));
|
|
@@ -7,7 +7,7 @@ Naming and the fixed column vocabulary for relational schemas in **every** Nurix
|
|
|
7
7
|
Three column roles are fixed across every table and every project:
|
|
8
8
|
|
|
9
9
|
- **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
|
|
10
|
-
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping
|
|
10
|
+
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping — copying between scopes is the `schema-design` skill. ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
|
|
11
11
|
- **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
|
|
12
12
|
|
|
13
13
|
Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
|
|
@@ -21,7 +21,7 @@ Reference columns name what they point at: `<entity>_id` for a UUID-PK reference
|
|
|
21
21
|
## Naming
|
|
22
22
|
|
|
23
23
|
- **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
|
|
24
|
-
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N
|
|
24
|
+
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N — the cardinality test that decides junction vs. column is the `schema-design` skill.
|
|
25
25
|
- **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
|
|
26
26
|
- **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
|
|
27
27
|
- **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
|
|
@@ -57,8 +57,12 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
|
|
|
57
57
|
|
|
58
58
|
## Suggested plan amendments
|
|
59
59
|
## Missing plan subsections
|
|
60
|
+
## Docs the plan's ledger missed
|
|
61
|
+
| Feature doc | Owned path in the touch list | In Doc Obligations? |
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
**The doc cross-check is free and you are the only one who can make it.** You already hold the touch list; resolving those paths against the ownership index and comparing the result to the plan's **Doc Obligations** rows costs one lookup. The plan's author wrote that ledger, so it is the one gate artifact nobody independently checked — a `None` that contradicts your touch list is the failure this section exists to catch. Report the gap; never edit the ledger yourself.
|
|
65
|
+
|
|
62
66
|
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
|
|
63
67
|
|
|
64
68
|
6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps these exact forms — implementation edits stay blocked while the segment reads `pending`. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
|
|
@@ -4,14 +4,16 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
4
4
|
|
|
5
5
|
The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
8
|
+
|
|
9
|
+
The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. The author writes `Docs: mapped YYYY-MM-DD (N docs)` when the Doc Obligations table is filled. Never hand-edit the line to skip a gate.
|
|
8
10
|
|
|
9
11
|
```
|
|
10
12
|
# {Feature Name}
|
|
11
13
|
|
|
12
14
|
**Status:** Draft | In Progress | Complete | Abandoned
|
|
13
|
-
**Gates:** Boundary: pending · Test-plan: pending
|
|
14
|
-
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
|
|
15
|
+
**Gates:** Boundary: pending · Test-plan: pending · Docs: pending
|
|
16
|
+
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
|
|
15
17
|
**Started:** YYYY-MM-DD
|
|
16
18
|
**Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
|
|
17
19
|
|
|
@@ -44,6 +46,18 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
44
46
|
- List which cross-cutting contracts this plan affects (db schema, wire envelope, auth, events, env/config, …)
|
|
45
47
|
- "None" is a valid answer and means the `boundary` check will pass cleanly
|
|
46
48
|
|
|
49
|
+
## Doc Obligations
|
|
50
|
+
|
|
51
|
+
<!-- One row per feature doc whose `owns:` globs intersect the Proposed Changes above — resolve
|
|
52
|
+
them against the injected ownership index. Hand-authored: this is a lookup, not research.
|
|
53
|
+
Amendable while the plan is open; discharged at Completion. -->
|
|
54
|
+
|
|
55
|
+
| Doc | Claim that changes | `owns:` still accurate? |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `docs/features/{feature}.md` | what it says today that this plan makes false | yes / no — new paths to add |
|
|
58
|
+
|
|
59
|
+
**"None" is valid** — a plan touching only unowned paths owes no doc update, and says so explicitly. It is not a free pass: `boundary` builds its own touch list from the same Proposed Changes and reports docs this table missed.
|
|
60
|
+
|
|
47
61
|
## Implementation Steps
|
|
48
62
|
|
|
49
63
|
1. Concrete, ordered, testable
|
|
@@ -60,7 +74,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
60
74
|
|
|
61
75
|
- [ ] Implementation complete
|
|
62
76
|
- [ ] Verification gate passed — every Test Obligations row discharged (satisfied / owes nothing / blocked → backlog), gate run date and results noted here
|
|
63
|
-
- [ ]
|
|
77
|
+
- [ ] Every Doc Obligations row discharged — **updated** / **owes nothing** (with the reason) / **blocked** (→ backlog item, so the debt outlives the frozen plan)
|
|
64
78
|
- [ ] Changelog entry created in `docs/history/changelog/`, carrying the gate's results
|
|
65
79
|
- [ ] Plan marked `Status: Complete`, **Completed:** date filled
|
|
66
80
|
|
|
@@ -85,22 +85,6 @@ One entity that exists at several scopes is **one table with a nullable scope co
|
|
|
85
85
|
- **Store the source's version at copy time** — `version` + `source_version` make "is an upgrade available" a comparison of two stored values, not a content diff.
|
|
86
86
|
- **A copy is a row, never a live read.** Nothing at the child scope reads the parent's row at runtime; propagation happens only through an explicit re-copy, so a local edit is never silently overwritten.
|
|
87
87
|
|
|
88
|
-
## Keys & the shared vocabulary
|
|
89
|
-
|
|
90
|
-
Three column roles are fixed across every table and every project:
|
|
91
|
-
|
|
92
|
-
- **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
|
|
93
|
-
- **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping (§ Copying rows between scopes). ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
|
|
94
|
-
- **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
|
|
95
|
-
|
|
96
|
-
Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
|
|
97
|
-
|
|
98
|
-
- **Enforced, same scope** (the referenced row must exist) → a real `FOREIGN KEY (<x>_id) REFERENCES <table>(id)`.
|
|
99
|
-
- **Cross-scope** (the reference must survive being copied) → `<x>_slug` as a plain column, **no** FK — an FK would bind the row to one scope's copy of the target, which is the remapping the slug exists to avoid.
|
|
100
|
-
- **Drift-tolerant** (a controlled-vocabulary value that may fall off-list) → a plain value column, **no** FK, paired with an `is_*` boolean recording whether the value is in the canonical set.
|
|
101
|
-
|
|
102
|
-
**Every inbound FK to a hub table declares a deliberate `ON DELETE`.** A hub (`users`, `devices`, anything several modules point at) collects edges from teams that never meet, and a single `NO ACTION` among them turns deleting the parent into a constraint error naming neither table. Decide per edge: the child dies with the row (`CASCADE`) or survives it (`SET NULL`, which needs a nullable column) — `NO ACTION` on a hub is a deferred outage, not a default.
|
|
103
|
-
|
|
104
88
|
## A junction spells many-to-many — nothing else
|
|
105
89
|
|
|
106
90
|
**A `_map` table is the schema's notation for M:N. Using it for 1:M is a category error** — the junction's own uniqueness admits many parents per child, so the "one parent" rule stops being enforced the moment it is written this way. Cardinality decides between the only two shapes:
|
|
@@ -131,14 +115,6 @@ Reference columns name what they point at: `<entity>_id` for a UUID-PK reference
|
|
|
131
115
|
- **`CASCADE` from the append-only side's own parent is still fine** — a cascade is a DELETE, so a `BEFORE UPDATE` trigger never sees it: an App's log dying with the App is legitimate, distinct from erasing a log to delete a person.
|
|
132
116
|
- **State the exemption where it exists.** A catalog whose rows must not disappear under a referencing row (a service definition, a vocabulary) keeps `NO ACTION` deliberately — that guarantee is worth one line saying so.
|
|
133
117
|
|
|
134
|
-
## Naming
|
|
135
|
-
|
|
136
|
-
- **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
|
|
137
|
-
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N (§ A junction spells many-to-many).
|
|
138
|
-
- **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
|
|
139
|
-
- **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
|
|
140
|
-
- **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
|
|
141
|
-
|
|
142
118
|
## Uniqueness with a nullable column
|
|
143
119
|
|
|
144
120
|
**`UNIQUE (a, b)` does not constrain rows where `b` is `NULL`** — SQL treats every `NULL` as distinct, so a composite unique over a nullable discriminator silently permits exactly the duplicates it was introduced to keep apart. Write one partial index per scope instead:
|
|
@@ -150,12 +126,6 @@ CREATE UNIQUE INDEX <t>_child_slug_unique ON <t> (child_id, slug) WHERE child_
|
|
|
150
126
|
|
|
151
127
|
**Prove a uniqueness claim by inserting the duplicate it forbids** — a schema review cannot tell a firing index from a silent one by reading.
|
|
152
128
|
|
|
153
|
-
## Column types (SQLite / D1)
|
|
154
|
-
|
|
155
|
-
- **Timestamps** — `created_at` + `updated_at`, both `TEXT NOT NULL DEFAULT (datetime('now'))`, on **every** table — except append-only logs, which carry only `created_at`.
|
|
156
|
-
- **Booleans** — `INTEGER` (Drizzle `integer(…, { mode: "boolean" })`); the `is_*` / `has_*` names are § Naming.
|
|
157
|
-
- **JSON** — `TEXT` (Drizzle `text(…, { mode: "json" })`).
|
|
158
|
-
|
|
159
129
|
## Vocabulary / lookup tables
|
|
160
130
|
|
|
161
131
|
A controlled vocabulary earns a **table** only when a user row FKs into it and reads its `label` / `description` at runtime — a service catalog rendered in a picker; static dev-authored config nobody references by FK stays **config** (§ What earns a table) — a `doc_type` value list. Enumerable is not tabular, and a `type`-style column validated against the constants file beats an FK to a seeded mirror. An earned table has a fixed shape: `id` (UUID) · `slug` (UNIQUE) · `label` · optional `description` · `status` · timestamps.
|
|
@@ -55,7 +55,7 @@ Three peer modes plus a Completion step — enter wherever the user's thinking a
|
|
|
55
55
|
| **LLD** (low-level design) | a new prompt family, task type, or eval suite | `lld-mode` agent + `documentation` skill | Plan |
|
|
56
56
|
| **Plan** | an LLD is approved | `documentation` skill → `docs/history/plans/YYYY-MM-DD-{slug}.md` | Completion |
|
|
57
57
|
|
|
58
|
-
**On Plan Completion:** freeze the plan (`Status: Complete`, Completed date);
|
|
58
|
+
**On Plan Completion:** freeze the plan (`Status: Complete`, Completed date); discharge every Doc Obligations row into **updated**, **owes nothing** (with the reason), or **blocked** (→ backlog) — for each row's doc, flip `status: active`, extend it in place, and confirm its `owns:` globs against the actual prompt files; write the changelog entry as a draft immediately.
|
|
59
59
|
|
|
60
60
|
**On Brainstorm Close:** the skill's `stop` procedure — converge, a user-approved harvest manifest, apply, uselessness test; the record moves to `docs/history/brainstorms/{topic}/`.
|
|
61
61
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lld-mode
|
|
3
3
|
description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
|
|
4
4
|
tools: Read, Glob, Grep, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
skills:
|
|
7
7
|
- documentation
|
|
8
8
|
---
|
|
@@ -33,6 +33,7 @@ This tells you what already exists, what's adjacent, how detailed the LLD should
|
|
|
33
33
|
4. **Current state** — Understand how the system works today in the area being specified
|
|
34
34
|
5. **Dependencies** — Map what this feature touches and what depends on it
|
|
35
35
|
6. **Ownership conflicts** — Compare the new feature's intended code paths against existing feature docs' `owns:` globs. Report overlaps (two docs claiming the same paths at equal specificity).
|
|
36
|
+
7. **Invalidation** — a different question from conflict, and the one that gets missed. A conflict is two docs claiming one glob; an invalidation is **one existing doc whose stated behavior this feature makes false** — it may own entirely different paths and still be wrong once this ships. For every doc whose territory this feature touches or whose described flow it changes, name the specific claim that stops being true.
|
|
36
37
|
|
|
37
38
|
## What You Return
|
|
38
39
|
|
|
@@ -44,6 +45,7 @@ Structure your findings as:
|
|
|
44
45
|
- **Current behavior**: How the relevant part of the system works today
|
|
45
46
|
- **Conventions**: Naming, structure, and patterns to follow based on existing code
|
|
46
47
|
- **Ownership conflicts**: Existing feature docs whose `owns:` globs overlap with the proposed feature. Include the conflicting paths and the owning doc name.
|
|
48
|
+
- **Invalidated claims**: One row per existing doc this feature makes wrong — doc path, the claim, why it stops being true. This list seeds the plan's **Doc Obligations** ledger, so it is the research finding most likely to survive into an artifact; "none" is a real answer and says the feature is genuinely additive.
|
|
47
49
|
- **Risks or concerns**: Anything the LLD should address or call out
|
|
48
50
|
|
|
49
51
|
Use the documentation skill's style guide and conventions when structuring your findings — imperative voice, ASCII diagrams for flows, function signatures over implementations, concise.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintenance
|
|
3
3
|
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maintenance Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: retro
|
|
3
3
|
description: Session-distillation agent for the changelog. Reads the per-unit-of-work entries under `docs/history/changelog/` accumulated since its last run and distills the refinement delta — what user-driven iteration added that the one-shot build lacked — into a typed punch list of doc-content gaps, doc-structure moves (missing doc / mothership split / merge), skill-evolution findings backed by survived-vs-overridden counts, unpinned discoveries needing an enforced artifact (test, token, guardrail, schema), and cross-project harness rules. Proposes only — actionable items land in the backlog, never applied directly. Run on demand: before a brainstorm or LLD session, before starting a new project, or after a refinement arc. Triggers on "run retro", "distill the changelog", "refinement retro", "/retro".
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Write, Edit
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Retro — changelog distillation
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"digest": "
|
|
11
|
+
"digest": "a2c8df3022bd"
|
|
12
12
|
}
|
|
@@ -4,14 +4,16 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
4
4
|
|
|
5
5
|
The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
8
|
+
|
|
9
|
+
The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. The author writes `Docs: mapped YYYY-MM-DD (N docs)` when the Doc Obligations table is filled. Never hand-edit the line to skip a gate.
|
|
8
10
|
|
|
9
11
|
```
|
|
10
12
|
# {Feature Name}
|
|
11
13
|
|
|
12
14
|
**Status:** Draft | In Progress | Complete | Abandoned
|
|
13
|
-
**Gates:** Boundary: pending · Test-plan: pending
|
|
14
|
-
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
|
|
15
|
+
**Gates:** Boundary: pending · Test-plan: pending · Docs: pending
|
|
16
|
+
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
|
|
15
17
|
**Started:** YYYY-MM-DD
|
|
16
18
|
**Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
|
|
17
19
|
|
|
@@ -44,6 +46,18 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
44
46
|
- List which cross-cutting contracts this plan affects (db schema, wire envelope, auth, events, env/config, …)
|
|
45
47
|
- "None" is a valid answer and means the `boundary` check will pass cleanly
|
|
46
48
|
|
|
49
|
+
## Doc Obligations
|
|
50
|
+
|
|
51
|
+
<!-- One row per feature doc whose `owns:` globs intersect the Proposed Changes above — resolve
|
|
52
|
+
them against the injected ownership index. Hand-authored: this is a lookup, not research.
|
|
53
|
+
Amendable while the plan is open; discharged at Completion. -->
|
|
54
|
+
|
|
55
|
+
| Doc | Claim that changes | `owns:` still accurate? |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `docs/features/{feature}.md` | what it says today that this plan makes false | yes / no — new paths to add |
|
|
58
|
+
|
|
59
|
+
**"None" is valid** — a plan touching only unowned paths owes no doc update, and says so explicitly. It is not a free pass: `boundary` builds its own touch list from the same Proposed Changes and reports docs this table missed.
|
|
60
|
+
|
|
47
61
|
## Implementation Steps
|
|
48
62
|
|
|
49
63
|
1. Concrete, ordered, testable
|
|
@@ -60,7 +74,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
60
74
|
|
|
61
75
|
- [ ] Implementation complete
|
|
62
76
|
- [ ] Verification gate passed — every Test Obligations row discharged (satisfied / owes nothing / blocked → backlog), gate run date and results noted here
|
|
63
|
-
- [ ]
|
|
77
|
+
- [ ] Every Doc Obligations row discharged — **updated** / **owes nothing** (with the reason) / **blocked** (→ backlog item, so the debt outlives the frozen plan)
|
|
64
78
|
- [ ] Changelog entry created in `docs/history/changelog/`, carrying the gate's results
|
|
65
79
|
- [ ] Plan marked `Status: Complete`, **Completed:** date filled
|
|
66
80
|
|
|
@@ -66,7 +66,7 @@ Three peer modes plus a Completion step — enter wherever the user's thinking a
|
|
|
66
66
|
**On Plan Completion:**
|
|
67
67
|
|
|
68
68
|
1. Freeze the plan — `Status: Complete`, fill the Completed date. The file is now frozen.
|
|
69
|
-
2.
|
|
69
|
+
2. **Discharge the plan's Doc Obligations ledger — every row, into exactly one outcome**: **updated**, **owes nothing** (with the reason), or **blocked** (→ backlog). For each row's doc: flip `status: active` if it was `draft`, extend it in place with the how-it-works sections, confirm its `owns:` globs against the actual components. The ledger, not memory, says how many docs this plan owes.
|
|
70
70
|
3. The `boundary` skill must already have gated this plan before its first implementation step — see Ownership, Boundaries & Documentation.
|
|
71
71
|
|
|
72
72
|
**On Brainstorm Close** (when a session ends, not when a plan completes): close + harvest is the skill's `stop` procedure — converge, a user-approved harvest manifest, apply, then a uselessness test; conclusions disperse by scope (architecture docs, decision records, backlog), the record moves from `docs/research/{topic}-brainstorm/` to `docs/history/brainstorms/{topic}/`, and the user decides whether the moved folder is deleted or committed.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: change-review
|
|
3
3
|
description: Velocity + change-risk analysis. First classifies recent commits into a pipe-delimited velocity/severity CSV by dispatching parallel low-cost subagents (module, feature, impact, severity, churn), then derives a commit heatmap, risk zones (high churn in contract-heavy code), stabilization signals, and team/bus-factor distribution. Self-contained — produces its own CSV, no separate git-history step. Use when planning where to invest in review focus or rules.
|
|
4
4
|
tools: Bash, Read, Write, Glob, Grep, Task
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Change-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lld-mode
|
|
3
3
|
description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
|
|
4
4
|
tools: Read, Glob, Grep, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
skills:
|
|
7
7
|
- documentation
|
|
8
8
|
---
|
|
@@ -33,6 +33,7 @@ This tells you what already exists, what's adjacent, how detailed the LLD should
|
|
|
33
33
|
4. **Current state** — Understand how the system works today in the area being specified
|
|
34
34
|
5. **Dependencies** — Map what this feature touches and what depends on it
|
|
35
35
|
6. **Ownership conflicts** — Compare the new feature's intended code paths against existing feature docs' `owns:` globs. Report overlaps (two docs claiming the same paths at equal specificity).
|
|
36
|
+
7. **Invalidation** — a different question from conflict, and the one that gets missed. A conflict is two docs claiming one glob; an invalidation is **one existing doc whose stated behavior this feature makes false** — it may own entirely different paths and still be wrong once this ships. For every doc whose territory this feature touches or whose described flow it changes, name the specific claim that stops being true.
|
|
36
37
|
|
|
37
38
|
## What You Return
|
|
38
39
|
|
|
@@ -44,6 +45,7 @@ Structure your findings as:
|
|
|
44
45
|
- **Current behavior**: How the relevant part of the system works today
|
|
45
46
|
- **Conventions**: Naming, structure, and patterns to follow based on existing code
|
|
46
47
|
- **Ownership conflicts**: Existing feature docs whose `owns:` globs overlap with the proposed feature. Include the conflicting paths and the owning doc name.
|
|
48
|
+
- **Invalidated claims**: One row per existing doc this feature makes wrong — doc path, the claim, why it stops being true. This list seeds the plan's **Doc Obligations** ledger, so it is the research finding most likely to survive into an artifact; "none" is a real answer and says the feature is genuinely additive.
|
|
47
49
|
- **Risks or concerns**: Anything the LLD should address or call out
|
|
48
50
|
|
|
49
51
|
Use the documentation skill's style guide and conventions when structuring your findings — imperative voice, ASCII diagrams for flows, function signatures over implementations, concise.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintenance
|
|
3
3
|
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maintenance Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: performance-review
|
|
3
3
|
description: Combined dependency + code health audit. Part A audits the dependency supply chain — security CVEs, outdated packages, license compliance, abandoned packages, unwanted transitive dependencies, install size. Part B audits code-level health — bundle size, tree-shaking issues, performance anti-patterns, code-quality flags. Run weekly or before releases. Merges the former `dep-health` and `review` agents so the package tree and the code that uses it are audited together.
|
|
4
4
|
tools: Read, Glob, Grep, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Performance-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: retro
|
|
3
3
|
description: Session-distillation agent for the changelog. Reads the per-unit-of-work entries under `docs/history/changelog/` accumulated since its last run and distills the refinement delta — what user-driven iteration added that the one-shot build lacked — into a typed punch list of doc-content gaps, doc-structure moves (missing doc / mothership split / merge), skill-evolution findings backed by survived-vs-overridden counts, unpinned discoveries needing an enforced artifact (test, token, guardrail, schema), and cross-project harness rules. Proposes only — actionable items land in the backlog, never applied directly. Run on demand: before a brainstorm or LLD session, before starting a new project, or after a refinement arc. Triggers on "run retro", "distill the changelog", "refinement retro", "/retro".
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Write, Edit
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Retro — changelog distillation
|
|
@@ -57,8 +57,12 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
|
|
|
57
57
|
|
|
58
58
|
## Suggested plan amendments
|
|
59
59
|
## Missing plan subsections
|
|
60
|
+
## Docs the plan's ledger missed
|
|
61
|
+
| Feature doc | Owned path in the touch list | In Doc Obligations? |
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
**The doc cross-check is free and you are the only one who can make it.** You already hold the touch list; resolving those paths against the ownership index and comparing the result to the plan's **Doc Obligations** rows costs one lookup. The plan's author wrote that ledger, so it is the one gate artifact nobody independently checked — a `None` that contradicts your touch list is the failure this section exists to catch. Report the gap; never edit the ledger yourself.
|
|
65
|
+
|
|
62
66
|
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
|
|
63
67
|
|
|
64
68
|
6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps these exact forms — implementation edits stay blocked while the segment reads `pending`. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
|
|
@@ -4,14 +4,16 @@ Record. Use for: every implementation plan. Plans are created here directly with
|
|
|
4
4
|
|
|
5
5
|
The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
8
|
+
|
|
9
|
+
The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. The author writes `Docs: mapped YYYY-MM-DD (N docs)` when the Doc Obligations table is filled. Never hand-edit the line to skip a gate.
|
|
8
10
|
|
|
9
11
|
```
|
|
10
12
|
# {Feature Name}
|
|
11
13
|
|
|
12
14
|
**Status:** Draft | In Progress | Complete | Abandoned
|
|
13
|
-
**Gates:** Boundary: pending · Test-plan: pending
|
|
14
|
-
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
|
|
15
|
+
**Gates:** Boundary: pending · Test-plan: pending · Docs: pending
|
|
16
|
+
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
|
|
15
17
|
**Started:** YYYY-MM-DD
|
|
16
18
|
**Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
|
|
17
19
|
|
|
@@ -44,6 +46,18 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
44
46
|
- List which cross-cutting contracts this plan affects (db schema, wire envelope, auth, events, env/config, …)
|
|
45
47
|
- "None" is a valid answer and means the `boundary` check will pass cleanly
|
|
46
48
|
|
|
49
|
+
## Doc Obligations
|
|
50
|
+
|
|
51
|
+
<!-- One row per feature doc whose `owns:` globs intersect the Proposed Changes above — resolve
|
|
52
|
+
them against the injected ownership index. Hand-authored: this is a lookup, not research.
|
|
53
|
+
Amendable while the plan is open; discharged at Completion. -->
|
|
54
|
+
|
|
55
|
+
| Doc | Claim that changes | `owns:` still accurate? |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `docs/features/{feature}.md` | what it says today that this plan makes false | yes / no — new paths to add |
|
|
58
|
+
|
|
59
|
+
**"None" is valid** — a plan touching only unowned paths owes no doc update, and says so explicitly. It is not a free pass: `boundary` builds its own touch list from the same Proposed Changes and reports docs this table missed.
|
|
60
|
+
|
|
47
61
|
## Implementation Steps
|
|
48
62
|
|
|
49
63
|
1. Concrete, ordered, testable
|
|
@@ -60,7 +74,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
|
|
|
60
74
|
|
|
61
75
|
- [ ] Implementation complete
|
|
62
76
|
- [ ] Verification gate passed — every Test Obligations row discharged (satisfied / owes nothing / blocked → backlog), gate run date and results noted here
|
|
63
|
-
- [ ]
|
|
77
|
+
- [ ] Every Doc Obligations row discharged — **updated** / **owes nothing** (with the reason) / **blocked** (→ backlog item, so the debt outlives the frozen plan)
|
|
64
78
|
- [ ] Changelog entry created in `docs/history/changelog/`, carrying the gate's results
|
|
65
79
|
- [ ] Plan marked `Status: Complete`, **Completed:** date filled
|
|
66
80
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.58",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "411f905588e3"
|
|
46
46
|
}
|