@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/templates/AGENTS.md +26 -23
- package/config/templates/docs/00_ADR.md +13 -23
- package/config/templates/docs/01_PRD.md +5 -2
- package/config/templates/docs/02_ROADMAP.md +9 -13
- package/config/templates/docs/03_ARCHITECTURE.md +2 -2
- package/config/templates/docs/04_DESIGN.md +12 -31
- package/config/templates/docs/05_FEATURES.md +6 -18
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +162 -394
- package/package.json +9 -9
- package/plugins/sp/README.md +12 -7
- package/plugins/sp/agents/super-reviewer.md +43 -8
- package/plugins/sp/commands/dev-refineall.md +1 -0
- package/plugins/sp/commands/dev-run.md +1 -0
- package/plugins/sp/commands/dev-runall.md +1 -0
- package/plugins/sp/commands/dev-verifyall.md +1 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +173 -2
- package/plugins/sp/scripts/batch-preflight.ts +257 -2
- package/plugins/sp/scripts/verify-answer-lint.ts +32 -9
- package/plugins/sp/skills/conflict-finding/SKILL.md +6 -0
- package/plugins/sp/skills/daily-summary/SKILL.md +1 -1
- package/plugins/sp/skills/doc-evolve/SKILL.md +26 -40
- package/plugins/sp/skills/doc-evolve/references/operations.md +17 -30
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +17 -1
- package/plugins/sp/skills/spur-cli/references/tasks.md +31 -1
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +14 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +36 -3
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +12 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +17 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +148 -17
- 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 === '--
|
|
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 {
|
|
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)
|
|
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
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
41
|
-
| **sync-check** | Did a change touch the docs its trigger obligates in the same commit? | §5 (triggers
|
|
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** |
|
|
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.
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
19
|
-
| sync-check | §5 (
|
|
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
|
|
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 |
|
|
28
|
-
|
|
|
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 | `
|
|
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 (
|
|
37
|
+
authoritative? What is the *minimal* repair (preserve decision history and condense only editorial noise under §6.1)?
|
|
38
38
|
|
|
39
|
-
## sync-check —
|
|
39
|
+
## sync-check — applicable §5 triggers → obligations
|
|
40
40
|
|
|
41
|
-
Read the
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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 |
|
|
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
|
|