@esneiderbravo/speclaw 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli/commands/check.js +11 -1
  2. package/dist/cli/commands/cortex.js +17 -0
  3. package/dist/cli/commands/index-build.js +13 -2
  4. package/dist/cli/commands/lawbook.js +31 -10
  5. package/dist/cli/commands/query.js +13 -0
  6. package/dist/cli/index.js +4 -1
  7. package/dist/modules/compass/indexer.js +14 -0
  8. package/dist/modules/compass/query.js +209 -62
  9. package/dist/modules/compass/register.js +17 -1
  10. package/dist/modules/cortex/compass-gate.js +84 -0
  11. package/dist/modules/cortex/harness.js +20 -11
  12. package/dist/modules/cortex/types.js +14 -0
  13. package/dist/modules/foundation/assets/AGENTS.template.md +2 -2
  14. package/dist/modules/foundation/assets/CLAUDE.template.md +4 -3
  15. package/dist/modules/foundation/assets/docs/compass.template.md +7 -7
  16. package/dist/modules/foundation/assets/docs/standards/lawbook.template.md +2 -2
  17. package/dist/modules/foundation/check.js +23 -5
  18. package/dist/modules/foundation/compass-nudge.js +189 -0
  19. package/dist/modules/foundation/hooks.js +18 -3
  20. package/dist/modules/lawbook/assets/agents/archiver.md +1 -1
  21. package/dist/modules/lawbook/assets/agents/explorer.md +7 -4
  22. package/dist/modules/lawbook/assets/agents/implementer.md +1 -1
  23. package/dist/modules/lawbook/assets/agents/planner.md +7 -4
  24. package/dist/modules/lawbook/assets/agents/reviewer.md +1 -1
  25. package/dist/modules/lawbook/assets/agents/tester.md +1 -1
  26. package/dist/modules/lawbook/assets/commands/draft.md +2 -2
  27. package/dist/modules/lawbook/assets/commands/quick.md +2 -2
  28. package/dist/modules/lawbook/assets/rules/spec-reports-disciplines.md +1 -1
  29. package/dist/modules/lawbook/assets/rules/spec-tasks-mandatory-steps.md +4 -4
  30. package/dist/modules/lawbook/assets/skills/archive/steps/02-reconcile.md +1 -1
  31. package/dist/modules/lawbook/assets/skills/build/steps/01-load-change.md +1 -1
  32. package/dist/modules/lawbook/assets/skills/cortex/steps/02-dispatch-loop.md +22 -6
  33. package/dist/modules/lawbook/assets/skills/draft/steps/01-ensure-workspace.md +1 -1
  34. package/dist/modules/lawbook/assets/skills/draft/steps/02-understand.md +7 -4
  35. package/dist/modules/lawbook/assets/skills/draft/steps/03-name-capabilities.md +3 -1
  36. package/dist/modules/lawbook/assets/skills/draft/steps/04-write-artifacts.md +12 -2
  37. package/dist/modules/lawbook/assets/skills/explore/steps/01-investigate.md +15 -6
  38. package/dist/modules/lawbook/assets/skills/explore/steps/02-summarize.md +2 -0
  39. package/dist/modules/lawbook/assets/skills/investigate/steps/02-hand-off.md +1 -1
  40. package/dist/modules/lawbook/assets/skills/quick/steps/02-implement.md +1 -1
  41. package/dist/modules/lawbook/assets/skills/sync/steps/01-confirm.md +1 -1
  42. package/dist/modules/lawbook/assets/skills/sync/steps/02-reconcile.md +1 -1
  43. package/dist/modules/lawbook/bugfix.js +22 -26
  44. package/dist/modules/lawbook/change-tool.js +15 -0
  45. package/dist/modules/lawbook/engine.js +11 -0
  46. package/dist/modules/lawbook/levels.js +2 -0
  47. package/dist/modules/lawbook/quick.js +14 -23
  48. package/dist/modules/lawbook/register.js +1 -1
  49. package/dist/modules/lawbook/scaffold-change.js +238 -0
  50. package/dist/shared/compass-calls.js +197 -0
  51. package/package.json +1 -1
@@ -27,8 +27,8 @@ steps, branch convention, and testing/documentation requirements.
27
27
  see the `spec-reports-disciplines` rule) with the unit/integration/e2e results
28
28
  for what the feature touched.
