@dtlabs/ai-toolkit 0.10.0 → 0.11.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/bin/cli.js +1178 -64
  3. package/docs/installation.md +74 -8
  4. package/docs/reference.md +60 -12
  5. package/lib/asset-catalog.js +52 -0
  6. package/package.json +9 -2
  7. package/{.claude → src/claude}/agents/install-toolkit.md +30 -103
  8. package/{.claude → src/claude}/workflows/am-phase1.js +9 -12
  9. package/{.claude → src/claude}/workflows/pm-phase2.js +2 -2
  10. package/.claude/.ai-toolkit-version +0 -1
  11. package/.claude/scripts/.gitkeep +0 -0
  12. package/.claude/scripts/tests/fixtures/ac-table-format.md +0 -48
  13. package/.claude/scripts/tests/fixtures/test-feature/TEST-001-Work-Breakdown.json +0 -234
  14. package/.claude/scripts/tests/fixtures/test-feature/TEST-001-semantic-output.json +0 -4
  15. package/.claude/scripts/tests/fixtures/test-feature/feature.md +0 -45
  16. package/.claude/scripts/tests/fixtures/wb-invalid-bad-domain.json +0 -39
  17. package/.claude/scripts/tests/fixtures/wb-invalid-cycle.json +0 -61
  18. package/.claude/scripts/tests/fixtures/wb-invalid-duplicate-id.json +0 -72
  19. package/.claude/scripts/tests/fixtures/wb-invalid-missing-field.json +0 -38
  20. package/.claude/scripts/tests/fixtures/wb-valid.json +0 -189
  21. package/.claude/scripts/tests/helpers/buildWaves.js +0 -21
  22. package/.claude/scripts/tests/helpers/pm-phase2-flow.js +0 -29
  23. package/.claude/scripts/tests/helpers/pm-phase2-gate2.js +0 -55
  24. package/.claude/scripts/tests/helpers/pm-phase2-ledger.js +0 -108
  25. package/.claude/scripts/tests/installer.scripts-distribution.test.js +0 -131
  26. package/.claude/scripts/tests/pm-phase2.gate2-payload.test.js +0 -353
  27. package/.claude/scripts/tests/pm-phase2.ledger-transitions.test.js +0 -346
  28. package/.claude/scripts/tests/pm-phase2.orchestration-flow.test.js +0 -276
  29. package/.claude/scripts/tests/verify-us-01-t03.js +0 -288
  30. package/.claude/scripts/tests/verify-us-03-t04.js +0 -262
  31. package/.claude/scripts/tests/wb-render.checks-md-csv-deps.test.js +0 -337
  32. package/.claude/scripts/tests/wb-render.checks-sanitize-commit-paths.test.js +0 -352
  33. package/.claude/scripts/tests/wb-render.regression-pm-phase3-csv.test.js +0 -219
  34. package/.claude/scripts/tests/wb-validate.checks-1-7.test.js +0 -330
  35. package/.claude/scripts/tests/wb-validate.checks-11.test.js +0 -166
  36. package/.claude/scripts/tests/wb-validate.checks-12-14.test.js +0 -222
  37. package/.claude/scripts/tests/wb-validate.checks-15-18.test.js +0 -359
  38. package/.claude/scripts/tests/wb-validate.checks-19-21.test.js +0 -362
  39. package/.claude/scripts/tests/wb-validate.checks-22-23-report.test.js +0 -297
  40. package/.claude/scripts/tests/wb-validate.checks-8-10.test.js +0 -243
  41. package/.claude/settings.json +0 -5
  42. /package/{.claude → src/claude}/agents/concurrency-safety-assessment.md +0 -0
  43. /package/{.claude → src/claude}/agents/define-feature.md +0 -0
  44. /package/{.claude → src/claude}/agents/dependency-injection-refactoring.md +0 -0
  45. /package/{.claude → src/claude}/agents/dependency-supply-chain-security.md +0 -0
  46. /package/{.claude → src/claude}/agents/developer-backend.md +0 -0
  47. /package/{.claude → src/claude}/agents/developer-frontend.md +0 -0
  48. /package/{.claude → src/claude}/agents/developer-testing.md +0 -0
  49. /package/{.claude → src/claude}/agents/domain-model-refactoring.md +0 -0
  50. /package/{.claude → src/claude}/agents/generate-requirements.md +0 -0
  51. /package/{.claude → src/claude}/agents/generate-tech-spec.md +0 -0
  52. /package/{.claude → src/claude}/agents/generate-work-breakdown.md +0 -0
  53. /package/{.claude → src/claude}/agents/generic-software-assessment.md +0 -0
  54. /package/{.claude → src/claude}/agents/god-class-decomposition.md +0 -0
  55. /package/{.claude → src/claude}/agents/init-agents-md.md +0 -0
  56. /package/{.claude → src/claude}/agents/intervention-documentation-standard.md +0 -0
  57. /package/{.claude → src/claude}/agents/layered-architecture-assessment.md +0 -0
  58. /package/{.claude → src/claude}/agents/review-solution.md +0 -0
  59. /package/{.claude → src/claude}/agents/security-hardening.md +0 -0
  60. /package/{.claude → src/claude}/agents/validate-feature-docs.md +0 -0
  61. /package/{.claude → src/claude}/agents/validate-work-breakdown-semantic.md +0 -0
  62. /package/{.claude → src/claude}/commands/assessment-status.md +0 -0
  63. /package/{.claude → src/claude}/commands/check-docs.md +0 -0
  64. /package/{.claude → src/claude}/commands/check-interventions.md +0 -0
  65. /package/{.claude → src/claude}/commands/feature-status.md +0 -0
  66. /package/{.claude → src/claude}/commands/next-intervention.md +0 -0
  67. /package/{.claude → src/claude}/commands/next-task.md +0 -0
  68. /package/{.claude → src/claude}/commands/pr-description.md +0 -0
  69. /package/{.claude → src/claude}/scripts/wb-render.js +0 -0
  70. /package/{.claude → src/claude}/scripts/wb-validate.js +0 -0
  71. /package/{.claude → src/claude}/skills/assess-codebase/SKILL.md +0 -0
  72. /package/{.claude → src/claude}/skills/define-feature/SKILL.md +0 -0
  73. /package/{.claude → src/claude}/skills/hi-gaia/SKILL.md +0 -0
  74. /package/{.claude → src/claude}/skills/implement-feature/SKILL.md +0 -0
  75. /package/{.claude → src/claude}/skills/init-agents/SKILL.md +0 -0
  76. /package/{.claude → src/claude}/skills/install-toolkit/SKILL.md +0 -0
  77. /package/{.claude → src/claude}/workflows/am-phase2.js +0 -0
  78. /package/{.claude → src/claude}/workflows/pm-phase1.js +0 -0
  79. /package/{.claude → src/claude}/workflows/pm-phase3.js +0 -0
