@gobing-ai/spur 0.3.77 → 0.3.80

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 (32) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/templates/AGENTS.md +26 -23
  3. package/config/templates/docs/00_ADR.md +13 -23
  4. package/config/templates/docs/01_PRD.md +5 -2
  5. package/config/templates/docs/02_ROADMAP.md +9 -13
  6. package/config/templates/docs/03_ARCHITECTURE.md +2 -2
  7. package/config/templates/docs/04_DESIGN.md +12 -31
  8. package/config/templates/docs/05_FEATURES.md +6 -18
  9. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +162 -394
  10. package/package.json +9 -9
  11. package/plugins/sp/README.md +12 -7
  12. package/plugins/sp/agents/super-reviewer.md +43 -8
  13. package/plugins/sp/commands/dev-refineall.md +1 -0
  14. package/plugins/sp/commands/dev-run.md +1 -0
  15. package/plugins/sp/commands/dev-runall.md +1 -0
  16. package/plugins/sp/commands/dev-verifyall.md +1 -0
  17. package/plugins/sp/plugin.json +1 -1
  18. package/plugins/sp/scripts/batch-preflight.mjs +173 -2
  19. package/plugins/sp/scripts/batch-preflight.ts +257 -2
  20. package/plugins/sp/scripts/verify-answer-lint.ts +32 -9
  21. package/plugins/sp/skills/conflict-finding/SKILL.md +6 -0
  22. package/plugins/sp/skills/daily-summary/SKILL.md +1 -1
  23. package/plugins/sp/skills/doc-evolve/SKILL.md +26 -40
  24. package/plugins/sp/skills/doc-evolve/references/operations.md +17 -30
  25. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +17 -1
  26. package/plugins/sp/skills/spur-cli/references/tasks.md +31 -1
  27. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +14 -0
  28. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +36 -3
  29. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +12 -0
  30. package/plugins/sp/skills/spur-dev/references/execution-batch.md +17 -0
  31. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +148 -17
  32. package/spur.js +1389 -615
@@ -25,6 +25,182 @@ export type PreflightResult =
25
25
  | { action: 'run'; code?: string; reason?: string }
26
26
  | { action: 'skip'; code: string; reason: string; unmetDeps?: string[] };
27
27
 