29
29
  - Update the technical documentation the change touches.
30
- - Archive the change within the same PR (the `archive` command / `lawbook_archive`
31
- tool) after Cortex review/test PASS.
30
+ - Archive the change within the same PR (the `archive` command / `lawbook_change`
31
+ action `archive`) after Cortex review/test PASS.
32
32
 
33
33
  ## 3. Manual verification — the tester must execute it
34
34
 
@@ -38,10 +38,10 @@ requires verification is not complete until the tester has verified it.
38
38
 
39
39
  ## 4. Archiving is part of the change
40
40
 
41
- A change is not done until it is archived with `lawbook_archive` (never a manual
41
+ A change is not done until it is archived with `lawbook_change` action `archive` (never a manual
42
42
  `mv`). The archive lands in the same PR that implements the change.
43
43
 
44
- `lawbook_archive` is gated: it refuses to archive while any task is unchecked,
44
+ Archive (`lawbook_change` action `archive`) is gated: it refuses to archive while any task is unchecked,
45
45
  while `reports/` has no discipline report, while the delta specs are not yet
46
46
  synced into the canonical specs, or while harness review/test verdicts are not
47
47
  PASS. Resolve those first — the gate is enforced in the engine, so a manual `mv`
@@ -1,7 +1,7 @@
1
1
  # Reconciliation review (agent-executed)
2
2
 
3
3
  Run the reconciliation from the `sync` skill: reconstruct what was built
4
- (branch diff since draft + `compass_explore` / `compass_impact`) and compare it
4
+ (branch diff since draft + `compass_explore` with include `blast_radius`) and compare it
5
5
  to the change's delta specs.
6
6
 
7
7
  - **If the code drifted past the contracts:** show short insights — a tight
@@ -1,7 +1,7 @@
1
1
  # Load the change
2
2
 
3
3
  - Read `lawbook/changes/<name>/proposal.md`, `tasks.md`, and the delta specs under
4
- `specs/`. If unsure which change, run `lawbook_list`.
4
+ `specs/`. If unsure which change, run `lawbook_change` (action: list).
5
5
  - Read the governing standards in `docs/standards/` for the areas you'll touch.
6
6
 
7
7
  Next: read `steps/02-branch.md` and do only what it says.
@@ -3,10 +3,26 @@
3
3
  For the current Cortex stage (`cortex` action `brief` or `status`):
4
4
 
5
5
  1. Dispatch (spawn or adopt) the matching role with a clear handoff brief.
6
- When the role is the planner, paste the explorer brief into the prompt
7
- unchanged: symbols with files, callers and callees, blast radius, standards
8
- already read, recommended approach, open questions, and gaps.
9
- 2. On role completion, call the matching Cortex op:
6
+ - **Explorer template.** Give the intent (what the change should achieve),
7
+ the symbols or concepts to locate, and the questions to answer. Do not
8
+ list file paths for the explorer to read — it locates code through
9
+ Compass (`compass_find` / `compass_explore`) and reports
10
+ `Compass calls made: N`.
11
+ - When the role is the planner, paste the explorer brief into the prompt
12
+ unchanged: symbols with files, callers and callees, blast radius,
13
+ standards already read, recommended approach, open questions, and gaps.
14
+ 2. **Single question round** (after the explorer, before the planner drafts):
15
+ 1. Propose a level with `lawbook_change` action `level`, mode `propose`,
16
+ passing the paths and symbols from the brief.
17
+ 2. Dispatch the planner for questions only (no artifacts yet).
18
+ 3. Merge the explorer open questions, the planner questions, and the level
19
+ confirmation into **one** `advance` with `pauseForQuestions` +
20
+ `openQuestions`. Ask the human once; never open a second round for the
21
+ level.
22
+ 4. After the answers, record the level with `lawbook_change` action
23
+ `level`, mode `set` (evidence and answers in `reason`), then redispatch
24
+ the planner to draft.
25
+ 3. On role completion, call the matching Cortex op:
10
26
  - exploring → `advance`
11
27
  - planning with questions → `advance` + `pauseForQuestions` + `openQuestions`
12
28
  - questions after human answers → `advance` (back to planning)
@@ -15,8 +31,8 @@ For the current Cortex stage (`cortex` action `brief` or `status`):
15
31
  - reviewing PASS → `advance` + `verdict: PASS`; FAIL → `rework` + `verdict: FAIL`
16
32
  - testing PASS → `advance` + `verdict: PASS`; FAIL → `rework` + `verdict: FAIL`
17
33
  - archiving success → `advance` (to done)
18
- 3. Max rework is 3; if the engine rejects further rework, **ask the human**.
19
- 4. Coordinator never edits `src/`, never archives, never pushes, never runs
34
+ 4. Max rework is 3; if the engine rejects further rework, **ask the human**.
35
+ 5. Coordinator never edits `src/`, never archives, never pushes, never runs
20
36
  `laws accept`.
21
37
 
22
38
  Next: read `steps/03-complete.md` and do only what it says.
@@ -1,5 +1,5 @@
1
1
  # Ensure the workspace exists
2
2
 
3
- If `lawbook/` is missing, run the `lawbook_init` tool once to create it.
3
+ If `lawbook/` is missing, run `lawbook_change` (action: init) once to create it.
4
4
 
5
5
  Next: read `steps/02-understand.md` and do only what it says.
@@ -10,9 +10,12 @@
10
10
  concept) before grep/read to locate the code and its blast radius. Read the
