@warnyin/sdlc 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 (2026-08-25)
4
+
5
+ - **`--auto` on every pipeline stage.** `/sdlc:auto` already ran the whole pipeline,
6
+ but it stopped at every escalation — so you were pulled back in three or four times
7
+ per change and typed each stage anyway. Now all seven stage commands take `--auto`:
8
+ the run gathers what it needs, confirms once, and goes to ship. The confirmation is
9
+ decidable item by item — scope as understood, the tier and why, every ambiguity with
10
+ the assumption to be acted on, and each escalation as its own refusable line, the
11
+ ship row naming the hard-floor surface it covers instead of hiding behind a general
12
+ "run without me". Nothing is written before you confirm, down to the active-change
13
+ pointer, so declining leaves the repository untouched. The approval covers that run
14
+ only — not config, not the next change, not a resume. Anything outside what you
15
+ confirmed still stops and asks.
16
+ - Escalations passed unattended are journalled, counted by `/sdlc:observe` as
17
+ `unattended×N`, and listed in the digest: the record shows where a human would
18
+ normally have stood and, that run, did not.
19
+ - **Verify and review outcomes now record how they were produced** (`mode=panel|solo`).
20
+ A journal that says "verify passed" hides the thing a reader most needs later —
21
+ whether that verdict came from independent reviewers or from the same loop that
22
+ wrote the code. `observe` marks such changes `self-judged`, and the digest must name
23
+ self-produced outcomes. Absent provenance reads as unknown, never as `panel`, so
24
+ older journals are not retroactively dressed up as independently reviewed; where
25
+ provenance is mixed, the weakest link decides.
26
+ - Where a panel cannot run, the playbooks now say to judge in the main loop and record
27
+ that — not to skip the stage. A review that never happened is worse than one
28
+ labelled honestly.
29
+ - The constitution gains a hard rule: human-written text SHALL NOT reach a shell as an
30
+ argument. It is the defect that got past two separate gates in 0.4.0.
31
+
3
32
  ## 0.4.0 (2026-08-25)
4
33
 
5
34
  - **New stage command `/sdlc:feedback`** — reports a bug, a rough edge, or a missing
package/README.md CHANGED
@@ -31,8 +31,14 @@ Then in your coding agent:
31
31
  /sdlc:init # interview → constitution + harness (the one human gate)
32
32
  /sdlc:auto Add rate limiting # AI runs new → contract → build → verify → ship
33
33
  /sdlc:auto add-rate-limiting # already opened it with /sdlc:new? auto resumes from there
34
+ /sdlc:new Add rate limiting --auto # any stage takes --auto: confirm once, then run to ship
34
35
  ```
35
36
 
37
+ `--auto` asks everything up front — scope, tier, each ambiguity, and every escalation
38
+ it wants pre-approved as its own line you can refuse — then runs unattended. Nothing
39
+ is written until you confirm, the approval covers that run only, and anything you did
40
+ not pre-approve still stops and asks.
41
+
36
42
  ## How it works
37
43
 
38
44
  ```