28
+ // ── Command-aware quick readiness (task 0814 R2) ──────────────────────────────
29
+ // Read-only admission decision for the requested dev operation. Distinguishes
30
+ // runnable / needs-refinement / blocked / skipped / invalid outcomes without
31
+ // LLM dispatch, full tests/lint, live-data probes, feature mutation, or
32
+ // corpus-wide relational checking. Refinement gaps are work to do, not errors.
33
+
34
+ export type ReadinessOperation = 'run' | 'refine' | 'verify';
35
+
36
+ export interface QuickReadinessInput {
37
+ wbs: string;
38
+ status: TaskStatus;
39
+ operation: ReadinessOperation;
40
+ /** Frontmatter dependencies[] WBS list (run only). */
41
+ dependencies?: string[];
42
+ /** Status of each dependency WBS; missing → treated as unmet (run only). */
43
+ depStatuses?: Record<string, string>;
44
+ /** Size of the status-filtered candidate set after the selector resolved; 0 = empty set. */
45
+ filteredCount?: number;
46
+ /** Required planning sections for this variant+status; empty = not applicable. */
47
+ requiredSections?: string[];
48
+ /** Sections that are actually present (non-placeholder) in the task. */
49
+ presentSections?: string[];
50
+ /** L1/L2/L3 content-policy findings keyed by section; empty = clean. */
51
+ sectionFindings?: Record<string, string>;
52
+ /** Verify only: re-verification semantics (--force) — never a dirty-tree bypass. */
53
+ force?: boolean;
54
+ }
55
+
56
+ export type QuickReadinessResult =
57
+ | { action: 'runnable'; code: string; reason: string }
58
+ | { action: 'needs-refinement'; code: string; reason: string; gaps: string[] }
59
+ | { action: 'blocked'; code: string; reason: string; unmetDeps?: string[] }
60
+ | { action: 'skipped'; code: string; reason: string }
61
+ | { action: 'invalid'; code: string; reason: string };
62
+
63
+ /**
64
+ * Evaluate quick readiness for a requested dev operation (0814 R2). Read-only:
65
+ * no model, no full tests/lint, no live-data probe, no feature mutation, no
66
+ * corpus-wide relational check. An empty status-filtered set is `skipped`
67
+ * (mirrors the zero-task rule), never an error. Refinement gaps under `refine`
68
+ * are work to do, so they do not block; under `run` they are `needs-refinement`.
69
+ */
70
+ export function quickReadiness(input: QuickReadinessInput): QuickReadinessResult {
71
+ const status = (input.status ?? '').toLowerCase();
72
+ const operation = input.operation;
73
+
74
+ if (operation !== 'run' && operation !== 'refine' && operation !== 'verify') {
75
+ return {
76
+ action: 'invalid',
77
+ code: 'IV',
78
+ reason: `quick-readiness: unknown operation '${operation}' (${input.wbs})`,
79
+ };
80
+ }
81
+
82
+ if (input.filteredCount !== undefined) {
83
+ if (input.filteredCount < 0) {
84
+ return {
85
+ action: 'invalid',
86
+ code: 'IV',
87
+ reason: `quick-readiness: invalid negative filtered count '${input.filteredCount}' (${input.wbs})`,
88
+ };
89
+ }
90
+ if (input.filteredCount === 0) {
91
+ return {
92
+ action: 'skipped',
93
+ code: 'EMPTY',
94
+ reason: `quick-readiness: empty status-filtered set — nothing to ${operation} (${input.wbs})`,
95
+ };
96
+ }
97
+ }
98
+
99
+ if (status === 'cancelled' || status === 'done') {
100
+ // verify --force re-verification (R2 AC): an already-verified terminal task
101
+ // is re-checked, not skipped — but force never bypasses a dirty tree or the
102
+ // owning gates; it only re-admits a terminal task for re-verification.
103
+ if (operation === 'verify' && input.force === true) {
104
+ return {
105
+ action: 'runnable',
106
+ code: 'FORCE',
107
+ reason: `quick-readiness: verify --force re-verification of ${status} task ${input.wbs}`,
108
+ };
109
+ }
110
+ return {
111
+ action: 'skipped',
112
+ code: status === 'done' ? 'DONE' : 'CANCELLED',
113
+ reason: `quick-readiness: already ${status} — no ${operation} hop (${input.wbs})`,
114
+ };
115
+ }
116
+
117
+ if (status === 'blocked') {
118
+ return {
119
+ action: 'blocked',
120
+ code: 'BLK',
121
+ reason: `quick-readiness: blocked — human/handover first (${input.wbs})`,
122
+ };
123
+ }
124
+
125
+ // Unmet out-of-set dependency is a block for the operation that needs it.
126
+ if (operation === 'run' && input.dependencies && input.dependencies.length > 0) {
127
+ const unmet = input.dependencies.filter((d) => (input.depStatuses?.[d] ?? 'missing').toLowerCase() !== 'done');
128
+ if (unmet.length > 0) {
129
+ return {
130
+ action: 'blocked',
131
+ code: 'DEP',
132
+ reason: `quick-readiness: unmet deps — ${unmet.join(', ')} (${input.wbs})`,
133
+ unmetDeps: unmet,
134
+ };
135
+ }
136
+ }
137
+
138
+ const required = input.requiredSections ?? [];
139
+ const present = input.presentSections ?? [];
140
+ // A required section is a gap when it is absent from the present-set OR carries a
141
+ // content-policy finding (the caller-supplied `sectionFindings` from the matrix /
142
+ // `TaskCheckService.checkContentPolicy`). This lets the function detect a gap itself
143
+ // rather than depending on the caller to pre-enumerate every missing section.
144
+ const gaps = required.filter((s) => {
145
+ const finding = input.sectionFindings?.[s];
146
+ return !present.includes(s) || (finding !== undefined && finding !== '');
147
+ });
148
+
149
+ // refine: missing/incomplete planning sections are the work, not a failure.
150
+ if (operation === 'refine') {
151
+ if (status !== 'backlog' && status !== 'todo') {
152
+ return {
153
+ action: 'skipped',
154
+ code: 'NONPLAN',
155
+ reason: `quick-readiness: refine targets backlog/todo only, not '${status}' (${input.wbs})`,
156
+ };
157
+ }
158
+ return {
159
+ action: 'runnable',
160
+ code: 'OK',
161
+ reason: `quick-readiness: refine ready for ${input.wbs} (${gaps.length} planning gap(s) to fill)`,
162
+ };
163
+ }
164
+
165
+ if (operation === 'verify') {
166
+ if (status !== 'testing' && status !== 'wip') {
167
+ return {
168
+ action: 'invalid',
169
+ code: 'NOVERIFY',
170
+ reason: `quick-readiness: verify needs testing/wip, not '${status}' (${input.wbs})`,
171
+ };
172
+ }
173
+ return {
174
+ action: 'runnable',
175
+ code: 'OK',
176
+ reason: `quick-readiness: verify ready for ${input.wbs}`,
177
+ };
178
+ }
179
+
180
+ // run: implementation admission. Eligible statuses are todo/wip/testing;
181
+ // a backlog task needs the chain's auto-promotion first (step 0).
182
+ if (status !== 'todo' && status !== 'wip' && status !== 'testing') {
183
+ return {
184
+ action: 'invalid',
185
+ code: 'NORUN',
186
+ reason: `quick-readiness: run needs todo/wip/testing, not '${status}' (${input.wbs})`,
187
+ };
188
+ }
189
+ if (gaps.length > 0) {
190
+ return {
191
+ action: 'needs-refinement',
192
+ code: 'REFINE',
193
+ reason: `quick-readiness: implementation sections incomplete (${input.wbs})`,
194
+ gaps,
195
+ };
196
+ }
197
+ return {
198
+ action: 'runnable',
199
+ code: 'OK',
200
+ reason: `quick-readiness: run ready for ${input.wbs}`,
201
+ };
202
+ }
203
+
28
204
  /**
29
205
  * Evaluate whether the batch should launch task-pipeline.yaml for this WBS.
30
206
  * STOP codes align with routing-table TABLE A row ids (A2, A7, A8, A9).
@@ -113,6 +289,16 @@ export interface PreflightCliArgs {
113
289
  recovery: boolean;
114
290
  help: boolean;
115
291
  json: boolean;
292
+ /** Quick-readiness operation (run|refine|verify); when set, run quickReadiness. */
293
+ operation: ReadinessOperation | null;
294
+ /** Verify-only re-verification (R2 AC). */
295
+ force: boolean;
296
+ /** Status-filtered candidate set size; 0 = empty set. */
297
+ filteredCount: number | null;
298
+ /** Required planning sections (matrix-selected). */
299
+ requiredSections: string[];
300
+ /** Sections actually present in the task. */
301
+ presentSections: string[];
116
302
  }