11
11
  governing standards in `docs/standards/`.
12
12
  - Clarify what the user wants (feature / fix / refactor) and confirm scope.
13
- - **Propose a ceremony level** with `lawbook_level` (mode `propose`) using the
14
- paths and symbols from the brief or from that locate pass; **confirm with the
15
- human** (mode `set`) before writing artifacts. For an obvious one-liner,
16
- offer `speclaw quick` instead.
13
+ - When `change.json` already has a `confirmedLevel` (the coordinator recorded
14
+ it in the single question round), use that level and skip the separate level
15
+ confirmation.
16
+ - Otherwise **propose a ceremony level** with `lawbook_change` action `level`
17
+ (mode `propose`) using the paths and symbols from the brief or from that
18
+ locate pass; **confirm with the human** (mode `set`) before writing
19
+ artifacts. For an obvious one-liner, offer `speclaw quick` instead.
17
20
 
18
21
  Next: read `steps/03-name-capabilities.md` and do only what it says.
@@ -3,12 +3,14 @@
3
3
  - **Change name:** kebab-case, action-oriented (e.g. `add-login`,
4
4
  `fix-shift-overlap`). This is the folder under `lawbook/changes/`, and it is
5
5
  per-feature — always distinct.
6
- - **Capabilities:** run `lawbook_list` to see the canonical capabilities. A
6
+ - **Capabilities:** run `lawbook_change` (action: list) to see the canonical capabilities. A
7
7
  capability is the living contract for an area of behavior — it is *not* the
8
8
  change. When your change modifies behavior an existing capability already
9
9
  governs, reuse that capability's **exact** name so `sync` updates its spec.
10
10
  Introduce a new capability only as a deliberate choice for a genuinely distinct
11
11
  area of behavior — never as a near-duplicate (`transfer` next to an existing
12
12
  `transfers`) of one that already exists.
13
+ - Pass that exact capability name to the scaffold (`--capability` /
14
+ `capability`), so the delta stub lands in the right capability.
13
15
 
14
16
  Next: read `steps/04-write-artifacts.md` and do only what it says.
@@ -1,8 +1,18 @@
1
1
  # Write the artifacts
2
2
 
3
3
  Artifact volume follows the **confirmed ceremony level** in `change.json`
4
- (propose + confirm with `lawbook_level` / the human **before** scaffolding).
5
- Missing `change.json` means level 3.
4
+ (propose + confirm with `lawbook_change` action `level` / the human **before**
5
+ scaffolding). Missing `change.json` means level 3.
6
+
7
+ Scaffold with `speclaw lawbook draft <name> --level N --capability <capability>`
8
+ (or `lawbook_change` action `draft` with `change`, `level`, and `capability`),
9
+ passing the exact capability name chosen in the previous step. It creates
10
+ `change.json`, `reports/README.md`, and the level's stub artifacts; fill them
11
+ in. When the capability is new, the delta stub is a marked placeholder:
12
+ replace it with the real spec and remove the
13
+ `<!-- speclaw:placeholder-delta -->` marker (validate warns and sync refuses
14
+ while it remains). For each further capability, add its delta by hand, and
15
+ delete any stub delta the change does not use.
6
16
 
7
17
  Create under `lawbook/changes/<name>/` only what the level needs:
8
18
 
@@ -1,11 +1,20 @@
1
1
  # Investigate
2
2
 
3
- - **Refresh the index first.** Run `compass_index` before investigating — it is
4
- incremental (unchanged files skipped by hash), so it is cheap and keeps your
5
- reasoning on the current graph rather than a stale one.
6
- - **Understand the code first.** Use `compass_find` (mode: concept) to find relevant code by
7
- meaning and `compass_explore` to read a symbol's source plus its callers and
8
- callees — before grep/read.
3
+ - **Locate through Compass first.** Your first code-locating call is
4
+ `compass_find` (mode: concept for an idea, exact for a name) or
5
+ `compass_explore <symbol>` to read a symbol's source plus its callers and
6
+ callees. Use `compass_diff_context` when the question is about a diff.
7
+ - **Index only when needed.** Run `compass_index` only when `compass_find`
8
+ returns nothing or reports a missing or stale index, then retry the find. It
9
+ is incremental (unchanged files skipped by hash).
10
+ - **Read/Grep code only with a named reason.** Before any Read/Grep/Glob of
11
+ indexed source code, name which Rule 1 fallback holds: (1) a Compass call ran
12
+ and returned nothing useful for the query, (2) the graph is missing and
13
+ `compass_index` cannot build it, or (3) the target is not indexed code
14
+ (markdown, JSON/config, stylesheets, logs, generated files, lockfiles).
15
+ "I know which file it is" is not a reason — `compass_explore` takes a path.
16
+ - **Count your Compass calls.** Keep a tally of `compass_explore`,
17
+ `compass_find`, and `compass_diff_context` calls for the brief.
9
18
  - **Ask sharp questions** to surface hidden assumptions, constraints, and edge
10
19
  cases. Confirm scope and non-goals.
11
20
  - **Check the law.** Read the relevant `docs/standards/` so any direction you
@@ -9,6 +9,8 @@ Produce a brief the planner can use as the code map. Include only these fields:
9
9
  - recommended approach
10
10
  - open questions
11
11
  - gaps: the phrase "nothing unresolved", or each symbol that is still unresolved
12
+ - `Compass calls made: N` — the count of `compass_explore`, `compass_find`, and
13
+ `compass_diff_context` calls (and their aliases) you made
12
14
 
13
15
  When the direction is clear, offer to `draft` the change.
14
16
 
@@ -1,6 +1,6 @@
1
1
  # Hand off to draft
2
2
 
3
- - **`compass_impact`** on the confirmed root cause for blast radius.
3
+ - **`compass_explore`** (include `blast_radius`) on the confirmed root cause.
4
4
  - **`speclaw lawbook draft --bug <name>`** — pre-seed only; fill repro, fix, regression test, prevention.
5
5
 
6
6
  Treat the ranking as **evidence**, not a verdict. No further steps remain — investigate workflow complete.
@@ -2,6 +2,6 @@
2
2
 
3
3
  Make the fix, tick every `- [ ]` in `record.md`, and write at least one
4
4
  discipline report under `reports/`. Archive with `lawbook_change` (action: archive) (no sync at
5
- level 0). Promote via `lawbook_level` if scope grew.
5
+ level 0). Promote via `lawbook_change` (action: level, mode: promote) if scope grew.
6
6
 
7
7
  No further steps — workflow complete.
@@ -1,5 +1,5 @@
1
1
  # Confirm which change to sync
2
2
 
3
- Confirm which change to sync (run `lawbook_list` if unsure).
3
+ Confirm which change to sync (run `lawbook_change` (action: list) if unsure).
4
4
 
5
5
  Next: read `steps/02-reconcile.md` and do only what it says.
@@ -3,7 +3,7 @@
3
3
  Before promoting, compare what was built against the change's delta specs:
4
4
 
5
5
  - Reconstruct what shipped: `git diff <branch-point>...HEAD` for the change's
6
- branch, then `compass_explore` / `compass_impact` on the touched symbols to
6
+ branch, then `compass_explore` (include `blast_radius`) on the touched symbols to
7
7
  understand behavior, not just changed lines.
8
8
  - Diff intent vs reality: list behavior that is implemented but missing from,
