@iceinvein/agent-skills 0.2.0 → 0.4.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/README.md +2 -2
- package/dist/cli/index.js +14 -10
- package/package.json +1 -1
- package/skills/index.json +4 -4
- package/skills/migrate/README.md +35 -23
- package/skills/migrate/SKILL.md +75 -15
- package/skills/migrate/bin/migrate.ts +90 -0
- package/skills/migrate/docs/architecture.md +61 -26
- package/skills/migrate/docs/reference.md +53 -8
- package/skills/migrate/fixtures/fake-gh.ts +113 -0
- package/skills/migrate/fixtures/flow-target/docs/WORK.md +12 -0
- package/skills/migrate/fixtures/flow-target/docs/modernisation/capability-map/.gitkeep +0 -0
- package/skills/migrate/fixtures/flow-target/tools/flow/src/cli.ts +156 -0
- package/skills/migrate/package.json +1 -1
- package/skills/migrate/references/phases/adjudicate.md +161 -0
- package/skills/migrate/references/phases/handoff.md +220 -0
- package/skills/migrate/references/phases/probe.md +2 -2
- package/skills/migrate/references/phases/queue.md +21 -14
- package/skills/migrate/references/run-ops.md +17 -13
- package/skills/migrate/scripts/__tests__/adapter-flow.test.ts +290 -0
- package/skills/migrate/scripts/__tests__/adapter-github.test.ts +232 -0
- package/skills/migrate/scripts/__tests__/adapter-markdown.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/adjudicate.test.ts +332 -0
- package/skills/migrate/scripts/__tests__/assumptions.test.ts +179 -0
- package/skills/migrate/scripts/__tests__/coverage.test.ts +192 -0
- package/skills/migrate/scripts/__tests__/e2e-express.test.ts +167 -7
- package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +9 -4
- package/skills/migrate/scripts/__tests__/forecast.test.ts +280 -0
- package/skills/migrate/scripts/__tests__/gates-handoff.test.ts +309 -0
- package/skills/migrate/scripts/__tests__/handoff-cmd.test.ts +308 -0
- package/skills/migrate/scripts/__tests__/handoff-order.test.ts +156 -0
- package/skills/migrate/scripts/adapters/flow.ts +280 -0
- package/skills/migrate/scripts/adapters/github.ts +260 -0
- package/skills/migrate/scripts/adapters/markdown.ts +175 -0
- package/skills/migrate/scripts/adjudicate-cmd.ts +243 -0
- package/skills/migrate/scripts/assumptions.ts +188 -0
- package/skills/migrate/scripts/check.ts +119 -320
- package/skills/migrate/scripts/coverage-cmd.ts +86 -0
- package/skills/migrate/scripts/coverage.ts +151 -0
- package/skills/migrate/scripts/dates.ts +17 -0
- package/skills/migrate/scripts/forecast-cmd.ts +124 -0
- package/skills/migrate/scripts/forecast.ts +264 -0
- package/skills/migrate/scripts/gates/adjudication.ts +30 -0
- package/skills/migrate/scripts/gates/census.ts +107 -0
- package/skills/migrate/scripts/gates/citations.ts +11 -0
- package/skills/migrate/scripts/gates/context.ts +76 -0
- package/skills/migrate/scripts/gates/coverage.ts +22 -0
- package/skills/migrate/scripts/gates/deltas.ts +15 -0
- package/skills/migrate/scripts/gates/handoff.ts +145 -0
- package/skills/migrate/scripts/gates/leaks.ts +11 -0
- package/skills/migrate/scripts/gates/parity.ts +15 -0
- package/skills/migrate/scripts/gates/queue.ts +9 -0
- package/skills/migrate/scripts/gates/refs.ts +97 -0
- package/skills/migrate/scripts/gates/run-state.ts +67 -0
- package/skills/migrate/scripts/gates/source.ts +28 -0
- package/skills/migrate/scripts/handoff-cmd.ts +186 -0
- package/skills/migrate/scripts/handoff.ts +330 -0
- package/skills/migrate/scripts/paths.ts +4 -0
- package/skills/migrate/scripts/types.ts +43 -0
- package/skills/migrate/scripts/validate.ts +12 -0
- package/skills/migrate/skill.json +2 -2
- package/skills/migrate/templates/forecast-assumptions.md +59 -0
- package/skills/sluice/SKILL.md +20 -7
- package/skills/sluice/references/deep-channel.md +20 -0
- package/skills/sluice/references/finish.md +4 -2
- package/skills/sluice/references/meter.md +38 -0
- package/skills/sluice/scripts/run-stats.sh +236 -0
- package/skills/sluice/skill.json +4 -3
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { computeCoverage, renderCoverage } from '../coverage.ts'
|
|
3
|
+
import type { HandoffFile } from '../handoff.ts'
|
|
4
|
+
import type { Requirement, Throughput } from '../types.ts'
|
|
5
|
+
|
|
6
|
+
function req(id: string, cap: string, confidence: Requirement['confidence']): Requirement {
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
cap,
|
|
10
|
+
requirement: `requirement ${id}`,
|
|
11
|
+
actors: 'User',
|
|
12
|
+
objects: 'Thing',
|
|
13
|
+
rules: 'none',
|
|
14
|
+
origin: 'intended',
|
|
15
|
+
confidence,
|
|
16
|
+
citations: [],
|
|
17
|
+
parity: { kind: 'rubric', level: 'high' },
|
|
18
|
+
batch: 'b-1',
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const CONFIRMED: Requirement['confidence'] = { kind: 'confirmed' }
|
|
23
|
+
const INFERRED: Requirement['confidence'] = { kind: 'inferred' }
|
|
24
|
+
|
|
25
|
+
const REQS = [
|
|
26
|
+
req('UM-001', 'user-management', CONFIRMED),
|
|
27
|
+
req('UM-002', 'user-management', CONFIRMED),
|
|
28
|
+
req('BI-001', 'billing', CONFIRMED),
|
|
29
|
+
req('BI-002', 'billing', INFERRED),
|
|
30
|
+
req('BI-003', 'billing', INFERRED),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const HANDOFF: HandoffFile = {
|
|
34
|
+
version: 1,
|
|
35
|
+
adapter: 'markdown',
|
|
36
|
+
items: [
|
|
37
|
+
{
|
|
38
|
+
key: 'billing',
|
|
39
|
+
title: 'Billing',
|
|
40
|
+
frs: ['BI-001', 'BI-002', 'BI-003'],
|
|
41
|
+
dependsOn: [],
|
|
42
|
+
weight: 3,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'user-management',
|
|
46
|
+
title: 'User management',
|
|
47
|
+
frs: ['UM-001', 'UM-002'],
|
|
48
|
+
dependsOn: ['billing'],
|
|
49
|
+
weight: 2,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
refs: {},
|
|
53
|
+
basis: { confirmed: 3, emitted: 5, order: ['billing', 'user-management'] },
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const through = (completions: Throughput['completions']): Throughput => ({
|
|
57
|
+
completions,
|
|
58
|
+
basis: 'markdown roadmap checkboxes, dated in file',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('the denominator is confirmed requirements only, with exclusions reported', () => {
|
|
62
|
+
const r = computeCoverage({
|
|
63
|
+
requirements: REQS,
|
|
64
|
+
handoff: HANDOFF,
|
|
65
|
+
throughput: through([
|
|
66
|
+
{ fr: 'UM-001', doneAt: '2026-08-12' },
|
|
67
|
+
{ fr: 'BI-001', doneAt: '2026-08-11' },
|
|
68
|
+
]),
|
|
69
|
+
})
|
|
70
|
+
// Five requirements exist; three are confirmed; two of those are built.
|
|
71
|
+
expect(r.confirmed).toBe(3)
|
|
72
|
+
expect(r.built).toBe(2)
|
|
73
|
+
expect(r.nonConfirmed).toEqual([{ slug: 'billing', count: 2 }])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('a completion for a non-confirmed requirement does not inflate the figure', () => {
|
|
77
|
+
// BI-002 is inferred, so it is outside the denominator. Reporting it as
|
|
78
|
+
// complete must not push built above the confirmed total it sits over.
|
|
79
|
+
const r = computeCoverage({
|
|
80
|
+
requirements: REQS,
|
|
81
|
+
handoff: HANDOFF,
|
|
82
|
+
throughput: through([
|
|
83
|
+
{ fr: 'BI-001', doneAt: '2026-08-11' },
|
|
84
|
+
{ fr: 'BI-002', doneAt: '2026-08-11' },
|
|
85
|
+
]),
|
|
86
|
+
})
|
|
87
|
+
expect(r.built).toBe(1)
|
|
88
|
+
expect(r.confirmed).toBe(3)
|
|
89
|
+
expect(r.unknown).toEqual([])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('capabilities are reported in the emitted dependency order', () => {
|
|
93
|
+
const r = computeCoverage({ requirements: REQS, handoff: HANDOFF, throughput: through([]) })
|
|
94
|
+
expect(r.caps.map((c) => c.slug)).toEqual(['billing', 'user-management'])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('an undated completion counts as built and is reported as undated', () => {
|
|
98
|
+
const r = computeCoverage({
|
|
99
|
+
requirements: REQS,
|
|
100
|
+
handoff: HANDOFF,
|
|
101
|
+
throughput: through([{ fr: 'UM-001', doneAt: null }]),
|
|
102
|
+
})
|
|
103
|
+
expect(r.built).toBe(1)
|
|
104
|
+
expect(r.undated).toBe(1)
|
|
105
|
+
expect(renderCoverage(r)).toContain('undated: 1 completion(s)')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('a completion naming an unknown requirement is collected, not counted', () => {
|
|
109
|
+
const r = computeCoverage({
|
|
110
|
+
requirements: REQS,
|
|
111
|
+
handoff: HANDOFF,
|
|
112
|
+
throughput: through([
|
|
113
|
+
{ fr: 'UM-001', doneAt: '2026-08-12' },
|
|
114
|
+
{ fr: 'ZZ-999', doneAt: '2026-08-12' },
|
|
115
|
+
]),
|
|
116
|
+
})
|
|
117
|
+
expect(r.unknown).toEqual(['ZZ-999'])
|
|
118
|
+
expect(r.built).toBe(1)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('the rendered report names its evidence and marks a finished capability', () => {
|
|
122
|
+
const r = computeCoverage({
|
|
123
|
+
requirements: REQS,
|
|
124
|
+
handoff: HANDOFF,
|
|
125
|
+
throughput: through([
|
|
126
|
+
{ fr: 'UM-001', doneAt: '2026-08-12' },
|
|
127
|
+
{ fr: 'UM-002', doneAt: '2026-08-13' },
|
|
128
|
+
]),
|
|
129
|
+
})
|
|
130
|
+
expect(renderCoverage(r)).toBe(
|
|
131
|
+
[
|
|
132
|
+
'built 2/3 confirmed requirements (67%)',
|
|
133
|
+
'evidence: markdown roadmap checkboxes, dated in file',
|
|
134
|
+
'excluded: 2 non-confirmed (billing 2)',
|
|
135
|
+
'',
|
|
136
|
+
'billing 0/1',
|
|
137
|
+
'user-management 2/2 done',
|
|
138
|
+
].join('\n'),
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('a store with no confirmed requirements reports zero rather than dividing by zero', () => {
|
|
143
|
+
const r = computeCoverage({
|
|
144
|
+
requirements: [req('BI-002', 'billing', INFERRED)],
|
|
145
|
+
handoff: { ...HANDOFF, basis: { confirmed: 0, emitted: 1, order: ['billing'] } },
|
|
146
|
+
throughput: through([]),
|
|
147
|
+
})
|
|
148
|
+
expect(r.confirmed).toBe(0)
|
|
149
|
+
expect(renderCoverage(r)).toContain('built 0/0 confirmed requirements (0%)')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('a capability the emitted order omits is counted and named as stale', () => {
|
|
153
|
+
// A stale handoff.json used to narrow both numerator and denominator
|
|
154
|
+
// silently, reporting 100% while confirmed requirements sat unbuilt in a
|
|
155
|
+
// capability that appeared nowhere in the output.
|
|
156
|
+
const r = computeCoverage({
|
|
157
|
+
requirements: REQS,
|
|
158
|
+
handoff: { ...HANDOFF, basis: { confirmed: 3, emitted: 5, order: ['user-management'] } },
|
|
159
|
+
throughput: through([
|
|
160
|
+
{ fr: 'UM-001', doneAt: '2026-08-12' },
|
|
161
|
+
{ fr: 'UM-002', doneAt: '2026-08-13' },
|
|
162
|
+
]),
|
|
163
|
+
})
|
|
164
|
+
expect(r.confirmed).toBe(3)
|
|
165
|
+
expect(r.built).toBe(2)
|
|
166
|
+
expect(r.stale).toEqual(['billing'])
|
|
167
|
+
expect(r.caps.map((c) => c.slug)).toEqual(['user-management', 'billing'])
|
|
168
|
+
expect(renderCoverage(r)).toContain('stale: 1 capability(ies) not in the emitted work (billing)')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('the percentage never contradicts the fraction beside it', () => {
|
|
172
|
+
const at = (built: number, confirmed: number): string => {
|
|
173
|
+
const reqs = Array.from({ length: confirmed }, (_, i) => req(`X-${i}`, 'billing', CONFIRMED))
|
|
174
|
+
return renderCoverage(
|
|
175
|
+
computeCoverage({
|
|
176
|
+
requirements: reqs,
|
|
177
|
+
handoff: {
|
|
178
|
+
...HANDOFF,
|
|
179
|
+
items: [],
|
|
180
|
+
basis: { confirmed, emitted: confirmed, order: ['billing'] },
|
|
181
|
+
},
|
|
182
|
+
throughput: through(
|
|
183
|
+
Array.from({ length: built }, (_, i) => ({ fr: `X-${i}`, doneAt: '2026-08-12' })),
|
|
184
|
+
),
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
expect(at(199, 200)).toContain('built 199/200 confirmed requirements (99%)')
|
|
189
|
+
expect(at(1, 250)).toContain('built 1/250 confirmed requirements (1%)')
|
|
190
|
+
expect(at(200, 200)).toContain('(100%)')
|
|
191
|
+
expect(at(0, 200)).toContain('(0%)')
|
|
192
|
+
})
|
|
@@ -755,7 +755,7 @@ function readTomlString(text: string, key: string): string {
|
|
|
755
755
|
return match[1]
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
-
test('contract-only run driven probe through
|
|
758
|
+
test('contract-only run driven probe through handoff, ending green at a plain check', async () => {
|
|
759
759
|
const groundTruthPath = join(source, 'GROUND-TRUTH.md')
|
|
760
760
|
const rows = await parseGroundTruth(groundTruthPath)
|
|
761
761
|
expect(rows.length).toBeGreaterThan(0)
|
|
@@ -1012,8 +1012,8 @@ test('contract-only run driven probe through queue, ending green at check --phas
|
|
|
1012
1012
|
|
|
1013
1013
|
// 10. Phase 5, queue. Every item this run owed was filed in the pass that
|
|
1014
1014
|
// named it, so this phase closes on the status flip, exactly as queue.md
|
|
1015
|
-
// says: closing is not "the queue is empty", since
|
|
1016
|
-
// adjudicates an item.
|
|
1015
|
+
// says: closing is not "the queue is empty", since phase 5 itself never
|
|
1016
|
+
// adjudicates an item. Phase 6, below, is where they get decided.
|
|
1017
1017
|
const listed = await migrate(['queue', 'list', '--open'])
|
|
1018
1018
|
expect(listed.code).toBe(0)
|
|
1019
1019
|
expect(listed.out).toContain(`${Object.keys(QUEUE_ITEMS).length + 1} item(s)`)
|
|
@@ -1025,12 +1025,13 @@ test('contract-only run driven probe through queue, ending green at check --phas
|
|
|
1025
1025
|
expect(green.out).not.toContain('Violations')
|
|
1026
1026
|
expect(green.code).toBe(0)
|
|
1027
1027
|
|
|
1028
|
-
// 12. Plain `migrate check` gates every phase through handoff
|
|
1029
|
-
//
|
|
1030
|
-
// is what makes
|
|
1028
|
+
// 12. Plain `migrate check` gates every phase through handoff. A run that
|
|
1029
|
+
// stops at the queue fails it on three distinct fronts, and naming each one
|
|
1030
|
+
// is what makes this an assertion rather than a hope: the two phases still
|
|
1031
|
+
// pending, every queue item nobody has ruled on, and the handoff that never
|
|
1032
|
+
// emitted anything.
|
|
1031
1033
|
const full = await migrate(['check'])
|
|
1032
1034
|
expect(full.code).toBe(1)
|
|
1033
|
-
expect(full.out).toContain('Violations (2):')
|
|
1034
1035
|
expect(full.out).toContain(' run-state:')
|
|
1035
1036
|
expect(full.out).toContain(
|
|
1036
1037
|
' phase adjudicate is pending; every phase through handoff must be done',
|
|
@@ -1038,6 +1039,10 @@ test('contract-only run driven probe through queue, ending green at check --phas
|
|
|
1038
1039
|
expect(full.out).toContain(
|
|
1039
1040
|
' phase handoff is pending; every phase through handoff must be done',
|
|
1040
1041
|
)
|
|
1042
|
+
expect(full.out).toContain(' adjudication:')
|
|
1043
|
+
expect(full.out).toContain('is still open; every queue item needs a ruling before handoff')
|
|
1044
|
+
expect(full.out).toContain(' handoff:')
|
|
1045
|
+
expect(full.out).toContain('no handoff.json in the store')
|
|
1041
1046
|
|
|
1042
1047
|
// 13. The terminus assertion in step 11 is load-bearing, shown by mutation
|
|
1043
1048
|
// rather than asserted: nulling every requirement's parity plan is a phase-4
|
|
@@ -1090,4 +1095,159 @@ test('contract-only run driven probe through queue, ending green at check --phas
|
|
|
1090
1095
|
expect(afterRemoval.out).toContain(
|
|
1091
1096
|
`lens census for ${mutatedSurface} claims in_ledger 0 + added ${claimed} = ${claimed} element(s) in the ledger, but elements.jsonl has ${actual}`,
|
|
1092
1097
|
)
|
|
1098
|
+
await writeFile(elementsPath, elementsText)
|
|
1099
|
+
expect((await migrate(['check', '--phase', 'queue'])).code).toBe(0)
|
|
1100
|
+
|
|
1101
|
+
// 15. Phase 6, adjudicate. The review sheet first, because that is how the
|
|
1102
|
+
// phase is meant to be worked: one pass over every open item with its
|
|
1103
|
+
// recommendation in view, rather than opening four files.
|
|
1104
|
+
const sheet = await migrate(['adjudicate'])
|
|
1105
|
+
expect(sheet.code).toBe(0)
|
|
1106
|
+
expect(sheet.out).toContain(`${Object.keys(QUEUE_ITEMS).length + 1} open`)
|
|
1107
|
+
for (const id of [...Object.keys(QUEUE_ITEMS), QUEUE_ID]) {
|
|
1108
|
+
expect(sheet.out).toContain(`${id} [`)
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const RULINGS: Record<string, string> = {
|
|
1112
|
+
'q-express-user-list-source':
|
|
1113
|
+
'the empty list is the real behaviour; the table stays unread and unmapped',
|
|
1114
|
+
'q-express-mailer-delivery-unobservable':
|
|
1115
|
+
'accept the rubric at moderate; delivery is unobservable from the source alone',
|
|
1116
|
+
'q-express-users-table-unwired': 'the table is in scope and stays mapped to UD-002',
|
|
1117
|
+
[QUEUE_ID]: 'the scaffold row is enumeration noise and is skipped by name',
|
|
1118
|
+
}
|
|
1119
|
+
for (const [id, ruling] of Object.entries(RULINGS)) {
|
|
1120
|
+
const ruled = await migrate(['adjudicate', id, '--ruling', ruling])
|
|
1121
|
+
expect(ruled.code).toBe(0)
|
|
1122
|
+
expect(ruled.out).toContain('open -> adjudicated')
|
|
1123
|
+
}
|
|
1124
|
+
// Re-ruling one of them refuses without --force, so an owner's recorded
|
|
1125
|
+
// decision cannot be replaced by a re-run that meant no harm.
|
|
1126
|
+
const reruled = await migrate(['adjudicate', QUEUE_ID, '--ruling', 'something else'])
|
|
1127
|
+
expect(reruled.code).toBe(1)
|
|
1128
|
+
expect(reruled.err).toContain('--force')
|
|
1129
|
+
|
|
1130
|
+
expect((await migrate(['adjudicate'])).out).toContain('0 open')
|
|
1131
|
+
expect((await migrate(['phase', 'adjudicate', '--status', 'done'])).code).toBe(0)
|
|
1132
|
+
|
|
1133
|
+
// 16. Phase 7, handoff. UD-006 still carries a `queued` confidence, and its
|
|
1134
|
+
// queue item is now adjudicated, so it no longer blocks: that is the whole
|
|
1135
|
+
// point of measuring blockers against open items rather than against the
|
|
1136
|
+
// confidence field.
|
|
1137
|
+
const dry = await migrate(['handoff', '--dry-run'])
|
|
1138
|
+
expect(dry.code).toBe(0)
|
|
1139
|
+
expect(dry.out).toContain('plan:')
|
|
1140
|
+
expect(dry.out).toContain('nothing written')
|
|
1141
|
+
expect(await Bun.file(join(storeDir, 'handoff.json')).exists()).toBe(false)
|
|
1142
|
+
|
|
1143
|
+
const emitted = await migrate(['handoff'])
|
|
1144
|
+
expect(emitted.code).toBe(0)
|
|
1145
|
+
expect(emitted.out).toContain('adapter markdown')
|
|
1146
|
+
|
|
1147
|
+
const handoffFile = JSON.parse(await readFile(join(storeDir, 'handoff.json'), 'utf8'))
|
|
1148
|
+
expect(handoffFile.basis.emitted).toBe(REQUIREMENTS.length)
|
|
1149
|
+
// Every capability the seam declared reached a work item.
|
|
1150
|
+
expect(handoffFile.items).toHaveLength(CAPABILITIES.length)
|
|
1151
|
+
const roadmapPath = join(target, 'docs', 'migrate', 'roadmap.md')
|
|
1152
|
+
const roadmap = await readFile(roadmapPath, 'utf8')
|
|
1153
|
+
for (const r of REQUIREMENTS) expect(roadmap).toContain(`- [ ] ${r.id} `)
|
|
1154
|
+
|
|
1155
|
+
expect((await migrate(['phase', 'handoff', '--status', 'done'])).code).toBe(0)
|
|
1156
|
+
|
|
1157
|
+
// 17. The milestone's acceptance proof. Plain `migrate check`, with no
|
|
1158
|
+
// --phase, gating every phase through handoff, exits 0. This is the first
|
|
1159
|
+
// time in the project's history that the unbounded gate can pass at all.
|
|
1160
|
+
const complete = await migrate(['check'])
|
|
1161
|
+
expect(complete.out).not.toContain('Violations')
|
|
1162
|
+
expect(complete.code).toBe(0)
|
|
1163
|
+
|
|
1164
|
+
// 18. Coverage, read back through the adapter that emitted the work. Two
|
|
1165
|
+
// boxes ticked by hand, one dated and one not, which is what an owner
|
|
1166
|
+
// actually does to a roadmap.
|
|
1167
|
+
const built = ['UD-001', 'UD-002']
|
|
1168
|
+
let ticked = roadmap
|
|
1169
|
+
ticked = ticked.replace(`- [ ] ${built[0]} `, `- [x] ${built[0]} <!-- done:2026-08-10 --> `)
|
|
1170
|
+
ticked = ticked.replace(`- [ ] ${built[1]} `, `- [x] ${built[1]} <!-- done:2026-08-12 --> `)
|
|
1171
|
+
await writeFile(roadmapPath, ticked)
|
|
1172
|
+
|
|
1173
|
+
const coverage = await migrate(['coverage'])
|
|
1174
|
+
expect(coverage.code).toBe(0)
|
|
1175
|
+
expect(coverage.out).toContain('evidence: markdown roadmap checkboxes, dated in file')
|
|
1176
|
+
// The fixture carries two non-confirmed requirements, UD-006 (`queued`) and
|
|
1177
|
+
// one inferred. Both sit outside the confirmed denominator and are reported
|
|
1178
|
+
// as exclusions rather than counted against delivery: parity is a promise
|
|
1179
|
+
// about behaviour the run confirmed.
|
|
1180
|
+
expect(coverage.out).toContain(
|
|
1181
|
+
'excluded: 2 non-confirmed (user-directory 1, welcome-notification 1)',
|
|
1182
|
+
)
|
|
1183
|
+
expect(coverage.out).toMatch(/built 2\/\d+ confirmed requirements/)
|
|
1184
|
+
|
|
1185
|
+
// Re-running handoff after the boxes were ticked must not erase them. This
|
|
1186
|
+
// is the data-loss regression the markdown adapter exists to avoid, checked
|
|
1187
|
+
// here against a real store rather than a unit fixture.
|
|
1188
|
+
expect((await migrate(['handoff'])).code).toBe(0)
|
|
1189
|
+
const afterRerun = await readFile(roadmapPath, 'utf8')
|
|
1190
|
+
expect(afterRerun).toContain(`- [x] ${built[0]} <!-- done:2026-08-10 --> `)
|
|
1191
|
+
expect(afterRerun).toContain(`- [x] ${built[1]} <!-- done:2026-08-12 --> `)
|
|
1192
|
+
|
|
1193
|
+
// 19. Forecast. It refuses before the owner has attested anything, which is
|
|
1194
|
+
// the difference between a projection and a guess.
|
|
1195
|
+
const unattested = await migrate(['forecast'])
|
|
1196
|
+
expect(unattested.code).toBe(1)
|
|
1197
|
+
expect(unattested.err).toContain('forecast-assumptions.md')
|
|
1198
|
+
|
|
1199
|
+
await writeFile(
|
|
1200
|
+
join(storeDir, 'forecast-assumptions.md'),
|
|
1201
|
+
[
|
|
1202
|
+
'---',
|
|
1203
|
+
'attestedBy: e2e',
|
|
1204
|
+
'attestedDate: 2026-08-13',
|
|
1205
|
+
'---',
|
|
1206
|
+
'',
|
|
1207
|
+
'## Territories',
|
|
1208
|
+
'',
|
|
1209
|
+
'| capability | territory |',
|
|
1210
|
+
'| --- | --- |',
|
|
1211
|
+
...CAPABILITIES.map((c) => `| ${c.slug} | established |`),
|
|
1212
|
+
'',
|
|
1213
|
+
'## Multipliers',
|
|
1214
|
+
'',
|
|
1215
|
+
'| territory | multiplier |',
|
|
1216
|
+
'| --- | --- |',
|
|
1217
|
+
'| established | 1.0 |',
|
|
1218
|
+
'',
|
|
1219
|
+
'## Scenarios',
|
|
1220
|
+
'',
|
|
1221
|
+
'| label | rate | streams | tax | note |',
|
|
1222
|
+
'| --- | --- | --- | --- | --- |',
|
|
1223
|
+
'| steady | as-is | 1 | 0 | measured |',
|
|
1224
|
+
'| target | 2 | 1 | 0 | owner target |',
|
|
1225
|
+
'',
|
|
1226
|
+
'## Caveats',
|
|
1227
|
+
'',
|
|
1228
|
+
'- The fixture is not a real campaign.',
|
|
1229
|
+
'',
|
|
1230
|
+
].join('\n'),
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
const forecast = await migrate(['forecast'])
|
|
1234
|
+
expect(forecast.code).toBe(0)
|
|
1235
|
+
expect(forecast.out).toContain('attested by e2e on 2026-08-13')
|
|
1236
|
+
// Two dated completions is exactly the minimum, so the measured rows
|
|
1237
|
+
// project; the target row is labelled as owner-attested either way.
|
|
1238
|
+
expect(forecast.out).toContain('target (owner-attested, nothing measures this)')
|
|
1239
|
+
expect(forecast.out).toContain('The fixture is not a real campaign.')
|
|
1240
|
+
|
|
1241
|
+
// 20. The acceptance proof is load-bearing, shown by mutation: reopening one
|
|
1242
|
+
// queue item must break the plain check that step 17 asserted, and only on
|
|
1243
|
+
// the adjudication gate.
|
|
1244
|
+
const reopenPath = join(storeDir, 'queue', `${QUEUE_ID}.md`)
|
|
1245
|
+
const ruledText = await readFile(reopenPath, 'utf8')
|
|
1246
|
+
await writeFile(reopenPath, ruledText.replace('status: adjudicated', 'status: open'))
|
|
1247
|
+
const reopened = await migrate(['check'])
|
|
1248
|
+
expect(reopened.code).toBe(1)
|
|
1249
|
+
expect(reopened.out).toContain(' adjudication:')
|
|
1250
|
+
expect(reopened.out).toContain(QUEUE_ID)
|
|
1251
|
+
await writeFile(reopenPath, ruledText)
|
|
1252
|
+
expect((await migrate(['check'])).code).toBe(0)
|
|
1093
1253
|
})
|
|
@@ -1208,12 +1208,13 @@ test('aspnet recipe run driven probe through queue, ending green at check --phas
|
|
|
1208
1208
|
expect(green.out).not.toContain('Violations')
|
|
1209
1209
|
expect(green.code).toBe(0)
|
|
1210
1210
|
|
|
1211
|
-
// 13. Plain `migrate check` gates every phase through handoff
|
|
1212
|
-
//
|
|
1213
|
-
// is what makes
|
|
1211
|
+
// 13. Plain `migrate check` gates every phase through handoff. A run that
|
|
1212
|
+
// stops at the queue fails it on three distinct fronts, and naming each one
|
|
1213
|
+
// is what makes this an assertion rather than a hope: the two phases still
|
|
1214
|
+
// pending, every queue item nobody has ruled on, and the handoff that never
|
|
1215
|
+
// emitted anything.
|
|
1214
1216
|
const full = await migrate(['check'])
|
|
1215
1217
|
expect(full.code).toBe(1)
|
|
1216
|
-
expect(full.out).toContain('Violations (2):')
|
|
1217
1218
|
expect(full.out).toContain(' run-state:')
|
|
1218
1219
|
expect(full.out).toContain(
|
|
1219
1220
|
' phase adjudicate is pending; every phase through handoff must be done',
|
|
@@ -1221,6 +1222,10 @@ test('aspnet recipe run driven probe through queue, ending green at check --phas
|
|
|
1221
1222
|
expect(full.out).toContain(
|
|
1222
1223
|
' phase handoff is pending; every phase through handoff must be done',
|
|
1223
1224
|
)
|
|
1225
|
+
expect(full.out).toContain(' adjudication:')
|
|
1226
|
+
expect(full.out).toContain('is still open; every queue item needs a ruling before handoff')
|
|
1227
|
+
expect(full.out).toContain(' handoff:')
|
|
1228
|
+
expect(full.out).toContain('no handoff.json in the store')
|
|
1224
1229
|
|
|
1225
1230
|
// 14. The terminus assertion in step 12 is load-bearing, shown by mutation
|
|
1226
1231
|
// rather than asserted: nulling every requirement's parity plan is a phase-4
|