117
303
 
118
304
  export function parsePreflightCliArgs(argv: string[]): PreflightCliArgs {
@@ -123,13 +309,43 @@ export function parsePreflightCliArgs(argv: string[]): PreflightCliArgs {
123
309
  let recovery = false;
124
310
  let help = false;
125
311
  let json = false;
312
+ let operation: ReadinessOperation | null = null;
313
+ let force = false;
314
+ let filteredCount: number | null = null;
315
+ let requiredSections: string[] = [];
316
+ let presentSections: string[] = [];
126
317
 
127
318
  for (let i = 0; i < argv.length; i++) {
128
319
  const a = argv[i];
129
320
  if (a === '--help' || a === '-h') help = true;
130
321
  else if (a === '--json') json = true;
131
322
  else if (a === '--recovery') recovery = true;
132
- else if (a === '--wbs') wbs = argv[++i] ?? wbs;
323
+ else if (a === '--force') force = true;
324
+ else if (a === '--operation') {
325
+ const v = argv[++i] ?? '';
326
+ operation = v === 'run' || v === 'refine' || v === 'verify' ? v : null;
327
+ } else if (a === '--filtered-count') {
328
+ const v = Number(argv[++i]);
329
+ filteredCount = Number.isFinite(v) ? v : null;
330
+ } else if (a === '--required-sections') {
331
+ const raw = argv[++i] ?? '';
332
+ requiredSections =
333
+ raw.length === 0
334
+ ? []
335
+ : raw
336
+ .split(',')
337
+ .map((s) => s.trim())
338
+ .filter(Boolean);
339
+ } else if (a === '--present-sections') {
340
+ const raw = argv[++i] ?? '';
341
+ presentSections =
342
+ raw.length === 0
343
+ ? []
344
+ : raw
345
+ .split(',')
346
+ .map((s) => s.trim())
347
+ .filter(Boolean);
348
+ } else if (a === '--wbs') wbs = argv[++i] ?? wbs;
133
349
  else if (a === '--status') status = argv[++i] ?? null;
134
350
  else if (a === '--deps') {
135
351
  const raw = argv[++i] ?? '';
@@ -149,12 +365,27 @@ export function parsePreflightCliArgs(argv: string[]): PreflightCliArgs {
149
365
  }
150
366
  }
151
367
  }
152
- return { status, deps, depStatuses, wbs, recovery, help, json };
368
+ return {
369
+ status,
370
+ deps,
371
+ depStatuses,
372
+ wbs,
373
+ recovery,
374
+ help,
375
+ json,
376
+ operation,
377
+ force,
378
+ filteredCount,
379
+ requiredSections,
380
+ presentSections,
381
+ };
153
382
  }
154
383
 
155
384
  export const PREFLIGHT_CLI_USAGE = `Usage:
156
385
  bun plugins/sp/scripts/batch-preflight.ts --wbs <wbs> --status <status> \\
157
386
  [--deps 0275,0276] [--dep-status 0275:done,0276:todo] [--recovery] [--json]
387
+ bun plugins/sp/scripts/batch-preflight.ts --operation <run|refine|verify> --wbs <wbs> --status <status> \
388
+ [--filtered-count <n>] [--required-sections A,B] [--present-sections A,B] [--force] [--json]
158
389
 
159
390
  Exit: 0 = run (or recovery hint printed); 2 = skip; 1 = usage.`;
160
391
 
@@ -173,6 +404,30 @@ export function runPreflightCli(argv: string[]): { exitCode: number; stdout: str
173
404
  return { exitCode: 0, stdout: body, stderr: '' };
174
405
  }
175
406
 
407
+ // Quick command-aware readiness (0814 R2) — read-only admission decision.
408
+ if (args.operation !== null) {
409
+ const result = quickReadiness({
410
+ wbs: args.wbs,
411
+ status: args.status,
412
+ operation: args.operation,
413
+ dependencies: args.deps,
414
+ depStatuses: args.depStatuses,
415
+ ...(args.filteredCount !== null ? { filteredCount: args.filteredCount } : {}),
416
+ ...(args.requiredSections.length > 0 ? { requiredSections: args.requiredSections } : {}),
417
+ ...(args.presentSections.length > 0 ? { presentSections: args.presentSections } : {}),
418
+ ...(args.force ? { force: true } : {}),
419
+ });
420
+ const runnable = result.action === 'runnable' || result.action === 'needs-refinement';
421
+ if (args.json) {
422
+ return { exitCode: runnable ? 0 : 2, stdout: `${JSON.stringify(result, null, 2)}\n`, stderr: '' };
423
+ }
424
+ return {
425
+ exitCode: runnable ? 0 : 2,
426
+ stdout: `${result.action}${result.code ? ` ${result.code}` : ''}: ${result.reason}\n`,
427
+ stderr: '',
428
+ };
429
+ }
430
+
176
431
  const result = preflightTask({
177
432
  wbs: args.wbs,
178
433
  status: args.status,
@@ -282,18 +282,24 @@ function stripAcWrappers(title: string): string {
282
282
  * Normalize an AC identity to its canonical key, mirroring the documented
283
283
  * matching behavior of feature-check `rowMatchesScenario` + ac-style-guide
284
284
  * "Four accepted id forms" (exact/bare title, `Scenario:` prefix, bracket
285
- * tags, `AC-N` ordinal) without importing that private matcher or adopting
285
+ * tags, `AC-N` ordinal) plus the task-side bold-trajectory paragraph
286
+ * (0817 R3), without importing that private matcher or adopting
286
287
  * its permissive trailing-Gherkin fallback (0804 R4). Comparison is
287
288
  * case/quote/whitespace-insensitive. A paraphrase normalizes differently
288
289
  * and still fails.
289
290
  */
290
291
  function normalizeAcTitle(title: string): string {
291
- return stripAcWrappers(title)
292
- .replace(/^R\d+\s*[:\-—]?\s*/, '')
293
- .toLowerCase()
294
- .replace(/[ʼ‘’“”]/g, '')
295
- .replace(/\s+/g, ' ')
296
- .trim();
292
+ return (
293
+ stripAcWrappers(title)
294
+ .replace(/^R\d+\s*[:\-—]?\s*/, '')
295
+ .toLowerCase()
296
+ // Exact pre-refactor removal set (0809 R5): ASCII apostrophe + the four curly
297
+ // quotes. Escaped form keeps U+0027 visible next to lookalike curly glyphs;
298
+ // U+02BC stays a meaningful character, never removable punctuation.
299
+ .replace(/[\u0027\u2018\u2019\u201c\u201d]/g, '')
300
+ .replace(/\s+/g, ' ')
301
+ .trim()
302
+ );
297
303
  }
298
304
 
299
305
  /**
@@ -302,7 +308,7 @@ function normalizeAcTitle(title: string): string {
302
308
  * A checklist-declared spelling always wins over the positional alias.
303
309
  */
304
310
  interface AcIdentityIndex {
305
- /** normalized canonical title → a declared spelling (label, token, or title). */
311
+ /** normalized canonical title → a declared spelling (label, token, title, or bold trajectory). */
306
312
  readonly byTitle: Map<string, string>;
307
313
  /** AC-N → task scenario title at that 1-based ordinal. */
308
314
  readonly taskScenarios: string[];
@@ -326,6 +332,19 @@ function buildAcIdentityIndex(taskContent: string, featureContent: string | null
326
332
  const leading = label.split(/\s+/)[0] ?? '';
327
333
  if (leading && leading !== label) declareIdentity(leading);
328
334
  }
335
+ // Bold-trajectory form (task 0817 R3): answers may cite an AC by a bare
336
+ // `**AC id**` paragraph (house style for long/complex ids). Only whole-line
337
+ // bold spans count — the line-anchored lazy regex rejects lines with two
338
+ // spans, keeping interpolated bold text out of the index. The head (text
339
+ // before the first colon) is declared like a checklist label, mirroring
340
+ // the `(?::|$)` label extraction above.
341
+ for (const m of section.matchAll(/^\*\*(.+?)\*\*\s*$/gm)) {
342
+ const inner = (m[1] ?? '').trim();
343
+ if (!inner) continue;
344
+ declareIdentity(inner);
345
+ const head = inner.split(':')[0]?.trim() ?? '';
346
+ if (head && head !== inner) declareIdentity(head);
347
+ }
329
348
  const scenarioTitles = (content: string): string[] =>
330
349
  [...content.matchAll(/^[ \t]*Scenario:\s*(.+)\s*$/gm)].map((m) => (m[1] ?? '').trim()).filter((t) => t !== '');
331
350
  const taskScenarios = scenarioTitles(sectionBetween(taskContent, 'Acceptance Criteria'));
@@ -343,6 +362,10 @@ type AcIdentityResolution = { ok: true; canonical: string } | { ok: false; error
343
362
  * alias — accepted only against a real scenario ordinal, and refused with an
344
363
  * actionable diagnostic when task and feature ordinals disagree. Undeclared
345
364
  * `ACn` tokens, paraphrases and invented ordinals never resolve.
365
+ * `ACn` tokens, paraphrases and invented ordinals never resolve.
366
+ *
367
+ * Declared forms recognized in the index include the bold-trajectory
368
+ * `**AC id**` paragraph (task 0817 R3); the AC-N failure hint names it.
346
369
  */
347
370
  function resolveAcIdentity(rowId: string, index: AcIdentityIndex): AcIdentityResolution {
348
371
  const canonical = index.byTitle.get(normalizeAcTitle(rowId));
@@ -360,7 +383,7 @@ function resolveAcIdentity(rowId: string, index: AcIdentityIndex): AcIdentityRes
360
383
  ok: false,
361
384
  error:
362
385
  `AC id "${rowId}" uses the AC-${n} positional alias but no scenario exists at that ordinal ` +
363
- '(task scenario list and linked-feature scenario list) — cite the exact scenario title or checklist label',
386
+ '(task scenario list and linked-feature scenario list) — cite the exact scenario title, checklist label, or a bare `**AC id**` paragraph',
364
387
  };
365
388
  }
366
389
  if (candidates.length > 1) {
@@ -94,6 +94,12 @@ Resolve `<scope>` and `--pillar`; confirm the repository root; establish **audit
94
94
  numbered-document mutation of any kind. With `--resolve`, no write happens until a repair set is
95
95
  presented, explicitly confirmed, and freshness-revalidated.
96
96
 
97
+ Validate every enum flag against the domain declared by the command surface
98
+ (`plugins/sp/commands/dev-find-conflict.md`): `--pillar` ∈ `source|tasks|features|authority|all`,
99
+ `--mode` ∈ `adaptive|full`, `--agent` ∈ `inline|auto|name`. An out-of-domain value **refuses** the
100
+ audit before any discovery work — report the received value, the flag's valid domain, and stop;
101
+ never silently coerce or ignore it. Only `<scope>` is free-form and exempt from this check.
102
+
97
103
  ### Step 2 — Discover local authority
98
104
 
99
105
  Read entry/process rules (`AGENTS.md`, `docs/99_PROJECT_CONSTITUTION.md`) before interpreting any
@@ -166,6 +166,6 @@ Read the skill file and follow the workflow manually.
166
166
  ## Additional Resources
167
167
 
168
168
  - **Script source:** [scripts/daily-summary/daily-summary.ts](../../scripts/daily-summary/daily-summary.ts) — CLI implementation
169
- - **Tests:** [tests/daily-summary.test.ts](tests/daily-summary.test.ts) — unit coverage for parsing, date ranges, markdown output
169
+ - **Tests:** [tests/daily-summary/daily-summary.test.ts](../../tests/daily-summary/daily-summary.test.ts) — unit coverage for parsing, date ranges, markdown output
170
170
  - **Related skills:** `sp:dev-handover` (blocker handoff), `sp:dev-changelog` (commit-based changelog), `sp:spur-cli` (task management)
171
171
  - **Upstream CLI:** [ccusage](https://github.com/ryoppippi/ccusage) — AI agent token usage reporter
@@ -4,7 +4,7 @@ description: "Evolve docs/00-05 + AGENTS.md per docs/99_PROJECT_CONSTITUTION.md:
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: spur
7
- version: "1.0"
7
+ version: "1.1"
8
8
  platforms: "claude-code,codex,openclaw,opencode,antigravity"
9
9
  interactions:
10
10
  - reviewer
@@ -30,17 +30,17 @@ this skill applies it.
30
30
 
31
31
  **Read `docs/99_PROJECT_CONSTITUTION.md` first.** It is the single source of truth for *how* these
32
32
  files are maintained (authority §2, doc map §4.1, frontmatter contracts §4.3, sync triggers §5,
33
- per-file edit rules §6, the audit §7, lessons §8). This skill is a runbook for executing §5/§7/§8;
33
+ per-file edit rules §6, the audit §7, lesson routing §8). This skill is a runbook for executing §5/§7/§8;
34
34
  when the two disagree, the constitution wins and this skill is the bug.
35
35
 
36
36
  ## Operations
37
37
 
38
38
  | Operation | What it does | Constitution authority | Deterministic helper |
39
39
  | --------- | ------------ | ---------------------- | -------------------- |
40
- | **drift-audit** | Reality (code/shipped) vs. what a key file says, and cross-doc contradictions | §7 (the 8-item checklist) | `rg` the real CLI/config surface; diff vs `04`/`AGENTS.md`/`00` |
41
- | **sync-check** | Did a change touch the docs its trigger obligates in the same commit? | §5 (triggers T1–T8) | git diff of code/config vs. the matching doc edit |
40
+ | **drift-audit** | Reality (code/shipped) vs. what a key file says, and cross-doc contradictions | §7 | `rg` the real CLI/config surface; diff vs `04`/`AGENTS.md`/`00` |
41
+ | **sync-check** | Did a change touch the docs its trigger obligates in the same commit? | §5 (applicable triggers) | git diff of code/config vs. the matching doc edit |
42
42
  | **contract-verify** | Each doc's frontmatter matches its §4.1 row; `updated_at` is plausible | §4.3 | parse frontmatter; compare `owns`/`authority` vs §4.1; `git log` recency |
43
- | **lesson-append** | Append a dated lesson; dedup; promote recurring ones to a rule | §8 | format-check the line; `rg` for an equivalent before adding |
43
+ | **lesson-append** | Record a useful lesson in existing project context; deduplicate; propose governance changes separately | §8 | format-check the line; `rg` for an equivalent before adding |
44
44
 
45
45
  No thin `dev-docs` command wrapper exists (`dev-operations.md §7`). Invoke this skill directly for
46
46
  an audit or a lesson, or reach it via `/sp:dev-plan`'s docs step and `/sp:spur-init`'s `customize`.
@@ -84,48 +84,40 @@ stubs filled or deliberately documented; `bun run lint` passes where applicable.
84
84
  Walk the §7 checklist. Each item pairs a detection command with the doc it validates:
85
85
 
86
86
  ```bash
87
- # Real CLI surface vs. what 04 / AGENTS.md / 00 claim
87
+ # Real CLI surface vs. owning non-UI contracts
88
88
  rg -n "\.command\('" apps/cli/src/commands/ # the true verb list
89
- rg -n '^#### `spur ' docs/04_DESIGN.md # documented commands
89
+ rg -n '^#### `spur ' docs/design/ # documented commands
90
90
  # → diff the two sets; a verb in code but not in 04 is T3 drift.
91
91
 
92
92
  # 05 status rows vs. reality
93
- rg -n '✅|🔶|⏳|💤' docs/05_FEATURES.md # claimed states
93
+ cat docs/features/INDEX.md # generated feature states
94
94
  # → spot-check each ✅/🔶 against code; confirm no ⏳ quietly shipped.
95
95
 
96
96
  # 02 phase bullets name real things (no dead names)
97
97
  # 03 module descriptions vs. the real tree
98
- fd -t d -d 2 . apps packages # real modules
98
+ rg --files apps packages # real modules
99
99
  # Frontmatter contracts (see contract-verify) + updated_at recency
100
100
  git log -1 --format='%ci' -- docs/04_DESIGN.md # last touch vs. recent surface changes
101
101
  ```
102
102
 
103
- **Repair protocol (§7, always this order):** fix the **authoritative** doc first (append-only files
104
- by dated amendment, never rewrite), then the derived docs that restate/sequence it, then
105
- `AGENTS.md`, then **flag what drifted and why** in the commit/task (a silent fix hides the systemic
106
- cause). Anything systemic becomes a §8 lesson or, if it recurs, a new §6 rule.
103
+ **Repair protocol (§7):** fix the authoritative statement first, then affected detail/index/entry
104
+ files. Preserve ADR numbers, original titles/dates and decision history; editorial condensation
105
+ follows §6.1, while actual reversals need a superseding decision. Record findings in a task/report.
106
+ Routine lessons go to existing learning/context storage (§8); no automatic constitution edits.
107
107
 
108
108
  Output a **drift report**: per finding, `{ doc, what code says, what the doc says, authority, repair
109
109
  }`. A clean report lists the checks run and that each returned no delta.
110
110
 
111
111
  ## sync-check (§5)
112
112
 
113
- Given a change (a diff, or a just-finished task), check the same-commit obligations:
113
+ Read the live constitution §5; it owns the trigger table. Classify each changed fact, identify
114
+ its owner, and inspect the diff to confirm required synchronization. Update an index or AGENTS.md
115
+ only when its own facts changed. A satellite edit with an unchanged index pointer is synchronized.
114
116
 
115
- | If the change… | Trigger | …the same commit must touch |
116
- | -------------- | ------- | --------------------------- |
117
- | adds/changes a command, flag, config key, env var, schema, DTO | **T3** | `04_DESIGN` + the `AGENTS.md` surface block |
118
- | ships a feature or changes its state | **T4** | its `05` row (+ a `01` scope row if new surface) |
119
- | makes a new cross-cutting decision (or reverses one) | **T1** | `00` first (dated), then `03`, `01` if scope shifts |
120
- | would contradict an existing ADR | **T2** | **stop** — add the superseding ADR entry first |
121
- | completes/reorders a phase | **T5** | `02` (the real shipped name) |
122
- | adds/cuts/defers scope | **T6** | `01` + placement in `02` |
123
- | changes the doc map or process | **T7** | this file → re-sync `AGENTS.md` (§4.4) → siblings |
124
- | plans a multi-wave batch | **T8** | schedule "doc sync" as an explicit work item |
125
-
126
- Detection is a diff read: list the changed code/config paths, map each to its trigger, then confirm
127
- the obligated doc was edited in the same change. A surface change with no `04` edit is the canonical
128
- miss (the one this whole §5 table exists to prevent).
117
+ T1 applies only to real architectural choices passing §6.1. Feature approvals, task completion,
118
+ bugfixes restoring an existing contract and verification receipts do not justify ADR entries.
119
+ T7 applies only to operator-authorized governance corrections under §6.8; a routine doc edit or
120
+ lesson does not justify touching the constitution. Portable changes include in-scope init templates.
129
121
 
130
122
  ## contract-verify (§4.3)
131
123
 
@@ -140,19 +132,13 @@ stale given recent commits?"
140
132
 
141
133
  ## lesson-append (§8)
142
134
 
143
- Append a lesson to the right per-file section of §8:
144
-
145
- ```
146
- - [YYYY-MM-DD] <project>: <lesson — what went wrong / what to do instead>
147
- ```
135
+ Read constitution §8 for the existing project's destination. Record a useful, deduplicated
136
+ lesson in existing learning/context storage or a dated report, with evidence. Do not append
137
+ lessons to the constitution. Do not create a second learning ledger when one already exists.
148
138
 
149
- - **Low threshold** when in doubt, append. **Check for an equivalent first** (`rg` the section);
150
- bump its date instead of duplicating.
151
- - **Promotion is the only sanctioned deletion:** a lesson that recurs or hardens into practice is
152
- promoted into a §6 rule (or §5 trigger) and removed from §8. Lessons are the inbox; §5/§6 are the
153
- law.
154
- - Lessons carry project provenance (this file is byte-identical across projects except §8 + the §3
155
- tool column) — a lesson from one project is a warning, not yet a law, for the others.
139
+ A recurring lesson may justify proposing a document-governance correction; apply it only within
140
+ operator-authorized §6.8 scope. Task receipts remain in task records. Fresh project templates
141
+ contain neither inherited lessons nor fabricated decisions/status claims.
156
142
 
157
143
  ## What this skill is NOT
158
144
 
@@ -15,41 +15,33 @@ it enforces; the skill never invents process.
15
15
 
16
16
  | Operation | Authority § | What "done" means |
17
17
  | --------- | ----------- | ----------------- |
18
- | drift-audit | §7 | the 8-item §7 checklist run, each backed by a command; report lists deltas (or the zero-delta commands) |
19
- | sync-check | §5 (T1–T8) | every changed surface mapped to its trigger; the obligated doc confirmed edited in the same change |
18
+ | drift-audit | §7 | the affected §7 checks run, each backed by a command; report lists deltas (or the zero-delta commands) |
19
+ | sync-check | §5 (applicable §5 triggers) | every changed surface mapped to its trigger; the obligated doc confirmed edited in the same change |
20
20
  | contract-verify | §4.3 (+ §4.1) | each doc's frontmatter `owns`/`authority` matches its §4.1 row; `updated_at` plausible |
21
- | lesson-append | §8 | a dedup'd, correctly-formatted dated line in the right per-file section |
21
+ | lesson-append | §8 | a deduplicated lesson in existing learning/context storage, never appended to 99 |
22
22
 
23
23
  ## drift-audit — §7 checklist → detection commands
24
24
 
25
25
  | §7 item | Detection (deterministic) | Authoritative doc |
26
26
  | ------- | ------------------------- | ----------------- |
27
- | Real CLI surface vs docs | `rg -n "\.command\('" apps/cli/src/commands/` vs `rg -n '^#### \`spur ' docs/04_DESIGN.md` | `04` + `AGENTS.md` surface, `00` committed-surface |
28
- | `05` ✅/🔶 spot-check; no quietly shipped | `rg -n '✅\|🔶\|⏳\|💤' docs/05_FEATURES.md`, then check each against code | `05` |
27
+ | Real CLI surface vs docs | Compare source-local help/registrations with owning `docs/design/` contracts | `04` satellites |
28
+ | Feature state matches evidence | Read the feature tool's generated index, then inspect affected acceptance evidence | `05` / feature records |
29
29
  | Every shipped surface has a `01` scope row | surface set (above) vs `rg` of `01` scope table | `01` |
30
30
  | `02` phase bullets name real things | read `02` current-phase bullets; grep each name in code/docs | `02` |
31
- | `03` modules vs real tree | `fd -t d -d 2 . apps packages` vs `03` module list | `03` |
31
+ | `03` modules vs real tree | `rg --files apps packages` vs `03` module map | `03` |
32
32
  | `04` covers every command/flag/config/schema | the verb/flag/config set vs `04` | `04` |
33
33
  | `AGENTS.md` doc map == §4.1 | diff the two tables | `AGENTS.md` (§4.4) |
34
34
  | frontmatter matches §4.1 + `updated_at` plausible | see contract-verify | each doc (§4.3) |
35
35
 
36
36
  **Judgment:** is a candidate real drift (vs. an intentional, documented exception)? Which doc is
37
- authoritative? What is the *minimal* repair (a one-line dated amendment beats a rewrite)?
37
+ authoritative? What is the *minimal* repair (preserve decision history and condense only editorial noise under §6.1)?
38
38
 
39
- ## sync-check — T1–T8 → obligations
39
+ ## sync-check — applicable §5 triggers → obligations
40
40
 
41
- Read the change (diff or finished task); for each changed path classify the trigger and confirm the
42
- obligated doc was touched in the same change. The high-frequency miss is **T3** (a CLI/config/schema
43
- change without the matching `04` + `AGENTS.md` edit) check it first. (T1–T8 listed in the SKILL.md
44
- table; this reference is the detection recipe, not a restatement of the triggers.)
45
-
46
- ```bash
47
- # Surface changed in this diff?
48
- git diff --name-only | rg 'apps/cli/src/commands/|packages/.*/schema|config/'
49
- # Was 04 / AGENTS.md touched in the same diff?
50
- git diff --name-only | rg 'docs/04_DESIGN.md|^AGENTS.md'
51
- # Both non-empty → likely synced; surface-changed-but-no-04 → T3 drift.
52
- ```
41
+ Read the diff and the live constitution §5. Map changed facts to their owning document and
42
+ check that contract, not merely whether a filename appears in the diff. Unchanged index pointers
43
+ and entry guidance need no edits. T7 additionally requires the governance reason and existing
44
+ operator authorization under §6.8; include affected templates.
53
45
 
54
46
  ## contract-verify — §4.3
55
47
 
@@ -65,15 +57,10 @@ Compare `owns`/`authority` against the §4.1 row (verbatim in meaning; §4.1 win
65
57
 
66
58
  ## lesson-append — §8
67
59
 
68
- ```
69
- - [YYYY-MM-DD] <project>: <lesson what went wrong / what to do instead>
70
- ```
71
-
72
- 1. Identify the per-file `### Lessons for <doc>` section (or the cross-cutting one).
73
- 2. `rg` that section for an equivalent lesson — if found, **bump its date**, don't duplicate.
74
- 3. Append the formatted line. If the lesson restates an existing §6 rule, it's already law — skip.
75
- 4. If it has recurred, **promote** it to a §6 rule / §5 trigger and remove from §8 (the only
76
- sanctioned deletion).
60
+ 1. Resolve the existing learning/context destination from the project's conventions.
61
+ 2. Search for an equivalent lesson; skip duplicates and routine completion receipts.
62
+ 3. Record the useful lesson with evidence outside the constitution.
63
+ 4. Propose any governance correction separately; recurrence does not authorize a §6.8 edit.
77
64
 
78
65
  ## Drift-report shape
79
66
 
@@ -84,7 +71,7 @@ Checks run: <n> (§7 items) · Findings: <m>
84
71
 
85
72
  | # | Doc | Reality says | Doc says | Authority | Trigger | Repair |
86
73
  |---|-----|--------------|----------|-----------|---------|--------|
87
- | 1 | 04_DESIGN | `spur foo` exists (apps/cli/.../foo.ts) | absent | 04 (T3) | | add the §6.4 command block |
74
+ | 1 | 04_DESIGN | Changed command contract | Satellite describes old behavior | 04 | T3 | update its owning satellite under §6.5 |
88
75
 
89
76
  Zero-finding checks: <list the §7 items that returned no delta, with the command used>
90
77
  ```
@@ -67,6 +67,20 @@ frontmatter scalar.
67
67
  wholesale. No inline-body flag. Section names: `Background`, `Requirements`, `Acceptance Criteria`, `Q&A`, `Design`, `Plan`, `Solution`, `Testing`, `Review`, `References`, `History`, `Notes`.
68
68
  - **Frontmatter** (`--feature <id>`, `--priority <p>`): sets the scalar frontmatter field on an
69
69
  existing task — the only post-create path, allow-listed to `feature_id` / `parent_wbs` / `priority`.
70
+ - **AC controls** (`--ac-altitude <graduating|task-local>`, `--ac-numbering task-local`) — independent
71
+ of each other (task 0818 R5). `--ac-altitude task-local` skips the **DD-09 feature-AC subset** rule
72
+ because the task's scenarios are intentionally not the feature's ship criteria; `--ac-numbering
73
+ task-local` opts the task into the **Requirements↔AC coverage** check inside the task. Setting one
74
+ never implies the other. `graduating` remains the default and DD-09 stays enforced for graduating
75
+ tasks. Use it for an issue/fix-batch task that is genuinely linked to a feature but whose
76
+ regression scenarios sit below that feature's ship criteria, and record the rationale in the task
77
+ body:
78
+
79
+ ```bash
80
+ # One frontmatter flag per call — `update` sets a single field and ignores the rest.
81
+ bun run apps/cli/src/index.ts task update 0818 --feature D6 --json
82
+ bun run apps/cli/src/index.ts task update 0818 --ac-altitude task-local --json
83
+ ```
70
84
 
71
85
  Exit code `2` when neither mode's required args are supplied (e.g. `--section` without `--from-file`,
72
86
  or no status and no `--section`/frontmatter flag).
@@ -183,7 +197,9 @@ traceability. Bare = whole corpus; with a WBS = one task. The matrix is loaded f
183
197
 
184
198
  **L4 traceability** resolves `feature_id` / `parent_wbs` / `dependencies` edges and checks **AC
185
199
  coverage** (DD-09): a task's scenarios must be a subset of its linked feature's AC by normalized
186
- title — orphans warn by default.
200
+ title — orphans warn by default. A task declaring `ac_altitude: task-local` is exempt from that
201
+ subset rule only (`--ac-altitude`, above); every graduating task is still enforced, and the exemption
202
+ does not touch `ac_numbering`'s Requirements↔AC coverage or any other layer.
187
203
 
188
204
  `--json` emits an array of per-task results:
189
205