9
9
  or contradicted by, `lawbook/changes/<name>/specs/**`.
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { gatherSignals, loadCeremonyConfig, proposeLevel, setCeremonyLevel, writeCeremonyRecord, } from "./levels.js";
3
+ import { scaffoldChange } from "./scaffold-change.js";
4
4
  export const BUGFIX_HEADINGS = [
5
5
  "1. Observed symptom",
6
6
  "2. Minimal reproduction",
@@ -153,32 +153,28 @@ ${blast}
153
153
  * Scaffold a bug change: `bugfix.md`, `change.json`, and `reports/`.
154
154
  */
155
155
  export function scaffoldBugfix(projectPath, name, opts = {}) {
156
- const changeDir = path.join(projectPath, "lawbook", "changes", name);
157
- if (fs.existsSync(changeDir)) {
158
- throw new Error(`change "${name}" already exists under lawbook/changes/`);
159
- }
160
- const targets = opts.targets ?? { paths: [], symbols: [] };
161
- const { thresholds } = loadCeremonyConfig(projectPath);
162
- const signals = gatherSignals(projectPath, targets, thresholds);
163
- const proposal = proposeLevel(signals, thresholds);
164
- const level = opts.level ?? (proposal.level !== null && proposal.level <= 1 ? proposal.level : 1);
165
- fs.mkdirSync(path.join(changeDir, "reports"), { recursive: true });
166
- fs.writeFileSync(path.join(changeDir, "bugfix.md"), bugfixTemplate(name, level, opts.seed));
167
- fs.writeFileSync(path.join(changeDir, "reports", "README.md"), `# Reports — ${name}\n\nBug reports MUST include the regression test **failing before the fix**.\n`);
168
- if (level >= 1) {
169
- fs.writeFileSync(path.join(changeDir, "tasks.md"), `- [ ] Reproduce and confirm root cause\n- [ ] Implement fix\n- [ ] Add regression test (red before, green after)\n- [ ] Complete prevention §7\n- [ ] Write discipline report under reports/\n`);
170
- }
171
- if (level >= 2) {
172
- fs.writeFileSync(path.join(changeDir, "design.md"), `# Design — ${name}\n\n## Approach\n\n(structural bugfix — document the fix architecture)\n`);
173
- }
174
- const record = setCeremonyLevel(projectPath, name, {
175
- proposal,
176
- level,
177
- confirmedBy: "human",
156
+ const r = scaffoldChange(projectPath, name, {
157
+ changeType: "bug",
158
+ targets: opts.targets,
159
+ level: (proposal) => opts.level ?? (proposal.level !== null && proposal.level <= 1 ? proposal.level : 1),
160
+ reportsReadme: `# Reports — ${name}\n\nBug reports MUST include the regression test **failing before the fix**.\n`,
161
+ artifacts: ({ level }) => {
162
+ const lvl = level ?? 1;
163
+ const files = {
164
+ "bugfix.md": bugfixTemplate(name, lvl, opts.seed),
165
+ };
166
+ if (lvl >= 1) {
167
+ files["tasks.md"] =
168
+ `- [ ] Reproduce and confirm root cause\n- [ ] Implement fix\n- [ ] Add regression test (red before, green after)\n- [ ] Complete prevention §7\n- [ ] Write discipline report under reports/\n`;
169
+ }
170
+ if (lvl >= 2) {
171
+ files["design.md"] =
172
+ `# Design — ${name}\n\n## Approach\n\n(structural bugfix — document the fix architecture)\n`;
173
+ }
174
+ return files;
175
+ },
178
176
  });
179
- const updated = { ...record, changeType: "bug" };
180
- writeCeremonyRecord(projectPath, name, updated);
181
- return { change: name, proposal, record: updated, dir: changeDir };
177
+ return { change: r.change, proposal: r.proposal, record: r.record, dir: r.dir };
182
178
  }
183
179
  /** Read change type from an archived folder path. */
184
180
  export function readChangeTypeFromDir(changeDir) {
@@ -1,12 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { specInit, specValidate, specSync, specArchive, specList } from "./engine.js";
3
3
  import { handleLevel } from "./quick.js";
4
+ import { scaffoldBugfix } from "./bugfix.js";
5
+ import { scaffoldFeature } from "./scaffold-change.js";
4
6
  import { buildCoverageReport, loadCoverageConfig, renderCoverageAgent } from "./coverage.js";
5
7
  import { buildDriftReport, renderDriftAgent } from "./drift.js";
6
8
  import { handleHarness, harnessOps } from "../cortex/harness.js";
7
9
  export const lawbookChangeActions = [
8
10
  "init",
9
11
  "list",
12
+ "draft",
10
13
  "validate",
11
14
  "sync",
12
15
  "archive",
@@ -38,6 +41,7 @@ export const lawbookChangeSchema = {
38
41
  openQuestions: z.array(z.string()).optional(),
39
42
  pauseForQuestions: z.boolean().optional(),
40
43
  note: z.string().optional(),
44
+ bug: z.boolean().optional(),
41
45
  };
42
46
  function requireField(args, field) {
43
47
  const v = args[field];
@@ -56,6 +60,17 @@ export function handleLawbookChange(args) {
56
60
  return specInit(args.projectPath);
57
61
  case "list":
58
62
  return specList(args.projectPath);
63
+ case "draft": {
64
+ // Covers: req~feature-draft~1
65
+ const name = requireField(args, "change");
66
+ if (args.bug)
67
+ return scaffoldBugfix(args.projectPath, name, { level: args.level });
68
+ return scaffoldFeature(args.projectPath, name, {
69
+ level: args.level,
70
+ reason: args.reason,
71
+ capability: args.capability,
72
+ });
73
+ }
59
74
  case "validate":
60
75
  return specValidate(args.projectPath, requireField(args, "change"));
61
76
  case "sync":
@@ -5,6 +5,7 @@ import { sealCapability } from "./anchors.js";
5
5
  import { classifyEars, diagnoseEars, extractNormativeBody, loadEarsConfig, splitRequirementBlocks, } from "./ears.js";
6
6
  import { artifactNeeds, confirmedLevel, countUncheckedTasks, gatherSignals, hasDisciplineReport, loadCeremonyConfig, proposeLevel, readChangeType, readCeremonyRecord, } from "./levels.js";
7
7
  import { inferBugResolution, preventionRequiresDelta, validateBugfixContent } from "./bugfix.js";
8
+ import { isPlaceholderDelta } from "./scaffold-change.js";
8
9
  import { harnessArchiveBlockers } from "../cortex/harness.js";
9
10
  // speclaw's own spec-driven workflow engine. Inspired by OpenSpec's model
10
11
  // (proposals, delta specs, changes, archive) but implemented from scratch and
@@ -251,6 +252,9 @@ export function specValidate(projectPath, change, remeasure) {
251
252
  for (const file of deltas) {
252
253
  const rel = path.relative(changeDir, file);
253
254
  const content = fs.readFileSync(file, "utf8");
255
+ if (isPlaceholderDelta(content)) {
256
+ warnings.push(`${rel}: placeholder delta from \`lawbook draft\` — replace it (and remove the marker) or delete it; sync refuses placeholders`);
257
+ }
254
258
  if (!/\b(SHALL|MUST)\b/.test(content)) {
255
259
  issues.push(`${rel}: no normative requirement (use SHALL/MUST)`);
256
260
  }
@@ -328,6 +332,13 @@ export function specSync(projectPath, change) {
328
332
  const updated = [];
329
333
  if (!fs.existsSync(changeSpecs))
330
334
  return { change, promoted, created, updated };
335
+ // Refuse before copying anything, so a placeholder never becomes canonical.
336
+ const placeholders = deltaSpecFiles(changeDir)
337
+ .filter((f) => isPlaceholderDelta(fs.readFileSync(f, "utf8")))
338
+ .map((f) => path.relative(changeDir, f));
339
+ if (placeholders.length > 0) {
340
+ throw new Error(`refusing to sync placeholder delta(s): ${placeholders.join(", ")} — fill them in and remove the marker, or delete them`);
341
+ }
331
342
  const walk = (dir) => {
332
343
  for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
333
344
  const full = path.join(dir, e.name);
@@ -385,6 +385,8 @@ export function setCeremonyLevel(projectPath, change, opts) {
385
385
  confirmedAt: new Date().toISOString(),
386
386
  overrideReason: opts.reason,
387
387
  promotions: prev?.promotions ?? [],
388
+ // A drafted change keeps its type when its level is confirmed later.
389
+ ...(prev?.changeType ? { changeType: prev.changeType } : {}),
388
390
  };
389
391
  writeCeremonyRecord(projectPath, change, record);
390
392
  return record;
@@ -1,6 +1,5 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { gatherSignals, loadCeremonyConfig, promoteCeremonyLevel, proposeLevel, setCeremonyLevel, } from "./levels.js";
2
+ import { scaffoldChange } from "./scaffold-change.js";
4
3
  /**
5
4
  * Scaffold a level-0 change: `record.md`, `change.json`, and `reports/`.
6
5
  *
@@ -9,25 +8,23 @@ import { gatherSignals, loadCeremonyConfig, promoteCeremonyLevel, proposeLevel,
9
8
  * @param targets - Optional paths/symbols used to propose the level (default empty → score 0).
10
9
  */
11
10
  export function scaffoldQuick(projectPath, name, targets = { paths: [], symbols: [] }) {
12
- const changeDir = path.join(projectPath, "lawbook", "changes", name);
13
- if (fs.existsSync(changeDir)) {
14
- throw new Error(`change "${name}" already exists under lawbook/changes/`);
15
- }
16
- const { thresholds } = loadCeremonyConfig(projectPath);
17
- const signals = gatherSignals(projectPath, targets, thresholds);
18
- const proposal = proposeLevel(signals, thresholds);
19
11
  // quick always records level 0; if measurement says higher, still allow but note it.
20
- const level = 0;
21
- fs.mkdirSync(path.join(changeDir, "reports"), { recursive: true });
22
- const rationale = proposal.level === null
12
+ const quickRationale = (proposal) => proposal.level === null
23
13
  ? proposal.rationale
24
14
  : proposal.level > 0
25
15
  ? `${proposal.rationale} — quick forced level 0; promote if scope grows`
26
16
  : proposal.rationale;
27
- const recordMd = `# ${name}
17
+ const r = scaffoldChange(projectPath, name, {
18
+ targets,
19
+ level: 0,
20
+ recordProposal: (proposal) => ({ ...proposal, rationale: quickRationale(proposal) }),
21
+ reason: (proposal) => proposal.level !== null && proposal.level > 0 ? "speclaw quick" : undefined,
22
+ reportsReadme: `# Reports — ${name}\n\nAdd at least one discipline report before archive.\n`,
23
+ artifacts: ({ proposal }) => ({
24
+ "record.md": `# ${name}
28
25
 
29
26
  **Level:** 0 (proposed: ${proposal.level ?? "n/a"}, confirmed by: human)
30
- **Why:** ${rationale}
27
+ **Why:** ${quickRationale(proposal)}
31
28
 
32
29
  ## What changes
33
30
 
@@ -42,16 +39,10 @@ export function scaffoldQuick(projectPath, name, targets = { paths: [], symbols:
42
39
  ## Evidence
43
40
 
44
41
  - \`reports/\` — add a discipline report before archive
45
- `;
46
- fs.writeFileSync(path.join(changeDir, "record.md"), recordMd);
47
- fs.writeFileSync(path.join(changeDir, "reports", "README.md"), `# Reports — ${name}\n\nAdd at least one discipline report before archive.\n`);
48
- const record = setCeremonyLevel(projectPath, name, {
49
- proposal: { ...proposal, rationale },
50
- level,
51
- confirmedBy: "human",
52
- reason: proposal.level !== null && proposal.level > 0 ? "speclaw quick" : undefined,
42
+ `,
43
+ }),
53
44
  });
54
- return { change: name, proposal, record, dir: changeDir };
45
+ return { change: r.change, proposal: r.proposal, record: r.record, dir: r.dir };
55
46
  }
56
47
  /**
57
48
  * Handle `lawbook_level` modes: propose / set / promote / explain.
@@ -33,7 +33,7 @@ export function registerSpec(server, opts = {}) {
33
33
  return;
34
34
  defineTool(server, { name, description, inputSchema, handler });
35
35
  };
36
- add("lawbook_change", "Lawbook lifecycle: init, list, validate, sync, archive, level, coverage, drift.", lawbookChangeSchema, async (args) => text(handleLawbookChange(args)));
36
+ add("lawbook_change", "Lawbook lifecycle: init, list, draft, validate, sync, archive, level, coverage, drift.", lawbookChangeSchema, async (args) => text(handleLawbookChange(args)));
37
37
  add("lawbook_investigate", "Rank bug origins from the graph. Pass stackTrace or symptom — evidence, not verdict.", {
38
38
  projectPath: z.string(),
39
39
  stackTrace: z.string().optional(),