package/lib/observe.mjs CHANGED
@@ -1,174 +1,187 @@
1
- // Observability aggregation — reads journals + context files, computes the
2
- // numbers /sdlc:observe reports. Pure given an sdlc root; no LLM involved.
3
-
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import { parseFrontmatter } from './frontmatter.mjs';
7
- import { CAPS, countEffectiveLines } from './caps.mjs';
8
-
9
- function readJournal(dir) {
10
- const p = path.join(dir, 'journal.ndjson');
11
- if (!fs.existsSync(p)) return [];
12
- return fs.readFileSync(p, 'utf8').split('\n').filter(Boolean).map((l) => {
13
- try { return JSON.parse(l); } catch { return null; }
14
- }).filter(Boolean);
15
- }
16
-
17
- function summarizeChange(dir, id, archived) {
18
- const events = readJournal(dir);
19
- const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
20
- let costUsd = 0;
21
- let costKnown = false;
22
- let sessions = 0;
23
- const verify = { rounds: 0, firstPass: null };
24
- let guards = 0;
25
- let compacts = 0;
26
-
27
- for (const e of events) {
28
- if (e.event === 'session') {
29
- sessions++;
30
- for (const k of Object.keys(tokens)) tokens[k] += e.totals?.[k] ?? 0;
31
- if (typeof e.costUsd === 'number') { costUsd += e.costUsd; costKnown = true; }
32
- } else if (e.event === 'verify') {
33
- verify.rounds++;
34
- if (verify.firstPass === null) verify.firstPass = e.result === 'pass';
35
- } else if (e.event === 'guard') guards++;
36
- else if (e.event === 'compact') compacts++;
37
- }
38
-
39
- const first = events[0]?.ts ? Date.parse(events[0].ts) : null;
40
- const shipEvent = events.find((e) => e.event === 'ship');
41
- const shippedAt = shipEvent?.ts ? Date.parse(shipEvent.ts) : null;
42
- const leadTimeMs = first != null && shippedAt != null ? shippedAt - first : null;
43
-
44
- let tier = null;
45
- let status = null;
46
- const changePath = path.join(dir, 'change.md');
47
- if (fs.existsSync(changePath)) {
48
- const { data } = parseFrontmatter(fs.readFileSync(changePath, 'utf8'));
49
- tier = data.tier ?? null;
50
- status = data.status ?? null;
51
- }
52
-
53
- return {
54
- id, archived, tier, status, sessions, tokens,
55
- costUsd: costKnown ? Number(costUsd.toFixed(4)) : null,
56
- verify, guards, compacts, leadTimeMs,
57
- digest: archived ? fs.existsSync(path.join(dir, 'digest.md')) : null,
58
- };
59
- }
60
-
61
- export function buildReport(sdlcRoot) {
62
- const changes = [];
63
-
64
- const changesDir = path.join(sdlcRoot, 'changes');
65
- if (fs.existsSync(changesDir)) {
66
- for (const d of fs.readdirSync(changesDir, { withFileTypes: true })) {
67
- if (!d.isDirectory() || d.name === 'archive') continue;
68
- changes.push(summarizeChange(path.join(changesDir, d.name), d.name, false));
69
- }
70
- const archiveDir = path.join(changesDir, 'archive');
71
- if (fs.existsSync(archiveDir)) {
72
- for (const d of fs.readdirSync(archiveDir, { withFileTypes: true })) {
73
- if (!d.isDirectory()) continue;
74
- changes.push(summarizeChange(path.join(archiveDir, d.name), d.name, true));
75
- }
76
- }
77
- }
78
-
79
- // Residency: constitution + always-steering effective lines vs budget.
80
- let alwaysLines = 0;
81
- const constitutionPath = path.join(sdlcRoot, 'context', 'constitution.md');
82
- if (fs.existsSync(constitutionPath)) {
83
- alwaysLines += countEffectiveLines(fs.readFileSync(constitutionPath, 'utf8'));
84
- }
85
- const steering = [];
86
- const pointerHits = new Map();
87
- for (const c of changes) {
88
- const dir = c.archived
89
- ? path.join(sdlcRoot, 'changes', 'archive', c.id)
90
- : path.join(sdlcRoot, 'changes', c.id);
91
- for (const e of readJournal(dir)) {
92
- if (e.event === 'pointer' && e.steering) {
93
- pointerHits.set(e.steering, (pointerHits.get(e.steering) ?? 0) + 1);
94
- }
95
- }
96
- }
97
- // Global journal (events with no active change) counts too.
98
- for (const e of readJournal(path.join(sdlcRoot, '.state'))) {
99
- if (e.event === 'pointer' && e.steering) {
100
- pointerHits.set(e.steering, (pointerHits.get(e.steering) ?? 0) + 1);
101
- }
102
- }
103
-
104
- const steeringDir = path.join(sdlcRoot, 'context', 'steering');
105
- if (fs.existsSync(steeringDir)) {
106
- for (const f of fs.readdirSync(steeringDir).filter((n) => n.endsWith('.md')).sort()) {
107
- const raw = fs.readFileSync(path.join(steeringDir, f), 'utf8');
108
- const { data } = parseFrontmatter(raw);
109
- const inclusion = data.inclusion ?? 'manual';
110
- if (inclusion === 'always') alwaysLines += countEffectiveLines(raw);
111
- steering.push({ file: f, inclusion, pointerHits: pointerHits.get(f) ?? 0 });
112
- }
113
- }
114
-
115
- // Flags plain strings the playbook can surface with suggested fixes.
116
- const flags = [];
117
- if (alwaysLines > CAPS.alwaysBudget) {
118
- flags.push(`residency: always-loaded is ${alwaysLines}/${CAPS.alwaysBudget} lines — distill via /sdlc:steer`);
119
- }
120
- const shipped = changes.filter((c) => c.archived);
121
- for (const s of steering.filter((s) => s.inclusion === 'paths' && s.pointerHits === 0)) {
122
- if (shipped.length >= 2) {
123
- flags.push(`steering/${s.file}: zero pointer hits across ${shipped.length} shipped changes — demote or delete`);
124
- }
125
- }
126
- for (const c of changes) {
127
- if (c.compacts > 0) flags.push(`${c.id}: ${c.compacts} compact event(s) — context overflowed, find the resident artifact`);
128
- if (c.verify.rounds > 3) flags.push(`${c.id}: ${c.verify.rounds} verify rounds — contract or routing needs attention`);
129
- }
130
- for (const c of shipped.filter((c) => c.digest === false)) {
131
- flags.push(`${c.id}: archived without digest.md — ship playbook step 4 was skipped`);
132
- }
133
-
134
- const firstPassRuns = changes.filter((c) => c.verify.firstPass !== null);
135
- const summary = {
136
- active: changes.filter((c) => !c.archived).length,
137
- shipped: shipped.length,
138
- firstPassRate: firstPassRuns.length
139
- ? Number((firstPassRuns.filter((c) => c.verify.firstPass).length / firstPassRuns.length).toFixed(2))
140
- : null,
141
- };
142
-
143
- return {
144
- summary,
145
- changes,
146
- residency: { alwaysLines, budget: CAPS.alwaysBudget },
147
- steering,
148
- flags,
149
- };
150
- }
151
-
152
- const fmtK = (n) => (n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M`
153
- : n >= 1_000 ? `${(n / 1_000).toFixed(1)}k` : String(n));
154
-
155
- export function renderReport(report) {
156
- const lines = [];
157
- const { summary, residency } = report;
158
- lines.push(`changes: ${summary.active} active · ${summary.shipped} shipped`
159
- + (summary.firstPassRate != null ? ` · first-pass ${(summary.firstPassRate * 100).toFixed(0)}%` : ''));
160
- lines.push(`residency: ${residency.alwaysLines}/${residency.budget} always-loaded lines`);
161
- for (const c of report.changes) {
162
- const t = c.tokens;
163
- const lead = c.leadTimeMs != null ? ` · lead ${(c.leadTimeMs / 3_600_000).toFixed(1)}h` : '';
164
- lines.push(` ${c.archived ? '✓' : '·'} ${c.id} [${c.tier ?? '?'}]`
165
- + ` ${fmtK(t.input)}in/${fmtK(t.output)}out`
166
- + (c.costUsd != null ? ` $${c.costUsd}` : '')
167
- + ` · verify×${c.verify.rounds}${lead}`);
168
- }
169
- for (const s of report.steering) {
170
- lines.push(` steering/${s.file} [${s.inclusion}] hits:${s.pointerHits}`);
171
- }
172
- for (const f of report.flags) lines.push(` ⚠ ${f}`);
173
- return lines.join('\n');
174
- }
1
+ // Observability aggregation — reads journals + context files, computes the
2
+ // numbers /sdlc:observe reports. Pure given an sdlc root; no LLM involved.
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { parseFrontmatter } from './frontmatter.mjs';
7
+ import { CAPS, countEffectiveLines } from './caps.mjs';
8
+
9
+ function readJournal(dir) {
10
+ const p = path.join(dir, 'journal.ndjson');
11
+ if (!fs.existsSync(p)) return [];
12
+ return fs.readFileSync(p, 'utf8').split('\n').filter(Boolean).map((l) => {
13
+ try { return JSON.parse(l); } catch { return null; }
14
+ }).filter(Boolean);
15
+ }
16
+
17
+ function summarizeChange(dir, id, archived) {
18
+ const events = readJournal(dir);
19
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
20
+ let costUsd = 0;
21
+ let costKnown = false;
22
+ let sessions = 0;
23
+ const verify = { rounds: 0, firstPass: null };
24
+ // null = the journal never said; a run that predates provenance must not be
25
+ // read as independently judged.
26
+ let selfJudged = null;
27
+ // escalations the run passed without a human, because one was pre-approved.
28
+ let preauthorized = 0;
29
+ let guards = 0;
30
+ let compacts = 0;
31
+
32
+ for (const e of events) {
33
+ if (e.event === 'escalation' && e.preauth === 'yes') preauthorized++;
34
+ if (e.event === 'verify' || e.event === 'review') {
35
+ // the weakest link decides: one self-produced judgment marks the change.
36
+ if (e.mode === 'solo') selfJudged = true;
37
+ else if (e.mode === 'panel' && selfJudged === null) selfJudged = false;
38
+ }
39
+ if (e.event === 'session') {
40
+ sessions++;
41
+ for (const k of Object.keys(tokens)) tokens[k] += e.totals?.[k] ?? 0;
42
+ if (typeof e.costUsd === 'number') { costUsd += e.costUsd; costKnown = true; }
43
+ } else if (e.event === 'verify') {
44
+ verify.rounds++;
45
+ if (verify.firstPass === null) verify.firstPass = e.result === 'pass';
46
+ } else if (e.event === 'guard') guards++;
47
+ else if (e.event === 'compact') compacts++;
48
+ }
49
+
50
+ const first = events[0]?.ts ? Date.parse(events[0].ts) : null;
51
+ const shipEvent = events.find((e) => e.event === 'ship');
52
+ const shippedAt = shipEvent?.ts ? Date.parse(shipEvent.ts) : null;
53
+ const leadTimeMs = first != null && shippedAt != null ? shippedAt - first : null;
54
+
55
+ let tier = null;
56
+ let status = null;
57
+ const changePath = path.join(dir, 'change.md');
58
+ if (fs.existsSync(changePath)) {
59
+ const { data } = parseFrontmatter(fs.readFileSync(changePath, 'utf8'));
60
+ tier = data.tier ?? null;
61
+ status = data.status ?? null;
62
+ }
63
+
64
+ return {
65
+ id, archived, tier, status, sessions, tokens,
66
+ costUsd: costKnown ? Number(costUsd.toFixed(4)) : null,
67
+ verify, selfJudged, preauthorized, guards, compacts, leadTimeMs,
68
+ digest: archived ? fs.existsSync(path.join(dir, 'digest.md')) : null,
69
+ };
70
+ }
71
+
72
+ export function buildReport(sdlcRoot) {
73
+ const changes = [];
74
+
75
+ const changesDir = path.join(sdlcRoot, 'changes');
76
+ if (fs.existsSync(changesDir)) {
77
+ for (const d of fs.readdirSync(changesDir, { withFileTypes: true })) {
78
+ if (!d.isDirectory() || d.name === 'archive') continue;
79
+ changes.push(summarizeChange(path.join(changesDir, d.name), d.name, false));
80
+ }
81
+ const archiveDir = path.join(changesDir, 'archive');
82
+ if (fs.existsSync(archiveDir)) {
83
+ for (const d of fs.readdirSync(archiveDir, { withFileTypes: true })) {
84
+ if (!d.isDirectory()) continue;
85
+ changes.push(summarizeChange(path.join(archiveDir, d.name), d.name, true));
86
+ }
87
+ }
88
+ }
89
+
90
+ // Residency: constitution + always-steering effective lines vs budget.
91
+ let alwaysLines = 0;
92
+ const constitutionPath = path.join(sdlcRoot, 'context', 'constitution.md');
93
+ if (fs.existsSync(constitutionPath)) {
94
+ alwaysLines += countEffectiveLines(fs.readFileSync(constitutionPath, 'utf8'));
95
+ }
96
+ const steering = [];
97
+ const pointerHits = new Map();
98
+ for (const c of changes) {
99
+ const dir = c.archived
100
+ ? path.join(sdlcRoot, 'changes', 'archive', c.id)
101
+ : path.join(sdlcRoot, 'changes', c.id);
102
+ for (const e of readJournal(dir)) {
103
+ if (e.event === 'pointer' && e.steering) {
104
+ pointerHits.set(e.steering, (pointerHits.get(e.steering) ?? 0) + 1);
105
+ }
106
+ }
107
+ }
108
+ // Global journal (events with no active change) counts too.
109
+ for (const e of readJournal(path.join(sdlcRoot, '.state'))) {
110
+ if (e.event === 'pointer' && e.steering) {
111
+ pointerHits.set(e.steering, (pointerHits.get(e.steering) ?? 0) + 1);
112
+ }
113
+ }
114
+
115
+ const steeringDir = path.join(sdlcRoot, 'context', 'steering');
116
+ if (fs.existsSync(steeringDir)) {
117
+ for (const f of fs.readdirSync(steeringDir).filter((n) => n.endsWith('.md')).sort()) {
118
+ const raw = fs.readFileSync(path.join(steeringDir, f), 'utf8');
119
+ const { data } = parseFrontmatter(raw);
120
+ const inclusion = data.inclusion ?? 'manual';
121
+ if (inclusion === 'always') alwaysLines += countEffectiveLines(raw);
122
+ steering.push({ file: f, inclusion, pointerHits: pointerHits.get(f) ?? 0 });
123
+ }
124
+ }
125
+
126
+ // Flags plain strings the playbook can surface with suggested fixes.
127
+ const flags = [];
128
+ if (alwaysLines > CAPS.alwaysBudget) {
129
+ flags.push(`residency: always-loaded is ${alwaysLines}/${CAPS.alwaysBudget} lines — distill via /sdlc:steer`);
130
+ }
131
+ const shipped = changes.filter((c) => c.archived);
132
+ for (const s of steering.filter((s) => s.inclusion === 'paths' && s.pointerHits === 0)) {
133
+ if (shipped.length >= 2) {
134
+ flags.push(`steering/${s.file}: zero pointer hits across ${shipped.length} shipped changes — demote or delete`);
135
+ }
136
+ }
137
+ for (const c of changes) {
138
+ if (c.compacts > 0) flags.push(`${c.id}: ${c.compacts} compact event(s) — context overflowed, find the resident artifact`);
139
+ if (c.verify.rounds > 3) flags.push(`${c.id}: ${c.verify.rounds} verify rounds — contract or routing needs attention`);
140
+ }
141
+ for (const c of shipped.filter((c) => c.digest === false)) {
142
+ flags.push(`${c.id}: archived without digest.md — ship playbook step 4 was skipped`);
143
+ }
144
+
145
+ const firstPassRuns = changes.filter((c) => c.verify.firstPass !== null);
146
+ const summary = {
147
+ active: changes.filter((c) => !c.archived).length,
148
+ shipped: shipped.length,
149
+ firstPassRate: firstPassRuns.length
150
+ ? Number((firstPassRuns.filter((c) => c.verify.firstPass).length / firstPassRuns.length).toFixed(2))
151
+ : null,
152
+ };
153
+
154
+ return {
155
+ summary,
156
+ changes,
157
+ residency: { alwaysLines, budget: CAPS.alwaysBudget },
158
+ steering,
159
+ flags,
160
+ };
161
+ }
162
+
163
+ const fmtK = (n) => (n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M`
164
+ : n >= 1_000 ? `${(n / 1_000).toFixed(1)}k` : String(n));
165
+
166
+ export function renderReport(report) {
167
+ const lines = [];
168
+ const { summary, residency } = report;
169
+ lines.push(`changes: ${summary.active} active · ${summary.shipped} shipped`
170
+ + (summary.firstPassRate != null ? ` · first-pass ${(summary.firstPassRate * 100).toFixed(0)}%` : ''));
171
+ lines.push(`residency: ${residency.alwaysLines}/${residency.budget} always-loaded lines`);
172
+ for (const c of report.changes) {
173
+ const t = c.tokens;
174
+ const lead = c.leadTimeMs != null ? ` · lead ${(c.leadTimeMs / 3_600_000).toFixed(1)}h` : '';
175
+ lines.push(` ${c.archived ? '✓' : '·'} ${c.id} [${c.tier ?? '?'}]`
176
+ + ` ${fmtK(t.input)}in/${fmtK(t.output)}out`
177
+ + (c.costUsd != null ? ` $${c.costUsd}` : '')
178
+ + ` · verify×${c.verify.rounds}${c.selfJudged === true ? ' · self-judged' : ''}`
179
+ + (c.preauthorized > 0 ? ` · unattended×${c.preauthorized}` : '')
180
+ + `${lead}`);
181
+ }
182
+ for (const s of report.steering) {
183
+ lines.push(` steering/${s.file} [${s.inclusion}] hits:${s.pointerHits}`);
184
+ }
185
+ for (const f of report.flags) lines.push(` ⚠ ${f}`);
186
+ return lines.join('\n');
187
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warnyin/sdlc",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Spec-driven, AI-driven SDLC framework — token-lean specs, contract-first changes, autonomous pipeline with managed hooks. Operationalizes the Day-1 'New SDLC with Vibe Coding' work process.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
- ---
2
- description: Implement — conductor in-session or orchestrator fan-out per [P] wave
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/build.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Implement — conductor in-session or orchestrator fan-out per [P] wave
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/build.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Write tests + evals BEFORE code, generate failing tests, adversarial check (the contract)
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/contract.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Write tests + evals BEFORE code, generate failing tests, adversarial check (the contract)
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/contract.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Record design decisions & trade-offs (deep tier or on signal)
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/design.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Record design decisions & trade-offs (deep tier or on signal)
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/design.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Open a change — triage tier, write Why + Delta spec + Tasks (Requirements)
3
- argument-hint: "<title>"
4
- ---
5
- Read `sdlc/.playbook/new.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Open a change — triage tier, write Why + Delta spec + Tasks (Requirements)
3
+ argument-hint: "<title> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/new.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Signal-triggered agent panel: architect / security / quality / ops (read-only)
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/review.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Signal-triggered agent panel: architect / security / quality / ops (read-only)
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/review.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Merge Delta into living specs, archive, run the learner, write the digest
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/ship.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Merge Delta into living specs, archive, run the learner, write the digest
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/ship.md` and execute it now. Arguments: $ARGUMENTS
@@ -1,5 +1,5 @@
1
- ---
2
- description: Run the contract: full tests + eval rubric; failures route back to build (max 3 rounds)
3
- argument-hint: "<change-id>"
4
- ---
5
- Read `sdlc/.playbook/verify.md` and execute it now. Arguments: $ARGUMENTS
1
+ ---
2
+ description: Run the contract: full tests + eval rubric; failures route back to build (max 3 rounds)
3
+ argument-hint: "<change-id> [--auto]"
4
+ ---
5
+ Read `sdlc/.playbook/verify.md` and execute it now. Arguments: $ARGUMENTS
@@ -10,6 +10,7 @@ new → [design] → contract → build → verify → [review] → ship
10
10
  |---|---|---|---|---|
11
11
  | /sdlc:init | Configure harness | interview | constitution, harness.md | human approves (once) |
12
12
  | /sdlc:auto | whole loop | status (resumes an open change) | everything below | escalation only |
13
+ | any stage `--auto` | that stage → ship | status + your answers | everything from there | one confirmation up front |
13
14
  | /sdlc:new | Requirements | specs Purpose headers | change.md | validator: delta + assumptions |
14
15
  | /sdlc:design | Architecture | change + touched specs | change.md § Design | escalate irreversible only |
15
16
  | /sdlc:contract | Contract-first | change.md | contract/*, failing tests | adversarial panel + validator |
@@ -1,31 +1,66 @@
1
- # /sdlc:auto <title|change-id> — the whole pipeline, one command
2
-
3
- Runs new → [design] → contract → build → verify → [review] → ship, each stage by
4
- its own playbook, WITHOUT pausing for the human except on the Autonomy-policy
5
- escalation conditions:
6
-
7
- - hard-floor surface (security, payments, data-loss, irreversible),
8
- - a `[NEEDS CLARIFICATION]` the agent cannot resolve alone,
9
- - verify failed more than 3 rounds,
10
- - token budget exceeded (if the user set one),
11
- - ship policy requires human approval (deep tier).
12
-
13
- Entry stage resolve this first, never assume `new`:
14
- - Run `npx @warnyin/sdlc status`. If the argument names an active change (or one
15
- is active and the argument describes it), RESUME: map its status to the entry
16
- stage with `next.md` §2 and start the pipeline there.
17
- - Resume never rewrites an existing `change.md` a change already triaged keeps
18
- its tier, Delta and Assumptions. Re-run a stage only if its output is missing
19
- or the validator rejects it.
20
- - Start at `new` only when the argument matches no active change.
21
-
22
- Rules:
23
- - Announce the plan in ≤3 lines after triage (id, tier, task count, entry stage),
24
- so a resume is never silent. Then work.
25
- - Between stages run `npx @warnyin/sdlc validate <id>` a red validator is a
26
- hard stop for that stage, not a suggestion.
27
- - On escalation: stop at the exact step, state what is needed in ≤5 lines, wait.
28
- When the human answers, resume from that step never restart the pipeline.
29
- - On completion report one line: shipped + digest path + total cost if known.
30
-
31
- This is orchestrator mode: the human describes the outcome and walks away.
1
+ # /sdlc:auto <title|change-id> — the whole pipeline, one command
2
+
3
+ Runs new → [design] → contract → build → verify → [review] → ship, each stage by
4
+ its own playbook, WITHOUT pausing for the human except on the Autonomy-policy
5
+ escalation conditions — each with the choice `--auto` may pre-approve:
6
+
7
+ | Condition | Pre-approvable as |
8
+ |---|---|
9
+ | a `[NEEDS CLARIFICATION]` the agent cannot resolve alone | assume-safe and continue · or stop and ask |
10
+ | verify failed more than 3 rounds | keep iterating · or stop |
11
+ | review found blockers | fix and continue · or stop for the human |
12
+ | ship needs human approval (deep tier / hard-floor: security, payments, data-loss, irreversible) | ship · or stop before ship |
13
+ | token budget exceeded (if the user set one) | continue · or stop |
14
+
15
+ `/sdlc:auto` and any stage command given `--auto` run in unattended mode below.
16
+
17
+ ## Unattended mode gather, confirm, run (in that order)
18
+
19
+ 1. **Gather.** Triage the tier, read `npx @warnyin/sdlc status`, resolve the entry
20
+ stage, and collect every question you would otherwise raise mid-run. This step
21
+ writes NOTHING: no `change.md`, no journal entry, no gate, no active-change
22
+ pointer. A run that never gets confirmed must leave the repository unchanged.
23
+ 2. **Confirm.** One message, and it must be decidable item by item:
24
+ - scope as you understood it, and the tier you triaged with its reason
25
+ - every ambiguity, each with the assumption you intend to act on
26
+ - one line per row of the escalation table above, each stating the choice you
27
+ want pre-approved, and each refusable on its own. For the ship row, name the
28
+ hard-floor surface it covers never fold it into a general "run without me".
29
+ If the human declines or edits any item, nothing has been written yet: revise
30
+ the summary and ask again, or stop. Do not start work on a partial yes.
31
+ 3. **Run.** Only now write anything. Work stage by stage to ship.
32
+
33
+ **Pre-authorization covers this run only.** It is never persisted to config, never
34
+ remembered for the next change, and never inherited by a resumed run — a resume
35
+ asks again. A condition outside the confirmed set stops the run and asks, exactly
36
+ as if no flag had been passed; treat that as a fact, not a judgement call.
37
+
38
+ Record every escalation you reach:
39
+ `node sdlc/.hooks/journal.mjs note escalation condition=<name> preauth=<yes|no>`
40
+ — `yes` when a pre-approval let you pass it, `no` when you stopped and asked.
41
+
42
+ Entry stage — resolve this first, never assume `new`:
43
+ - Run `npx @warnyin/sdlc status`. If the argument names an active change (or one
44
+ is active and the argument describes it), RESUME: map its status to the entry
45
+ stage with `next.md` §2 and start the pipeline there.
46
+ - Resume never rewrites an existing `change.md` — a change already triaged keeps
47
+ its tier, Delta and Assumptions. Re-run a stage only if its output is missing
48
+ or the validator rejects it.
49
+ - Start at `new` only when the argument matches no active change.
50
+ - With `--auto` on a stage command: the stage still does its own work first, then
51
+ the pipeline continues from there. A stage typed earlier than what the change's
52
+ status maps to is skipped with a one-line announcement, never re-run — the
53
+ pipeline is a ratchet.
54
+
55
+ Rules:
56
+ - Announce the plan in ≤3 lines after triage (id, tier, task count, entry stage),
57
+ so a resume is never silent. Then work.
58
+ - Between stages run `npx @warnyin/sdlc validate <id>` — a red validator is a
59
+ hard stop for that stage, not a suggestion.
60
+ - On escalation NOT pre-approved for this run: stop at the exact step, state what
61
+ is needed in ≤5 lines, wait. When the human answers, resume from that step —
62
+ never restart the pipeline. One that IS pre-approved: take the approved choice,
63
+ journal it, and keep going without asking.
64
+ - On completion report one line: shipped + digest path + total cost if known.
65
+
66
+ This is orchestrator mode: the human describes the outcome and walks away.
@@ -1,23 +1,26 @@
1
- # /sdlc:build <id> — implement (Run the harness)
2
-
3
- Precondition: status ≥ contracted (vibe tier is exempt from contracts).
4
- Set `status: building`.
5
-
6
- Mode by size:
7
- - **Conductor** (≤2 tasks): implement in this session, task by task.
8
- - **Orchestrator** (>2 tasks): fan out one `sdlc-builder` subagent per task in a
9
- `[P]` wave; serialize between dependent waves. Each builder receives ONLY:
10
- its task line, `contract/tests.md`, the touched capability's spec, and steering
11
- matching its file area — never the whole change history.
12
-
13
- Rules for whoever implements:
14
- - Follow steering pointers the moment the PostToolUse hook emits them.
15
- - Never edit `sdlc/specs/**`, archive, journals, or lint/test configs to go
16
- green — hooks deny the first two; the rest is the config-protection rule.
17
- - Per-task self-check = that task's tests + lint only; the full test run belongs
18
- to /sdlc:verify (moved, not removed).
19
- - Tick `- [x]` in `## Tasks` as each task lands; note surprises in one line max.
20
- - Honor `[tier:x]` markers when delegating (see routing.md).
21
-
22
- Done when all tasks are ticked and the code compiles/lints.
23
- `node sdlc/.hooks/journal.mjs note build tasks=<n>` then → /sdlc:verify.
1
+ # /sdlc:build <id> — implement (Run the harness)
2
+
3
+ Precondition: status ≥ contracted (vibe tier is exempt from contracts).
4
+ Set `status: building`.
5
+
6
+ Mode by size:
7
+ - **Conductor** (≤2 tasks): implement in this session, task by task.
8
+ - **Orchestrator** (>2 tasks): fan out one `sdlc-builder` subagent per task in a
9
+ `[P]` wave; serialize between dependent waves. Each builder receives ONLY:
10
+ its task line, `contract/tests.md`, the touched capability's spec, and steering
11
+ matching its file area — never the whole change history.
12
+
13
+ Rules for whoever implements:
14
+ - Follow steering pointers the moment the PostToolUse hook emits them.
15
+ - Never edit `sdlc/specs/**`, archive, journals, or lint/test configs to go
16
+ green — hooks deny the first two; the rest is the config-protection rule.
17
+ - Per-task self-check = that task's tests + lint only; the full test run belongs
18
+ to /sdlc:verify (moved, not removed).
19
+ - Tick `- [x]` in `## Tasks` as each task lands; note surprises in one line max.
20
+ - Honor `[tier:x]` markers when delegating (see routing.md).
21
+
22
+ Done when all tasks are ticked and the code compiles/lints.
23
+ `node sdlc/.hooks/journal.mjs note build tasks=<n>` then → /sdlc:verify.
24
+
25
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
26
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,23 +1,26 @@
1
- # /sdlc:contract <id> — tests + evals before code
2
-
3
- The contract IS the handshake. Nothing in `## Tasks` may be implemented while
4
- status is `new`.
5
-
6
- 1. From the Delta scenarios, write `contract/tests.md` (≤60 lines): one row per
7
- behavior — Given/When/Then, kind, mapped requirement. List what is explicitly
8
- out of scope and why.
9
- 2. Deep tier (standard optional): write `contract/evals.md` (≤40 lines) — the
10
- trajectory + quality rubric the sdlc-evaluator will score.
11
- 3. Generate failing tests: delegate to the `sdlc-contractor` agent (cheap tier)
12
- with ONLY tests.md + the delta + the project's test conventions. Run the test
13
- command from `sdlc/harness.md` — every new test must FAIL (red) now; a test
14
- that passes before implementation tests nothing.
15
- 4. Adversarial check instead of human approval: ask the `sdlc-quality` agent to
16
- attack the contract — uncovered scenarios, untestable rows, missing edge
17
- cases vs the delta. Fix findings; one round is usually enough, two max.
18
- 5. `npx @warnyin/sdlc validate <id>` clean → set frontmatter `status: contracted`
19
- and `node sdlc/.hooks/journal.mjs note contract tests=<n>`.
20
-
21
- Escalate only if the delta itself turns out ambiguous (back to /sdlc:new step 5).
22
-
23
- Next: /sdlc:build.
1
+ # /sdlc:contract <id> — tests + evals before code
2
+
3
+ The contract IS the handshake. Nothing in `## Tasks` may be implemented while
4
+ status is `new`.
5
+
6
+ 1. From the Delta scenarios, write `contract/tests.md` (≤60 lines): one row per
7
+ behavior — Given/When/Then, kind, mapped requirement. List what is explicitly
8
+ out of scope and why.
9
+ 2. Deep tier (standard optional): write `contract/evals.md` (≤40 lines) — the
10
+ trajectory + quality rubric the sdlc-evaluator will score.
11
+ 3. Generate failing tests: delegate to the `sdlc-contractor` agent (cheap tier)
12
+ with ONLY tests.md + the delta + the project's test conventions. Run the test
13
+ command from `sdlc/harness.md` — every new test must FAIL (red) now; a test
14
+ that passes before implementation tests nothing.
15
+ 4. Adversarial check instead of human approval: ask the `sdlc-quality` agent to
16
+ attack the contract — uncovered scenarios, untestable rows, missing edge
17
+ cases vs the delta. Fix findings; one round is usually enough, two max.
18
+ 5. `npx @warnyin/sdlc validate <id>` clean → set frontmatter `status: contracted`
19
+ and `node sdlc/.hooks/journal.mjs note contract tests=<n>`.
20
+
21
+ Escalate only if the delta itself turns out ambiguous (back to /sdlc:new step 5).
22
+
23
+ Next: /sdlc:build.
24
+
25
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
26
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,20 +1,23 @@
1
- # /sdlc:design <id> — decisions & trade-offs (deep tier, or on signal)
2
-
3
- Run only when: tier is deep, OR the change needs an architectural decision
4
- (new dependency, schema change, cross-capability contract). Otherwise skip —
5
- an empty Design section is garbage.
6
-
7
- 1. Read `change.md`, the full spec of every touched capability, and any steering
8
- whose scope matches. Nothing else by default.
9
- 2. Gather in parallel, judge serially: fan out read-only subagents for research
10
- (one per question: prior art in this repo, external constraint, data shape).
11
- The DECISION is made in the main loop — never delegated, never parallel.
12
- 3. Fill `## Design` with decision lines only:
13
- `- decision: <what> · alternatives: <a/b> · because: <why>`
14
- Never restate the delta. Respect the tier cap (deep total ≤150).
15
- 4. Escalate to the human ONLY for decisions listed in
16
- `sdlc/harness.md § Autonomy policy` (irreversible or hard-floor). Everything
17
- else: decide, record, move on.
18
- 5. `npx @warnyin/sdlc validate <id>`.
19
-
20
- Next: /sdlc:contract.
1
+ # /sdlc:design <id> — decisions & trade-offs (deep tier, or on signal)
2
+
3
+ Run only when: tier is deep, OR the change needs an architectural decision
4
+ (new dependency, schema change, cross-capability contract). Otherwise skip —
5
+ an empty Design section is garbage.
6
+
7
+ 1. Read `change.md`, the full spec of every touched capability, and any steering
8
+ whose scope matches. Nothing else by default.
9
+ 2. Gather in parallel, judge serially: fan out read-only subagents for research
10
+ (one per question: prior art in this repo, external constraint, data shape).
11
+ The DECISION is made in the main loop — never delegated, never parallel.
12
+ 3. Fill `## Design` with decision lines only:
13
+ `- decision: <what> · alternatives: <a/b> · because: <why>`
14
+ Never restate the delta. Respect the tier cap (deep total ≤150).
15
+ 4. Escalate to the human ONLY for decisions listed in
16
+ `sdlc/harness.md § Autonomy policy` (irreversible or hard-floor). Everything
17
+ else: decide, record, move on.
18
+ 5. `npx @warnyin/sdlc validate <id>`.
19
+
20
+ Next: /sdlc:contract.
21
+
22
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
23
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,22 +1,25 @@
1
- # /sdlc:new <title> — open a change (Requirements)
2
-
3
- 1. Triage the tier with `sdlc/harness.md § Tier triage`. Hard-floor surface
4
- (security, payments, data-loss, irreversible) forces `deep` — no override
5
- without an explicit user instruction (record it in Assumptions).
6
- 2. Create `sdlc/changes/<kebab-id>/change.md` from the tier's template at
7
- `sdlc/.playbook/templates/change-{vibe|standard|deep}.md` — copy the
8
- structure exactly, respect the cap comment.
9
- 3. Ground the delta: grep `## Purpose` of every `sdlc/specs/*/spec.md`; open the
10
- FULL spec only for capabilities this change touches. Name each `## Delta:`
11
- after an existing capability, or a new kebab-case capability.
12
- 4. Write Why (≤5 lines, no solutioning) and the Delta requirements
13
- (`ADDED/MODIFIED/REMOVED Requirement` + WHEN/THEN scenarios — grammar in the
14
- delta-spec-format skill). Then Tasks with `[P]` and `[tier:x]` markers.
15
- 5. Ambiguity policy (AI-driven): make the safest assumption and record it under
16
- `## Assumptions` with why it is safe. Use `[NEEDS CLARIFICATION: q]` ONLY for
17
- facts you cannot obtain or safely assume — then ask the user those questions
18
- now, in one batch, and resolve every marker.
19
- 6. `node sdlc/.hooks/journal.mjs set-active <id>` then
20
- `npx @warnyin/sdlc validate <id>` — fix errors. Status stays `new`.
21
-
22
- Next: deep tier or risky decision → /sdlc:design; otherwise /sdlc:contract.
1
+ # /sdlc:new <title> — open a change (Requirements)
2
+
3
+ 1. Triage the tier with `sdlc/harness.md § Tier triage`. Hard-floor surface
4
+ (security, payments, data-loss, irreversible) forces `deep` — no override
5
+ without an explicit user instruction (record it in Assumptions).
6
+ 2. Create `sdlc/changes/<kebab-id>/change.md` from the tier's template at
7
+ `sdlc/.playbook/templates/change-{vibe|standard|deep}.md` — copy the
8
+ structure exactly, respect the cap comment.
9
+ 3. Ground the delta: grep `## Purpose` of every `sdlc/specs/*/spec.md`; open the
10
+ FULL spec only for capabilities this change touches. Name each `## Delta:`
11
+ after an existing capability, or a new kebab-case capability.
12
+ 4. Write Why (≤5 lines, no solutioning) and the Delta requirements
13
+ (`ADDED/MODIFIED/REMOVED Requirement` + WHEN/THEN scenarios — grammar in the
14
+ delta-spec-format skill). Then Tasks with `[P]` and `[tier:x]` markers.
15
+ 5. Ambiguity policy (AI-driven): make the safest assumption and record it under
16
+ `## Assumptions` with why it is safe. Use `[NEEDS CLARIFICATION: q]` ONLY for
17
+ facts you cannot obtain or safely assume — then ask the user those questions
18
+ now, in one batch, and resolve every marker.
19
+ 6. `node sdlc/.hooks/journal.mjs set-active <id>` then
20
+ `npx @warnyin/sdlc validate <id>` — fix errors. Status stays `new`.
21
+
22
+ Next: deep tier or risky decision → /sdlc:design; otherwise /sdlc:contract.
23
+
24
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
25
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,12 +1,14 @@
1
- # /sdlc:next — where am I, what now (read-only)
2
-
3
- 1. Run `npx @warnyin/sdlc status`.
4
- 2. For each active change map status → next command:
5
- - `new` + markers unresolved → resolve questions (playbook new.md §5)
6
- - `new` (clean) → /sdlc:design (deep/signal) or /sdlc:contract
7
- - `contracted` → /sdlc:build
8
- - `building` → /sdlc:build (finish open tasks)
9
- - `verified` → /sdlc:review (if signals) or /sdlc:ship
10
- 3. If nothing is active: suggest /sdlc:new, or /sdlc:observe if archived changes
11
- have unread digests.
12
- 4. Answer in ≤5 lines. Create or modify nothing.
1
+ # /sdlc:next — where am I, what now (read-only)
2
+
3
+ 1. Run `npx @warnyin/sdlc status`.
4
+ 2. For each active change map status → next command:
5
+ - `new` + markers unresolved → resolve questions (playbook new.md §5)
6
+ - `new` (clean) → /sdlc:design (deep/signal) or /sdlc:contract
7
+ - `contracted` → /sdlc:build
8
+ - `building` → /sdlc:build (finish open tasks)
9
+ - `verified` → /sdlc:review (if signals) or /sdlc:ship
10
+ 3. If nothing is active: suggest /sdlc:new, or /sdlc:observe if archived changes
11
+ have unread digests.
12
+ 4. Answer in ≤5 lines. Create or modify nothing.
13
+ 5. When the remaining path is more than one stage, add one line: the same command
14
+ with `--auto` confirms once and runs to ship.
@@ -1,17 +1,26 @@
1
- # /sdlc:review <id> — agent panel (signal-triggered)
2
-
3
- Run when: tier deep, OR the diff touches auth/payments/data handling, OR >10
4
- files changed. Otherwise skip silently — a ceremonial review is garbage.
5
-
6
- 1. Fan out in parallel, all read-only, each with the diff + change.md only:
7
- - `sdlc-architect` (deepest): design integrity, coupling, contract drift.
8
- - `sdlc-security` (balanced): injection, authz, secrets, unsafe deps.
9
- - `sdlc-quality` (cheap): contract coverage gaps, edge cases, dead code.
10
- - `sdlc-ops` (cheap): config, migrations, rollback, observability impact.
11
- 2. Merge findings in the main loop. Classify: blocker | improvement | note.
12
- 3. Blockers → append as fix tasks and route back to /sdlc:build (counts toward
13
- the same 3-round budget as verify). Improvements: apply if ≤5 min each,
14
- otherwise record one line in the change for the digest.
15
- 4. `node sdlc/.hooks/journal.mjs note review blockers=<n>`.
16
-
17
- Pass condition: zero open blockers. Next: /sdlc:ship.
1
+ # /sdlc:review <id> — agent panel (signal-triggered)
2
+
3
+ Run when: tier deep, OR the diff touches auth/payments/data handling, OR >10
4
+ files changed. Otherwise skip silently — a ceremonial review is garbage.
5
+
6
+ 1. Fan out in parallel, all read-only, each with the diff + change.md only:
7
+ - `sdlc-architect` (deepest): design integrity, coupling, contract drift.
8
+ - `sdlc-security` (balanced): injection, authz, secrets, unsafe deps.
9
+ - `sdlc-quality` (cheap): contract coverage gaps, edge cases, dead code.
10
+ - `sdlc-ops` (cheap): config, migrations, rollback, observability impact.
11
+ 2. Merge findings in the main loop. Classify: blocker | improvement | note.
12
+ 3. Blockers → append as fix tasks and route back to /sdlc:build (counts toward
13
+ the same 3-round budget as verify). Improvements: apply if ≤5 min each,
14
+ otherwise record one line in the change for the digest.
15
+ 4. `node sdlc/.hooks/journal.mjs note review blockers=<n> mode=<panel|solo>`.
16
+ `mode=panel` when the four agents produced the findings; `mode=solo` when the
17
+ panel cannot run subagents unavailable or disallowed — and the main loop
18
+ reviewed its own work through those four lenses instead. Run it that way
19
+ rather than skipping the review, and say so in the note: a self-review that
20
+ is recorded as a panel is worse than no review, because it reads as
21
+ independent evidence months later.
22
+
23
+ Pass condition: zero open blockers. Next: /sdlc:ship.
24
+
25
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
26
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,24 +1,33 @@
1
- # /sdlc:ship <id> — merge, archive, learn, digest
2
-
3
- Precondition: `status: verified` (+ review passed when it ran).
4
-
5
- 1. **Policy check** (`sdlc/harness.md § Autonomy policy`): if this change is NOT
6
- auto-shippable (deep/hard-floor), show the human a 5-line summary (why, delta
7
- heads, verify result, cost so far) and wait for approval. Otherwise proceed.
8
- 2. Open the gate and archive mechanically:
9
- `node sdlc/.hooks/journal.mjs open-ship <id>`
10
- `npx @warnyin/sdlc archive <id>`
11
- (validates --strict, merges every Delta into `sdlc/specs/`, promotes evals,
12
- stamps `status: shipped`, moves the folder to `changes/archive/<date>-<id>/`).
13
- 3. **Learn** — delegate to `sdlc-learner` (cheap) with the archived change.md +
14
- its journal.ndjson. It proposes ≤3 items: add-rule (with evidence pointer) /
15
- expire-or-demote (rule or steering that never fired) / harness tweak.
16
- Apply reductions and demotions immediately (they always save tokens).
17
- Additions to always-loaded context are NOT applied — list them in the digest.
18
- 4. **Digest** — write `sdlc/changes/archive/<date>-<id>/digest.md` (≤15 lines):
19
- what shipped, spec deltas merged, assumptions made, verify rounds, tokens/cost
20
- (from journal `session` events), learner proposals awaiting the human.
21
- 5. Close the gate: `node sdlc/.hooks/journal.mjs close`. Tell the user in one
22
- line: shipped + where the digest is.
23
-
24
- The digest is the async human touchpoint reviewable and revertible later.
1
+ # /sdlc:ship <id> — merge, archive, learn, digest
2
+
3
+ Precondition: `status: verified` (+ review passed when it ran).
4
+
5
+ 1. **Policy check** (`sdlc/harness.md § Autonomy policy`): if this change is NOT
6
+ auto-shippable (deep/hard-floor), show the human a 5-line summary (why, delta
7
+ heads, verify result, cost so far) and wait for approval. Otherwise proceed.
8
+ 2. Open the gate and archive mechanically:
9
+ `node sdlc/.hooks/journal.mjs open-ship <id>`
10
+ `npx @warnyin/sdlc archive <id>`
11
+ (validates --strict, merges every Delta into `sdlc/specs/`, promotes evals,
12
+ stamps `status: shipped`, moves the folder to `changes/archive/<date>-<id>/`).
13
+ 3. **Learn** — delegate to `sdlc-learner` (cheap) with the archived change.md +
14
+ its journal.ndjson. It proposes ≤3 items: add-rule (with evidence pointer) /
15
+ expire-or-demote (rule or steering that never fired) / harness tweak.
16
+ Apply reductions and demotions immediately (they always save tokens).
17
+ Additions to always-loaded context are NOT applied — list them in the digest.
18
+ 4. **Digest** — write `sdlc/changes/archive/<date>-<id>/digest.md` (≤15 lines):
19
+ what shipped, spec deltas merged, assumptions made, verify rounds, tokens/cost
20
+ (from journal `session` events), learner proposals awaiting the human.
21
+ When any verify or review note carries `mode=solo`, the digest SHALL say which
22
+ outcomes were self-produced. A reader months from now cannot otherwise tell a
23
+ panel's verdict from the author's own.
24
+ When any `escalation` event carries `preauth=yes`, the digest SHALL list those
25
+ pre-authorized escalations by condition — the points where a human would normally
26
+ have stood and, this run, did not.
27
+ 5. Close the gate: `node sdlc/.hooks/journal.mjs close`. Tell the user in one
28
+ line: shipped + where the digest is.
29
+
30
+ The digest is the async human touchpoint — reviewable and revertible later.
31
+
32
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
33
+ mode — gather, confirm once, run. The stage still does its own work first.
@@ -1,24 +1,37 @@
1
- # /sdlc:verify <id> — the feedback loop
2
-
3
- Two halves, both must pass. Verification is against the CONTRACT, not vibes.
4
-
5
- 1. **Tests (deterministic)**: run the full test command from `sdlc/harness.md`.
6
- Every row of `contract/tests.md` must be covered by a passing test.
7
- 2. **Evals (non-deterministic)** — when `contract/evals.md` exists: delegate to
8
- the `sdlc-evaluator` agent (cheap) with the rubric + the diff + the task log;
9
- it returns a score per rubric line. Pass bar is written in the file.
10
-
11
- On failure:
12
- - Cluster failures by root cause (one line each) and append the cluster note to
13
- the change's `## Tasks` area as unchecked fix tasks.
14
- - `node sdlc/.hooks/journal.mjs note verify result=fail round=<n>`
15
- - Route back to /sdlc:build. Maximum 3 rounds total; on the 4th failure STOP and
16
- escalate to the human with the cluster history (Autonomy policy condition).
17
- - Never lower the bar: do not edit tests/evals to pass unless the contract
18
- itself was wrong changing the contract reopens the adversarial check.
19
-
20
- On pass: set `status: verified`,
21
- `node sdlc/.hooks/journal.mjs note verify result=pass round=<n>`.
22
-
23
- Next: review signals present (deep tier, security-touching diff, >10 files)
24
- → /sdlc:review; otherwise → /sdlc:ship.
1
+ # /sdlc:verify <id> — the feedback loop
2
+
3
+ Two halves, both must pass. Verification is against the CONTRACT, not vibes.
4
+
5
+ 1. **Tests (deterministic)**: run the full test command from `sdlc/harness.md`.
6
+ Every row of `contract/tests.md` must be covered by a passing test.
7
+ 2. **Evals (non-deterministic)** — when `contract/evals.md` exists: delegate to
8
+ the `sdlc-evaluator` agent (cheap) with the rubric + the diff + the task log;
9
+ it returns a score per rubric line. Pass bar is written in the file.
10
+ If the evaluator cannot run — subagents unavailable or disallowed in this
11
+ session — score in the main loop instead and record that. A panel that could
12
+ not run is a fact to write down, never a reason to stop the pipeline; but a
13
+ run that judged its own work is weaker evidence and must not read as if a
14
+ panel had agreed.
15
+
16
+ On failure:
17
+ - Cluster failures by root cause (one line each) and append the cluster note to
18
+ the change's `## Tasks` area as unchecked fix tasks.
19
+ - `node sdlc/.hooks/journal.mjs note verify result=fail round=<n> mode=<panel|solo>`
20
+ - Route back to /sdlc:build. Maximum 3 rounds total; on the 4th failure STOP and
21
+ escalate to the human with the cluster history (Autonomy policy condition).
22
+ - Never lower the bar: do not edit tests/evals to pass unless the contract
23
+ itself was wrong changing the contract reopens the adversarial check.
24
+
25
+ On pass: set `status: verified`,
26
+ `node sdlc/.hooks/journal.mjs note verify result=pass round=<n> mode=<panel|solo>`.
27
+
28
+ `mode=panel` only when independent agents produced the judgment; `mode=solo` when
29
+ the main loop judged its own work. Every verify note carries it, pass or fail —
30
+ `/sdlc:observe` reports a change as self-judged from this field, and omitting it
31
+ leaves the record silently indistinguishable from an independent one.
32
+
33
+ Next: review signals present (deep tier, security-touching diff, >10 files)
34
+ → /sdlc:review; otherwise → /sdlc:ship.
35
+
36
+ `--auto`: do this stage, then continue to ship under `auto.md`'s unattended
37
+ mode — gather, confirm once, run. The stage still does its own work first.