@warnyin/sdlc 0.4.0 → 0.5.1
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 +39 -0
- package/README.md +6 -0
- package/lib/observe.mjs +187 -174
- package/lib/usage.mjs +49 -46
- package/package.json +1 -1
- package/payload/adapters/claude/commands/sdlc/build.md +5 -5
- package/payload/adapters/claude/commands/sdlc/contract.md +5 -5
- package/payload/adapters/claude/commands/sdlc/design.md +5 -5
- package/payload/adapters/claude/commands/sdlc/new.md +5 -5
- package/payload/adapters/claude/commands/sdlc/review.md +5 -5
- package/payload/adapters/claude/commands/sdlc/ship.md +5 -5
- package/payload/adapters/claude/commands/sdlc/verify.md +5 -5
- package/payload/playbook/README.md +1 -0
- package/payload/playbook/auto.md +66 -31
- package/payload/playbook/build.md +26 -23
- package/payload/playbook/contract.md +26 -23
- package/payload/playbook/design.md +23 -20
- package/payload/playbook/new.md +25 -22
- package/payload/playbook/next.md +14 -12
- package/payload/playbook/review.md +26 -17
- package/payload/playbook/ship.md +33 -24
- package/payload/playbook/verify.md +37 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 (2026-08-25)
|
|
4
|
+
|
|
5
|
+
- **Fix (cost)**: `costUsd()` never charged cache-write tokens, the highest-rate of
|
|
6
|
+
the four classes the usage parser collects. Every cost `/sdlc:observe` and the
|
|
7
|
+
session summary have printed was therefore low. A model priced without a
|
|
8
|
+
`cacheWrite` rate now charges nothing for that class rather than inferring one from
|
|
9
|
+
`input` — the module's rule is never to guess a price, and a guess reports as
|
|
10
|
+
confidently as a known rate. Existing journalled costs are left alone: backfilling
|
|
11
|
+
would rewrite history from a rate that was not in force at the time.
|
|
12
|
+
|
|
13
|
+
## 0.5.0 (2026-08-25)
|
|
14
|
+
|
|
15
|
+
- **`--auto` on every pipeline stage.** `/sdlc:auto` already ran the whole pipeline,
|
|
16
|
+
but it stopped at every escalation — so you were pulled back in three or four times
|
|
17
|
+
per change and typed each stage anyway. Now all seven stage commands take `--auto`:
|
|
18
|
+
the run gathers what it needs, confirms once, and goes to ship. The confirmation is
|
|
19
|
+
decidable item by item — scope as understood, the tier and why, every ambiguity with
|
|
20
|
+
the assumption to be acted on, and each escalation as its own refusable line, the
|
|
21
|
+
ship row naming the hard-floor surface it covers instead of hiding behind a general
|
|
22
|
+
"run without me". Nothing is written before you confirm, down to the active-change
|
|
23
|
+
pointer, so declining leaves the repository untouched. The approval covers that run
|
|
24
|
+
only — not config, not the next change, not a resume. Anything outside what you
|
|
25
|
+
confirmed still stops and asks.
|
|
26
|
+
- Escalations passed unattended are journalled, counted by `/sdlc:observe` as
|
|
27
|
+
`unattended×N`, and listed in the digest: the record shows where a human would
|
|
28
|
+
normally have stood and, that run, did not.
|
|
29
|
+
- **Verify and review outcomes now record how they were produced** (`mode=panel|solo`).
|
|
30
|
+
A journal that says "verify passed" hides the thing a reader most needs later —
|
|
31
|
+
whether that verdict came from independent reviewers or from the same loop that
|
|
32
|
+
wrote the code. `observe` marks such changes `self-judged`, and the digest must name
|
|
33
|
+
self-produced outcomes. Absent provenance reads as unknown, never as `panel`, so
|
|
34
|
+
older journals are not retroactively dressed up as independently reviewed; where
|
|
35
|
+
provenance is mixed, the weakest link decides.
|
|
36
|
+
- Where a panel cannot run, the playbooks now say to judge in the main loop and record
|
|
37
|
+
that — not to skip the stage. A review that never happened is worse than one
|
|
38
|
+
labelled honestly.
|
|
39
|
+
- The constitution gains a hard rule: human-written text SHALL NOT reach a shell as an
|
|
40
|
+
argument. It is the defect that got past two separate gates in 0.4.0.
|
|
41
|
+
|
|
3
42
|
## 0.4.0 (2026-08-25)
|
|
4
43
|
|
|
5
44
|
- **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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
for (const
|
|
173
|
-
|
|
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/lib/usage.mjs
CHANGED
|
@@ -1,46 +1,49 @@
|
|
|
1
|
-
// Transcript-usage parser: Claude Code transcripts are JSONL; assistant
|
|
2
|
-
// entries carry `message.usage` and `message.model`. We sum per model and
|
|
3
|
-
// price via the optional table in sdlc/config.yaml. Never guess: when a
|
|
4
|
-
// price is unknown, cost stays null (reported as n/a).
|
|
5
|
-
|
|
6
|
-
export function parseTranscriptUsage(jsonlText) {
|
|
7
|
-
const byModel = new Map();
|
|
8
|
-
for (const line of (jsonlText ?? '').split('\n')) {
|
|
9
|
-
if (!line.trim()) continue;
|
|
10
|
-
let entry;
|
|
11
|
-
try { entry = JSON.parse(line); } catch { continue; }
|
|
12
|
-
const msg = entry?.message;
|
|
13
|
-
const usage = msg?.usage;
|
|
14
|
-
if (!usage || typeof usage !== 'object') continue;
|
|
15
|
-
const model = msg.model ?? 'unknown';
|
|
16
|
-
const acc = byModel.get(model) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
17
|
-
acc.input += usage.input_tokens ?? 0;
|
|
18
|
-
acc.output += usage.output_tokens ?? 0;
|
|
19
|
-
acc.cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
20
|
-
acc.cacheWrite += usage.cache_creation_input_tokens ?? 0;
|
|
21
|
-
byModel.set(model, acc);
|
|
22
|
-
}
|
|
23
|
-
const models = Object.fromEntries(byModel);
|
|
24
|
-
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
25
|
-
for (const m of byModel.values()) {
|
|
26
|
-
totals.input += m.input; totals.output += m.output;
|
|
27
|
-
totals.cacheRead += m.cacheRead; totals.cacheWrite += m.cacheWrite;
|
|
28
|
-
}
|
|
29
|
-
return { models, totals };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// prices: { "<model>": { input, output, cacheRead } } in USD per 1M
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
// Transcript-usage parser: Claude Code transcripts are JSONL; assistant
|
|
2
|
+
// entries carry `message.usage` and `message.model`. We sum per model and
|
|
3
|
+
// price via the optional table in sdlc/config.yaml. Never guess: when a
|
|
4
|
+
// price is unknown, cost stays null (reported as n/a).
|
|
5
|
+
|
|
6
|
+
export function parseTranscriptUsage(jsonlText) {
|
|
7
|
+
const byModel = new Map();
|
|
8
|
+
for (const line of (jsonlText ?? '').split('\n')) {
|
|
9
|
+
if (!line.trim()) continue;
|
|
10
|
+
let entry;
|
|
11
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
12
|
+
const msg = entry?.message;
|
|
13
|
+
const usage = msg?.usage;
|
|
14
|
+
if (!usage || typeof usage !== 'object') continue;
|
|
15
|
+
const model = msg.model ?? 'unknown';
|
|
16
|
+
const acc = byModel.get(model) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
17
|
+
acc.input += usage.input_tokens ?? 0;
|
|
18
|
+
acc.output += usage.output_tokens ?? 0;
|
|
19
|
+
acc.cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
20
|
+
acc.cacheWrite += usage.cache_creation_input_tokens ?? 0;
|
|
21
|
+
byModel.set(model, acc);
|
|
22
|
+
}
|
|
23
|
+
const models = Object.fromEntries(byModel);
|
|
24
|
+
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
25
|
+
for (const m of byModel.values()) {
|
|
26
|
+
totals.input += m.input; totals.output += m.output;
|
|
27
|
+
totals.cacheRead += m.cacheRead; totals.cacheWrite += m.cacheWrite;
|
|
28
|
+
}
|
|
29
|
+
return { models, totals };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// prices: { "<model>": { input, output, cacheRead, cacheWrite } } in USD per 1M
|
|
33
|
+
// tokens. A class the table omits is charged nothing — never inferred from another
|
|
34
|
+
// rate, since a guessed price reports as confidently as a known one.
|
|
35
|
+
export function costUsd(usage, prices) {
|
|
36
|
+
if (!prices) return null;
|
|
37
|
+
let usd = 0;
|
|
38
|
+
let priced = false;
|
|
39
|
+
for (const [model, u] of Object.entries(usage.models)) {
|
|
40
|
+
const p = prices[model];
|
|
41
|
+
if (!p) continue;
|
|
42
|
+
priced = true;
|
|
43
|
+
usd += (u.input * (p.input ?? 0)
|
|
44
|
+
+ u.output * (p.output ?? 0)
|
|
45
|
+
+ u.cacheRead * (p.cacheRead ?? 0)
|
|
46
|
+
+ u.cacheWrite * (p.cacheWrite ?? 0)) / 1_000_000;
|
|
47
|
+
}
|
|
48
|
+
return priced ? Number(usd.toFixed(4)) : null;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warnyin/sdlc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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 |
|
package/payload/playbook/auto.md
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
package/payload/playbook/new.md
CHANGED
|
@@ -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.
|
package/payload/playbook/next.md
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/payload/playbook/ship.md
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|