@@ -1,353 +0,0 @@
1
- 'use strict';
2
-
3
- const { buildGate2Payload } = require('./helpers/pm-phase2-gate2');
4
-
5
- // ─── shared fixtures ──────────────────────────────────────────────────────────
6
-
7
- // Minimal report that satisfies wbValidatorPassed (valid=true, empty errors array)
8
- const REPORT_CLEAN = { valid: true, errors: [] };
9
-
10
- // A report that fails structural validation (valid=false)
11
- const REPORT_INVALID = { valid: false, errors: [{ category: 'schema_version_invalid', message: 'bad schema' }] };
12
-
13
- // ─────────────────────────────────────────────────────────────────────────────
14
-
15
- describe('pm-phase2 gate2_payload assembly — buildGate2Payload()', () => {
16
-
17
- // ── Group 1: gate2_blocked flag ───────────────────────────────────────────
18
-
19
- describe('gate2_blocked flag', () => {
20
-
21
- test('is false when wb-validate passed, semantic ok, render ok, and no blocking findings', () => {
22
- // Arrange
23
- const options = {
24
- wbValidatorReport: REPORT_CLEAN,
25
- validateFailed: false,
26
- semanticResult: { findings: [{ blocking: false, message: 'style suggestion' }] },
27
- semanticFailed: false,
28
- renderResult: { ok: true },
29
- renderFailed: false,
30
- };
31
- // Act
32
- const payload = buildGate2Payload(options);
33
- // Assert
34
- expect(payload.gate2_blocked).toBe(false);
35
- });
36
-
37
- test('is true when validateFailed is true (wb-validate technical failure)', () => {
38
- // Arrange
39
- const options = {
40
- wbValidatorReport: REPORT_CLEAN,
41
- validateFailed: true,
42
- semanticFailed: false,
43
- renderFailed: false,
44
- semanticResult: null,
45
- };
46
- // Act
47
- const payload = buildGate2Payload(options);
48
- // Assert
49
- expect(payload.gate2_blocked).toBe(true);
50
- });
51
-
52
- test('is true when wb-validate exits 1 with valid=false in report', () => {
53
- // Arrange
54
- const options = {
55
- wbValidatorReport: REPORT_INVALID,
56
- validateFailed: false,
57
- semanticFailed: false,
58
- renderFailed: false,
59
- semanticResult: null,
60
- };
61
- // Act
62
- const payload = buildGate2Payload(options);
63
- // Assert
64
- expect(payload.gate2_blocked).toBe(true);
65
- });
66
-
67
- test('is true when semanticFailed is true', () => {
68
- // Arrange
69
- const options = {
70
- wbValidatorReport: REPORT_CLEAN,
71
- validateFailed: false,
72
- semanticFailed: true,
73
- renderFailed: false,
74
- semanticResult: null,
75
- };
76
- // Act
77
- const payload = buildGate2Payload(options);
78
- // Assert
79
- expect(payload.gate2_blocked).toBe(true);
80
- });
81
-
82
- test('is true when renderFailed is true', () => {
83
- // Arrange
84
- const options = {
85
- wbValidatorReport: REPORT_CLEAN,
86
- validateFailed: false,
87
- semanticFailed: false,
88
- renderFailed: true,
89
- semanticResult: null,
90
- };
91
- // Act
92
- const payload = buildGate2Payload(options);
93
- // Assert
94
- expect(payload.gate2_blocked).toBe(true);
95
- });
96
-
97
- test('is true when semantic result contains at least one blocking finding', () => {
98
- // Arrange
99
- const options = {
100
- wbValidatorReport: REPORT_CLEAN,
101
- validateFailed: false,
102
- semanticFailed: false,
103
- renderFailed: false,
104
- semanticResult: { findings: [{ blocking: true, message: 'critical scope gap' }] },
105
- };
106
- // Act
107
- const payload = buildGate2Payload(options);
108
- // Assert
109
- expect(payload.gate2_blocked).toBe(true);
110
- });
111
-
112
- test('is false when semantic result has only non-blocking (warning-level) findings', () => {
113
- // Arrange
114
- const options = {
115
- wbValidatorReport: REPORT_CLEAN,
116
- validateFailed: false,
117
- semanticFailed: false,
118
- renderFailed: false,
119
- semanticResult: {
120
- findings: [
121
- { blocking: false, message: 'minor wording suggestion' },
122
- { blocking: false, message: 'optional improvement' },
123
- ],
124
- },
125
- };
126
- // Act
127
- const payload = buildGate2Payload(options);
128
- // Assert
129
- expect(payload.gate2_blocked).toBe(false);
130
- });
131
-
132
- });
133
-
134
- // ── Group 2: warning_band_tasks derivation ────────────────────────────────
135
-
136
- describe('warning_band_tasks derivation', () => {
137
-
138
- test('has one entry with correct taskId and agentMinutes when report has one duration_warning', () => {
139
- // Arrange — fixture uses agentMinutes: the canonical field name emitted by wb-validate.js check 15
140
- const options = {
141
- wbValidatorReport: {
142
- valid: true,
143
- errors: [],
144
- warnings: [
145
- { category: 'duration_warning', taskId: 'US-01-T03', details: { agentMinutes: 110, targetMax: 15, warningMax: 30 } },
146
- ],
147
- },
148
- validateFailed: false,
149
- };
150
- // Act
151
- const payload = buildGate2Payload(options);
152
- // Assert
153
- expect(payload.warning_band_tasks).toHaveLength(1);
154
- expect(payload.warning_band_tasks[0]).toEqual({ taskId: 'US-01-T03', agentMinutes: 110 });
155
- });
156
-
157
- test('is an empty array when report has no warnings', () => {
158
- // Arrange
159
- const options = {
160
- wbValidatorReport: { valid: true, errors: [], warnings: [] },
161
- validateFailed: false,
162
- };
163
- // Act
164
- const payload = buildGate2Payload(options);
165
- // Assert
166
- expect(payload.warning_band_tasks).toEqual([]);
167
- });
168
-
169
- test('is an empty array when report is null', () => {
170
- // Arrange
171
- const options = {
172
- wbValidatorReport: null,
173
- validateFailed: false,
174
- };
175
- // Act
176
- const payload = buildGate2Payload(options);
177
- // Assert
178
- expect(payload.warning_band_tasks).toEqual([]);
179
- });
180
-
181
- });
182
-
183
- // ── Group 3: split_required_tasks derivation ──────────────────────────────
184
-
185
- describe('split_required_tasks derivation', () => {
186
-
187
- test('has one entry with taskId and agentMinutes when report has one split_required error', () => {
188
- // Arrange — fixture uses agentMinutes: the canonical field name emitted by wb-validate.js check 15
189
- const options = {
190
- wbValidatorReport: {
191
- valid: false,
192
- errors: [
193
- { category: 'split_required', taskId: 'US-02-T01', details: { agentMinutes: 200, warningMax: 30 } },
194
- ],
195
- },
196
- validateFailed: false,
197
- };
198
- // Act
199
- const payload = buildGate2Payload(options);
200
- // Assert
201
- expect(payload.split_required_tasks).toHaveLength(1);
202
- expect(payload.split_required_tasks[0]).toEqual({ taskId: 'US-02-T01', agentMinutes: 200 });
203
- });
204
-
205
- test('is an empty array when report errors have no split_required category entries', () => {
206
- // Arrange
207
- const options = {
208
- wbValidatorReport: {
209
- valid: false,
210
- errors: [
211
- { category: 'must_ac_uncovered', taskId: 'US-01-T01', details: { acId: 'AC-01' } },
212
- { category: 'schema_version_invalid', taskId: null, details: null },
213
- ],
214
- },
215
- validateFailed: false,
216
- };
217
- // Act
218
- const payload = buildGate2Payload(options);
219
- // Assert
220
- expect(payload.split_required_tasks).toEqual([]);
221
- });
222
-
223
- test('is an empty array when report is null', () => {
224
- // Arrange
225
- const options = {
226
- wbValidatorReport: null,
227
- validateFailed: false,
228
- };
229
- // Act
230
- const payload = buildGate2Payload(options);
231
- // Assert
232
- expect(payload.split_required_tasks).toEqual([]);
233
- });
234
-
235
- });
236
-
237
- // ── Group 4: must_ac_uncovered derivation ─────────────────────────────────
238
-
239
- describe('must_ac_uncovered derivation', () => {
240
-
241
- test('has one entry with acId when report has one must_ac_uncovered error', () => {
242
- // Arrange
243
- const options = {
244
- wbValidatorReport: {
245
- valid: false,
246
- errors: [
247
- { category: 'must_ac_uncovered', taskId: null, details: { acId: 'AC-03' } },
248
- ],
249
- },
250
- validateFailed: false,
251
- };
252
- // Act
253
- const payload = buildGate2Payload(options);
254
- // Assert
255
- expect(payload.must_ac_uncovered).toHaveLength(1);
256
- expect(payload.must_ac_uncovered[0]).toEqual({ acId: 'AC-03' });
257
- });
258
-
259
- test('is an empty array when report is null', () => {
260
- // Arrange
261
- const options = {
262
- wbValidatorReport: null,
263
- validateFailed: false,
264
- };
265
- // Act
266
- const payload = buildGate2Payload(options);
267
- // Assert
268
- expect(payload.must_ac_uncovered).toEqual([]);
269
- });
270
-
271
- });
272
-
273
- // ── Group 5: phase_unschedulable derivation ───────────────────────────────
274
-
275
- describe('phase_unschedulable derivation', () => {
276
-
277
- test('contains the unschedulable phase id when report dependencies flag one phase', () => {
278
- // Arrange
279
- const options = {
280
- wbValidatorReport: {
281
- valid: true,
282
- errors: [],
283
- dependencies: { phaseUnschedulable: ['Phase-2'] },
284
- },
285
- validateFailed: false,
286
- };
287
- // Act
288
- const payload = buildGate2Payload(options);
289
- // Assert
290
- expect(payload.phase_unschedulable).toContain('Phase-2');
291
- expect(payload.phase_unschedulable).toHaveLength(1);
292
- });
293
-
294
- test('is an empty array when report is null', () => {
295
- // Arrange
296
- const options = {
297
- wbValidatorReport: null,
298
- validateFailed: false,
299
- };
300
- // Act
301
- const payload = buildGate2Payload(options);
302
- // Assert
303
- expect(payload.phase_unschedulable).toEqual([]);
304
- });
305
-
306
- });
307
-
308
- // ── Group 6: duration_bands and domain_distribution passthrough ───────────
309
-
310
- describe('duration_bands and domain_distribution passthrough', () => {
311
-
312
- test('duration_bands is the durationBands object from the report', () => {
313
- // Arrange
314
- const durationBands = { target: 5, aboveTarget: 2, warning: 1, splitRequired: 0 };
315
- const options = {
316
- wbValidatorReport: { valid: true, errors: [], durationBands },
317
- validateFailed: false,
318
- };
319
- // Act
320
- const payload = buildGate2Payload(options);
321
- // Assert
322
- expect(payload.duration_bands).toEqual(durationBands);
323
- });
324
-
325
- test('domain_distribution is the domainDistribution object from the report', () => {
326
- // Arrange
327
- const domainDistribution = { BE: 3 };
328
- const options = {
329
- wbValidatorReport: { valid: true, errors: [], domainDistribution },
330
- validateFailed: false,
331
- };
332
- // Act
333
- const payload = buildGate2Payload(options);
334
- // Assert
335
- expect(payload.domain_distribution).toEqual(domainDistribution);
336
- });
337
-
338
- test('duration_bands and domain_distribution are both null when report is null', () => {
339
- // Arrange
340
- const options = {
341
- wbValidatorReport: null,
342
- validateFailed: false,
343
- };
344
- // Act
345
- const payload = buildGate2Payload(options);
346
- // Assert
347
- expect(payload.duration_bands).toBeNull();
348
- expect(payload.domain_distribution).toBeNull();
349
- });
350
-
351
- });
352
-
353
- });
@@ -1,346 +0,0 @@
1
- 'use strict';
2
-
3
- const { simulateLedgerEntries } = require('./helpers/pm-phase2-ledger');
4
-
5
- // ─── shared fixtures ──────────────────────────────────────────────────────────
6
-
7
- // Minimal report that satisfies wbValidatorPassed (valid=true, empty errors array)
8
- const REPORT_CLEAN = { valid: true, errors: [] };
9
-
10
- // A report that reflects exit code 1 — validator ran but found structural errors
11
- const REPORT_INVALID = { valid: false, errors: [{ category: 'missing_field', message: 'outcome missing' }] };
12
-
13
- // ─────────────────────────────────────────────────────────────────────────────
14
-
15
- describe('pm-phase2 ledger state machine — simulateLedgerEntries()', () => {
16
-
17
- // ── Group 1: wb-validate ledger entries ───────────────────────────────────
18
-
19
- describe('wb-validate ledger entries', () => {
20
-
21
- test('wb-validate passes → entry has final_status done and carries actual token count', () => {
22
- // Arrange
23
- const scenario = {
24
- validateFailed: false,
25
- validateTokens: 500,
26
- wbValidatorReport: REPORT_CLEAN,
27
- };
28
- // Act
29
- const entries = simulateLedgerEntries(scenario);
30
- // Assert
31
- expect(entries[0].final_status).toBe('done');
32
- expect(entries[0].phase_delta_tokens).toBe(500);
33
- expect(entries[0].error_summary).toBeUndefined();
34
- });
35
-
36
- test('wb-validate fails technically → entry has final_status failed with error_summary and exit_code', () => {
37
- // Arrange
38
- const scenario = {
39
- validateFailed: true,
40
- validateError: 'Script crashed',
41
- validateExitCode: 2,
42
- validateTokens: 100,
43
- wbValidatorReport: null,
44
- };
45
- // Act
46
- const entries = simulateLedgerEntries(scenario);
47
- // Assert
48
- expect(entries[0].final_status).toBe('failed');
49
- expect(entries[0].error_summary).toBe('Script crashed');
50
- expect(entries[0].exit_code).toBe(2);
51
- });
52
-
53
- test('wb-validate exit 1 (structural errors) → entry has final_status done (completed run, not a failure)', () => {
54
- // Arrange
55
- const scenario = {
56
- validateFailed: false,
57
- validateTokens: 300,
58
- wbValidatorReport: REPORT_INVALID,
59
- };
60
- // Act
61
- const entries = simulateLedgerEntries(scenario);
62
- // Assert
63
- expect(entries[0].final_status).toBe('done');
64
- expect(entries[0].error_summary).toBeUndefined();
65
- });
66
-
67
- test('wb-validate status_sequence always starts with running — never skipped', () => {
68
- // Arrange — both success and failure scenarios
69
- const successScenario = { validateFailed: false, wbValidatorReport: REPORT_CLEAN };
70
- const failureScenario = { validateFailed: true, wbValidatorReport: null };
71
- // Act
72
- const successEntries = simulateLedgerEntries(successScenario);
73
- const failureEntries = simulateLedgerEntries(failureScenario);
74
- // Assert
75
- expect(successEntries[0].status_sequence[0]).toBe('running');
76
- expect(failureEntries[0].status_sequence[0]).toBe('running');
77
- });
78
-
79
- });
80
-
81
- // ── Group 2: semantic validator ledger entries ────────────────────────────
82
-
83
- describe('semantic validator ledger entries', () => {
84
-
85
- test('wb-validate failed → semantic entry has final_status skipped with zero tokens and no error_summary', () => {
86
- // Arrange
87
- const scenario = {
88
- validateFailed: true,
89
- validateExitCode: 1,
90
- wbValidatorReport: null,
91
- };
92
- // Act
93
- const entries = simulateLedgerEntries(scenario);
94
- // Assert
95
- expect(entries[1].final_status).toBe('skipped');
96
- expect(entries[1].phase_delta_tokens).toBe(0);
97
- expect(entries[1].error_summary).toBeUndefined();
98
- });
99
-
100
- test('wb-validate exit 1 with structural errors → semantic entry has final_status skipped (wbValidatorPassed is false)', () => {
101
- // Arrange
102
- const scenario = {
103
- validateFailed: false,
104
- wbValidatorReport: REPORT_INVALID,
105
- };
106
- // Act
107
- const entries = simulateLedgerEntries(scenario);
108
- // Assert
109
- expect(entries[1].final_status).toBe('skipped');
110
- });
111
-
112
- test('wb-validate passed and semantic succeeds → semantic entry has final_status done with actual token count', () => {
113
- // Arrange
114
- const scenario = {
115
- validateFailed: false,
116
- wbValidatorReport: REPORT_CLEAN,
117
- semanticTokens: 1200,
118
- };
119
- // Act
120
- const entries = simulateLedgerEntries(scenario);
121
- // Assert
122
- expect(entries[1].final_status).toBe('done');
123
- expect(entries[1].phase_delta_tokens).toBe(1200);
124
- });
125
-
126
- test('wb-validate passed and semantic fails → semantic entry has final_status failed with error_summary and exit_code null', () => {
127
- // Arrange
128
- const scenario = {
129
- validateFailed: false,
130
- wbValidatorReport: REPORT_CLEAN,
131
- semanticFailed: true,
132
- semanticError: 'Agent timeout',
133
- semanticTokens: 800,
134
- };
135
- // Act
136
- const entries = simulateLedgerEntries(scenario);
137
- // Assert
138
- expect(entries[1].final_status).toBe('failed');
139
- expect(entries[1].error_summary).toBe('Agent timeout');
140
- expect(entries[1].exit_code).toBeNull();
141
- });
142
-
143
- test('skipped semantic entry always has phase_delta_tokens of zero regardless of semanticTokens in scenario', () => {
144
- // Arrange — wb-validate failed forces semantic to be skipped
145
- const scenario = {
146
- validateFailed: true,
147
- wbValidatorReport: null,
148
- semanticTokens: 999,
149
- };
150
- // Act
151
- const entries = simulateLedgerEntries(scenario);
152
- // Assert
153
- expect(entries[1].phase_delta_tokens).toBe(0);
154
- });
155
-
156
- });
157
-
158
- // ── Group 3: wb-render ledger entries ────────────────────────────────────
159
-
160
- describe('wb-render ledger entries', () => {
161
-
162
- test('wb-validate failed → render entry has final_status skipped', () => {
163
- // Arrange
164
- const scenario = {
165
- validateFailed: true,
166
- wbValidatorReport: null,
167
- };
168
- // Act
169
- const entries = simulateLedgerEntries(scenario);
170
- // Assert
171
- expect(entries[2].final_status).toBe('skipped');
172
- });
173
-
174
- test('wb-validate exit 1 (structural errors) → render entry has final_status skipped', () => {
175
- // Arrange
176
- const scenario = {
177
- validateFailed: false,
178
- wbValidatorReport: REPORT_INVALID,
179
- };
180
- // Act
181
- const entries = simulateLedgerEntries(scenario);
182
- // Assert
183
- expect(entries[2].final_status).toBe('skipped');
184
- });
185
-
186
- test('wb-validate passed but semantic failed → render entry has final_status skipped', () => {
187
- // Arrange
188
- const scenario = {
189
- validateFailed: false,
190
- wbValidatorReport: REPORT_CLEAN,
191
- semanticFailed: true,
192
- semanticError: 'Critical gap found',
193
- };
194
- // Act
195
- const entries = simulateLedgerEntries(scenario);
196
- // Assert
197
- expect(entries[2].final_status).toBe('skipped');
198
- });
199
-
200
- test('all steps passed → render entry has final_status done with actual token count', () => {
201
- // Arrange
202
- const scenario = {
203
- validateFailed: false,
204
- wbValidatorReport: REPORT_CLEAN,
205
- renderTokens: 600,
206
- };
207
- // Act
208
- const entries = simulateLedgerEntries(scenario);
209
- // Assert
210
- expect(entries[2].final_status).toBe('done');
211
- expect(entries[2].phase_delta_tokens).toBe(600);
212
- });
213
-
214
- test('render fails → entry has final_status failed with error_summary and exit_code from renderExitCode', () => {
215
- // Arrange
216
- const scenario = {
217
- validateFailed: false,
218
- wbValidatorReport: REPORT_CLEAN,
219
- renderFailed: true,
220
- renderError: 'Template not found',
221
- renderExitCode: 3,
222
- renderTokens: 400,
223
- };
224
- // Act
225
- const entries = simulateLedgerEntries(scenario);
226
- // Assert
227
- expect(entries[2].final_status).toBe('failed');
228
- expect(entries[2].error_summary).toBe('Template not found');
229
- expect(entries[2].exit_code).toBe(3);
230
- });
231
-
232
- });
233
-
234
- // ── Group 4: token attribution ────────────────────────────────────────────
235
-
236
- describe('token attribution', () => {
237
-
238
- test('done entry carries the actual tokens consumed by the agent', () => {
239
- // Arrange
240
- const scenario = {
241
- validateFailed: false,
242
- wbValidatorReport: REPORT_CLEAN,
243
- validateTokens: 750,
244
- };
245
- // Act
246
- const entries = simulateLedgerEntries(scenario);
247
- // Assert
248
- expect(entries[0].phase_delta_tokens).toBe(750);
249
- });
250
-
251
- test('failed entry carries the actual tokens consumed before the failure — not zero', () => {
252
- // Arrange
253
- const scenario = {
254
- validateFailed: true,
255
- validateTokens: 250,
256
- validateError: 'crash',
257
- validateExitCode: 1,
258
- wbValidatorReport: null,
259
- };
260
- // Act
261
- const entries = simulateLedgerEntries(scenario);
262
- // Assert
263
- expect(entries[0].phase_delta_tokens).toBe(250);
264
- expect(entries[0].phase_delta_tokens).toBeGreaterThan(0);
265
- });
266
-
267
- test('all skipped entries have phase_delta_tokens of zero regardless of scenario token values', () => {
268
- // Arrange — wb-validate failure causes both semantic and render to be skipped
269
- const scenario = {
270
- validateFailed: true,
271
- wbValidatorReport: null,
272
- validateTokens: 999,
273
- semanticTokens: 999,
274
- renderTokens: 999,
275
- };
276
- // Act
277
- const entries = simulateLedgerEntries(scenario);
278
- // Assert — entries[1] and entries[2] are both skipped
279
- expect(entries[1].phase_delta_tokens).toBe(0);
280
- expect(entries[2].phase_delta_tokens).toBe(0);
281
- });
282
-
283
- });
284
-
285
- // ── Group 5: exit_code attribution ───────────────────────────────────────
286
-
287
- describe('exit_code attribution', () => {
288
-
289
- test('wb-validate technical failure with exit_code 2 → entry has exit_code 2', () => {
290
- // Arrange
291
- const scenario = {
292
- validateFailed: true,
293
- validateExitCode: 2,
294
- wbValidatorReport: null,
295
- };
296
- // Act
297
- const entries = simulateLedgerEntries(scenario);
298
- // Assert
299
- expect(entries[0].exit_code).toBe(2);
300
- });
301
-
302
- test('wb-validate technical failure with null exit_code → entry has exit_code null', () => {
303
- // Arrange
304
- const scenario = {
305
- validateFailed: true,
306
- validateExitCode: null,
307
- wbValidatorReport: null,
308
- };
309
- // Act
310
- const entries = simulateLedgerEntries(scenario);
311
- // Assert
312
- expect(entries[0].exit_code).toBeNull();
313
- });
314
-
315
- test('render failure → entry has exit_code taken from renderExitCode', () => {
316
- // Arrange
317
- const scenario = {
318
- validateFailed: false,
319
- wbValidatorReport: REPORT_CLEAN,
320
- renderFailed: true,
321
- renderError: 'crash',
322
- renderExitCode: 5,
323
- };
324
- // Act
325
- const entries = simulateLedgerEntries(scenario);
326
- // Assert
327
- expect(entries[2].exit_code).toBe(5);
328
- });
329
-
330
- test('semantic failure → entry has exit_code null (agent invocation carries no script exit code)', () => {
331
- // Arrange
332
- const scenario = {
333
- validateFailed: false,
334
- wbValidatorReport: REPORT_CLEAN,
335
- semanticFailed: true,
336
- semanticError: 'Timeout',
337
- };
338
- // Act
339
- const entries = simulateLedgerEntries(scenario);
340
- // Assert
341
- expect(entries[1].exit_code).toBeNull();
342
- });
343
-
344
- });
345
-
346
- });