@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.
- package/CHANGELOG.md +79 -0
- package/bin/cli.js +1178 -64
- package/docs/installation.md +74 -8
- package/docs/reference.md +60 -12
- package/lib/asset-catalog.js +52 -0
- package/package.json +9 -2
- package/{.claude → src/claude}/agents/install-toolkit.md +30 -103
- package/{.claude → src/claude}/workflows/am-phase1.js +9 -12
- package/{.claude → src/claude}/workflows/pm-phase2.js +2 -2
- package/.claude/.ai-toolkit-version +0 -1
- package/.claude/scripts/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/ac-table-format.md +0 -48
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-Work-Breakdown.json +0 -234
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-semantic-output.json +0 -4
- package/.claude/scripts/tests/fixtures/test-feature/feature.md +0 -45
- package/.claude/scripts/tests/fixtures/wb-invalid-bad-domain.json +0 -39
- package/.claude/scripts/tests/fixtures/wb-invalid-cycle.json +0 -61
- package/.claude/scripts/tests/fixtures/wb-invalid-duplicate-id.json +0 -72
- package/.claude/scripts/tests/fixtures/wb-invalid-missing-field.json +0 -38
- package/.claude/scripts/tests/fixtures/wb-valid.json +0 -189
- package/.claude/scripts/tests/helpers/buildWaves.js +0 -21
- package/.claude/scripts/tests/helpers/pm-phase2-flow.js +0 -29
- package/.claude/scripts/tests/helpers/pm-phase2-gate2.js +0 -55
- package/.claude/scripts/tests/helpers/pm-phase2-ledger.js +0 -108
- package/.claude/scripts/tests/installer.scripts-distribution.test.js +0 -131
- package/.claude/scripts/tests/pm-phase2.gate2-payload.test.js +0 -353
- package/.claude/scripts/tests/pm-phase2.ledger-transitions.test.js +0 -346
- package/.claude/scripts/tests/pm-phase2.orchestration-flow.test.js +0 -276
- package/.claude/scripts/tests/verify-us-01-t03.js +0 -288
- package/.claude/scripts/tests/verify-us-03-t04.js +0 -262
- package/.claude/scripts/tests/wb-render.checks-md-csv-deps.test.js +0 -337
- package/.claude/scripts/tests/wb-render.checks-sanitize-commit-paths.test.js +0 -352
- package/.claude/scripts/tests/wb-render.regression-pm-phase3-csv.test.js +0 -219
- package/.claude/scripts/tests/wb-validate.checks-1-7.test.js +0 -330
- package/.claude/scripts/tests/wb-validate.checks-11.test.js +0 -166
- package/.claude/scripts/tests/wb-validate.checks-12-14.test.js +0 -222
- package/.claude/scripts/tests/wb-validate.checks-15-18.test.js +0 -359
- package/.claude/scripts/tests/wb-validate.checks-19-21.test.js +0 -362
- package/.claude/scripts/tests/wb-validate.checks-22-23-report.test.js +0 -297
- package/.claude/scripts/tests/wb-validate.checks-8-10.test.js +0 -243
- package/.claude/settings.json +0 -5
- /package/{.claude → src/claude}/agents/concurrency-safety-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/define-feature.md +0 -0
- /package/{.claude → src/claude}/agents/dependency-injection-refactoring.md +0 -0
- /package/{.claude → src/claude}/agents/dependency-supply-chain-security.md +0 -0
- /package/{.claude → src/claude}/agents/developer-backend.md +0 -0
- /package/{.claude → src/claude}/agents/developer-frontend.md +0 -0
- /package/{.claude → src/claude}/agents/developer-testing.md +0 -0
- /package/{.claude → src/claude}/agents/domain-model-refactoring.md +0 -0
- /package/{.claude → src/claude}/agents/generate-requirements.md +0 -0
- /package/{.claude → src/claude}/agents/generate-tech-spec.md +0 -0
- /package/{.claude → src/claude}/agents/generate-work-breakdown.md +0 -0
- /package/{.claude → src/claude}/agents/generic-software-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/god-class-decomposition.md +0 -0
- /package/{.claude → src/claude}/agents/init-agents-md.md +0 -0
- /package/{.claude → src/claude}/agents/intervention-documentation-standard.md +0 -0
- /package/{.claude → src/claude}/agents/layered-architecture-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/review-solution.md +0 -0
- /package/{.claude → src/claude}/agents/security-hardening.md +0 -0
- /package/{.claude → src/claude}/agents/validate-feature-docs.md +0 -0
- /package/{.claude → src/claude}/agents/validate-work-breakdown-semantic.md +0 -0
- /package/{.claude → src/claude}/commands/assessment-status.md +0 -0
- /package/{.claude → src/claude}/commands/check-docs.md +0 -0
- /package/{.claude → src/claude}/commands/check-interventions.md +0 -0
- /package/{.claude → src/claude}/commands/feature-status.md +0 -0
- /package/{.claude → src/claude}/commands/next-intervention.md +0 -0
- /package/{.claude → src/claude}/commands/next-task.md +0 -0
- /package/{.claude → src/claude}/commands/pr-description.md +0 -0
- /package/{.claude → src/claude}/scripts/wb-render.js +0 -0
- /package/{.claude → src/claude}/scripts/wb-validate.js +0 -0
- /package/{.claude → src/claude}/skills/assess-codebase/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/define-feature/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/hi-gaia/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/implement-feature/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/init-agents/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/install-toolkit/SKILL.md +0 -0
- /package/{.claude → src/claude}/workflows/am-phase2.js +0 -0
- /package/{.claude → src/claude}/workflows/pm-phase1.js +0 -0
- /package/{.claude → src/claude}/workflows/pm-phase3.js +0 -0
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
wbValidatorPassed,
|
|
5
|
-
canRunSemantic,
|
|
6
|
-
canRunRender,
|
|
7
|
-
gate2IsBlocked,
|
|
8
|
-
} = require('./helpers/pm-phase2-flow');
|
|
9
|
-
|
|
10
|
-
// ─── shared fixtures ──────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
const REPORT_VALID = { valid: true, errors: [] };
|
|
13
|
-
const REPORT_INVALID = { valid: false, errors: [{ category: 'schema_version_invalid', message: 'bad schema' }] };
|
|
14
|
-
|
|
15
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
describe('pm-phase2 orchestration flow — pure predicates', () => {
|
|
18
|
-
|
|
19
|
-
// ── Group 1: wbValidatorPassed ─────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
describe('wbValidatorPassed()', () => {
|
|
22
|
-
|
|
23
|
-
test('returns true when validateFailed is false and report is valid with empty errors', () => {
|
|
24
|
-
// Arrange
|
|
25
|
-
const validateFailed = false;
|
|
26
|
-
const report = { valid: true, errors: [] };
|
|
27
|
-
// Act
|
|
28
|
-
const result = wbValidatorPassed(validateFailed, report);
|
|
29
|
-
// Assert
|
|
30
|
-
expect(result).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('returns false when validateFailed is true even though report looks valid (technical failure overrides)', () => {
|
|
34
|
-
// Arrange
|
|
35
|
-
const validateFailed = true;
|
|
36
|
-
const report = { valid: true, errors: [] };
|
|
37
|
-
// Act
|
|
38
|
-
const result = wbValidatorPassed(validateFailed, report);
|
|
39
|
-
// Assert
|
|
40
|
-
expect(result).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('returns false when validateFailed is false but report has valid=false and structural errors', () => {
|
|
44
|
-
// Arrange
|
|
45
|
-
const validateFailed = false;
|
|
46
|
-
const report = { valid: false, errors: [{ category: 'missing_field', message: 'outcome missing' }] };
|
|
47
|
-
// Act
|
|
48
|
-
const result = wbValidatorPassed(validateFailed, report);
|
|
49
|
-
// Assert
|
|
50
|
-
expect(result).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('returns false when validateFailed is false and report is null (no result produced)', () => {
|
|
54
|
-
// Arrange
|
|
55
|
-
const validateFailed = false;
|
|
56
|
-
const report = null;
|
|
57
|
-
// Act
|
|
58
|
-
const result = wbValidatorPassed(validateFailed, report);
|
|
59
|
-
// Assert
|
|
60
|
-
expect(result).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('returns false when validateFailed is false but report has valid=true with non-empty errors array', () => {
|
|
64
|
-
// Arrange
|
|
65
|
-
const validateFailed = false;
|
|
66
|
-
const report = { valid: true, errors: [{ category: 'some_error', message: 'something wrong' }] };
|
|
67
|
-
// Act
|
|
68
|
-
const result = wbValidatorPassed(validateFailed, report);
|
|
69
|
-
// Assert
|
|
70
|
-
expect(result).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// ── Group 2: canRunSemantic ────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
describe('canRunSemantic()', () => {
|
|
78
|
-
|
|
79
|
-
test('returns true (semantic can run) when wb-validate passed with a clean valid report', () => {
|
|
80
|
-
// Arrange
|
|
81
|
-
const validateFailed = false;
|
|
82
|
-
const report = REPORT_VALID;
|
|
83
|
-
// Act
|
|
84
|
-
const result = canRunSemantic(validateFailed, report);
|
|
85
|
-
// Assert
|
|
86
|
-
expect(result).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('returns false (semantic is skipped) when wb-validate failed technically', () => {
|
|
90
|
-
// Arrange
|
|
91
|
-
const validateFailed = true;
|
|
92
|
-
const report = REPORT_VALID;
|
|
93
|
-
// Act
|
|
94
|
-
const result = canRunSemantic(validateFailed, report);
|
|
95
|
-
// Assert
|
|
96
|
-
expect(result).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('returns false (semantic is skipped) when wb-validate exit 1 due to structural errors in the report', () => {
|
|
100
|
-
// Arrange
|
|
101
|
-
const validateFailed = false;
|
|
102
|
-
const report = REPORT_INVALID;
|
|
103
|
-
// Act
|
|
104
|
-
const result = canRunSemantic(validateFailed, report);
|
|
105
|
-
// Assert
|
|
106
|
-
expect(result).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ── Group 3: canRunRender ──────────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
describe('canRunRender()', () => {
|
|
114
|
-
|
|
115
|
-
test('returns true when wb-validate passed and semantic did not fail', () => {
|
|
116
|
-
// Arrange
|
|
117
|
-
const validateFailed = false;
|
|
118
|
-
const report = REPORT_VALID;
|
|
119
|
-
const semanticFailed = false;
|
|
120
|
-
// Act
|
|
121
|
-
const result = canRunRender(validateFailed, report, semanticFailed);
|
|
122
|
-
// Assert
|
|
123
|
-
expect(result).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test('returns false when wb-validate passed but semantic did fail', () => {
|
|
127
|
-
// Arrange
|
|
128
|
-
const validateFailed = false;
|
|
129
|
-
const report = REPORT_VALID;
|
|
130
|
-
const semanticFailed = true;
|
|
131
|
-
// Act
|
|
132
|
-
const result = canRunRender(validateFailed, report, semanticFailed);
|
|
133
|
-
// Assert
|
|
134
|
-
expect(result).toBe(false);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('returns false when wb-validate failed technically regardless of semantic outcome', () => {
|
|
138
|
-
// Arrange
|
|
139
|
-
const validateFailed = true;
|
|
140
|
-
const report = REPORT_VALID;
|
|
141
|
-
const semanticFailed = false;
|
|
142
|
-
// Act
|
|
143
|
-
const result = canRunRender(validateFailed, report, semanticFailed);
|
|
144
|
-
// Assert
|
|
145
|
-
expect(result).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('returns false when wb-validate exit 1 (structural errors) regardless of semantic outcome', () => {
|
|
149
|
-
// Arrange
|
|
150
|
-
const validateFailed = false;
|
|
151
|
-
const report = REPORT_INVALID;
|
|
152
|
-
const semanticFailed = false;
|
|
153
|
-
// Act
|
|
154
|
-
const result = canRunRender(validateFailed, report, semanticFailed);
|
|
155
|
-
// Assert
|
|
156
|
-
expect(result).toBe(false);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// ── Group 4: gate2IsBlocked ────────────────────────────────────────────────
|
|
162
|
-
|
|
163
|
-
describe('gate2IsBlocked()', () => {
|
|
164
|
-
|
|
165
|
-
test('returns false (gate is open) when all steps passed and semantic has no blocking findings', () => {
|
|
166
|
-
// Arrange
|
|
167
|
-
const validateFailed = false;
|
|
168
|
-
const report = REPORT_VALID;
|
|
169
|
-
const semanticFailed = false;
|
|
170
|
-
const renderFailed = false;
|
|
171
|
-
const semanticResult = { findings: [{ blocking: false, message: 'style suggestion' }] };
|
|
172
|
-
// Act
|
|
173
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
174
|
-
// Assert
|
|
175
|
-
expect(result).toBe(false);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('returns true when validateFailed is true', () => {
|
|
179
|
-
// Arrange
|
|
180
|
-
const validateFailed = true;
|
|
181
|
-
const report = REPORT_VALID;
|
|
182
|
-
const semanticFailed = false;
|
|
183
|
-
const renderFailed = false;
|
|
184
|
-
const semanticResult = null;
|
|
185
|
-
// Act
|
|
186
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
187
|
-
// Assert
|
|
188
|
-
expect(result).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test('returns true when wb-validate exit 1 (report.valid is false)', () => {
|
|
192
|
-
// Arrange
|
|
193
|
-
const validateFailed = false;
|
|
194
|
-
const report = REPORT_INVALID;
|
|
195
|
-
const semanticFailed = false;
|
|
196
|
-
const renderFailed = false;
|
|
197
|
-
const semanticResult = null;
|
|
198
|
-
// Act
|
|
199
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
200
|
-
// Assert
|
|
201
|
-
expect(result).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('returns true when semanticFailed is true', () => {
|
|
205
|
-
// Arrange
|
|
206
|
-
const validateFailed = false;
|
|
207
|
-
const report = REPORT_VALID;
|
|
208
|
-
const semanticFailed = true;
|
|
209
|
-
const renderFailed = false;
|
|
210
|
-
const semanticResult = null;
|
|
211
|
-
// Act
|
|
212
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
213
|
-
// Assert
|
|
214
|
-
expect(result).toBe(true);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('returns true when renderFailed is true', () => {
|
|
218
|
-
// Arrange
|
|
219
|
-
const validateFailed = false;
|
|
220
|
-
const report = REPORT_VALID;
|
|
221
|
-
const semanticFailed = false;
|
|
222
|
-
const renderFailed = true;
|
|
223
|
-
const semanticResult = null;
|
|
224
|
-
// Act
|
|
225
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
226
|
-
// Assert
|
|
227
|
-
expect(result).toBe(true);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test('returns true when semantic result contains at least one blocking finding', () => {
|
|
231
|
-
// Arrange
|
|
232
|
-
const validateFailed = false;
|
|
233
|
-
const report = REPORT_VALID;
|
|
234
|
-
const semanticFailed = false;
|
|
235
|
-
const renderFailed = false;
|
|
236
|
-
const semanticResult = { findings: [{ blocking: true, message: 'critical scope gap' }] };
|
|
237
|
-
// Act
|
|
238
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
239
|
-
// Assert
|
|
240
|
-
expect(result).toBe(true);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('returns false when semantic result has only non-blocking findings (gate allows proceed)', () => {
|
|
244
|
-
// Arrange
|
|
245
|
-
const validateFailed = false;
|
|
246
|
-
const report = REPORT_VALID;
|
|
247
|
-
const semanticFailed = false;
|
|
248
|
-
const renderFailed = false;
|
|
249
|
-
const semanticResult = {
|
|
250
|
-
findings: [
|
|
251
|
-
{ blocking: false, message: 'minor wording suggestion' },
|
|
252
|
-
{ blocking: false, message: 'optional improvement' },
|
|
253
|
-
],
|
|
254
|
-
};
|
|
255
|
-
// Act
|
|
256
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
257
|
-
// Assert
|
|
258
|
-
expect(result).toBe(false);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('returns true when multiple failure conditions are true simultaneously', () => {
|
|
262
|
-
// Arrange
|
|
263
|
-
const validateFailed = true;
|
|
264
|
-
const report = REPORT_INVALID;
|
|
265
|
-
const semanticFailed = true;
|
|
266
|
-
const renderFailed = true;
|
|
267
|
-
const semanticResult = { findings: [{ blocking: true, message: 'critical gap' }] };
|
|
268
|
-
// Act
|
|
269
|
-
const result = gate2IsBlocked(validateFailed, report, semanticFailed, renderFailed, semanticResult);
|
|
270
|
-
// Assert
|
|
271
|
-
expect(result).toBe(true);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
});
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* US-01-T03: Schema v2 Compliance Verification
|
|
5
|
-
*
|
|
6
|
-
* Reads TEST-001-Work-Breakdown.json and performs 9 structural checks
|
|
7
|
-
* derived from the INFRA-T02 constants in wb-validate.js — with no
|
|
8
|
-
* dependency on wb-validate.js itself (runs in parallel with US-02).
|
|
9
|
-
*
|
|
10
|
-
* Uses only Node.js built-ins: fs, path.
|
|
11
|
-
* Exit 0 = all checks pass; Exit 1 = one or more checks fail.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
|
|
17
|
-
// ─── Schema v2 constants (sourced from wb-validate.js — not imported) ────────
|
|
18
|
-
|
|
19
|
-
const REQUIRED_PHASE_FIELDS = ['id', 'type', 'title', 'commit', 'tasks'];
|
|
20
|
-
|
|
21
|
-
const REQUIRED_TASK_FIELDS = [
|
|
22
|
-
'id',
|
|
23
|
-
'title',
|
|
24
|
-
'outcome',
|
|
25
|
-
'domain',
|
|
26
|
-
'agentType',
|
|
27
|
-
'dependsOn',
|
|
28
|
-
'acceptanceCriteria',
|
|
29
|
-
'verification',
|
|
30
|
-
'estimate',
|
|
31
|
-
'outputCount',
|
|
32
|
-
'groupingRationale',
|
|
33
|
-
'commit',
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const VALID_DOMAINS = ['BE', 'FE', 'DB', 'DevOps', 'INFRA', 'TEST'];
|
|
37
|
-
|
|
38
|
-
const VALID_AGENT_TYPES = [
|
|
39
|
-
'developer-backend',
|
|
40
|
-
'developer-frontend',
|
|
41
|
-
'developer-testing',
|
|
42
|
-
'developer-database',
|
|
43
|
-
'review-solution',
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
// ─── Fixture path (resolved relative to this script file) ────────────────────
|
|
47
|
-
|
|
48
|
-
const FIXTURE_PATH = path.join(
|
|
49
|
-
__dirname,
|
|
50
|
-
'fixtures',
|
|
51
|
-
'test-feature',
|
|
52
|
-
'TEST-001-Work-Breakdown.json'
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// ─── Report accumulators ─────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
const passes = [];
|
|
58
|
-
const failures = [];
|
|
59
|
-
|
|
60
|
-
function pass(label) {
|
|
61
|
-
passes.push(label);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function fail(label, detail) {
|
|
65
|
-
failures.push({ label, detail });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ─── Load and parse fixture ───────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
let wb;
|
|
71
|
-
try {
|
|
72
|
-
const raw = fs.readFileSync(FIXTURE_PATH, 'utf8');
|
|
73
|
-
wb = JSON.parse(raw);
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error(`FATAL: Cannot read or parse fixture at ${FIXTURE_PATH}`);
|
|
76
|
-
console.error(` ${err.message}`);
|
|
77
|
-
process.exit(2);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ─── Build full task-ID index (used by checks 3, 4, 8) ───────────────────────
|
|
81
|
-
|
|
82
|
-
const allTaskIds = new Set();
|
|
83
|
-
for (const phase of (wb.phases || [])) {
|
|
84
|
-
for (const task of (phase.tasks || [])) {
|
|
85
|
-
if (task.id != null) allTaskIds.add(task.id);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ─── Check 1: schemaVersion === 2 ────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
if (wb.schemaVersion === 2) {
|
|
92
|
-
pass('Check 1 — schemaVersion === 2');
|
|
93
|
-
} else {
|
|
94
|
-
fail(
|
|
95
|
-
'Check 1 — schemaVersion === 2',
|
|
96
|
-
`Expected 2, got: ${JSON.stringify(wb.schemaVersion)}`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ─── Check 2: every phase has all REQUIRED_PHASE_FIELDS ──────────────────────
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
let ok = true;
|
|
104
|
-
for (const phase of (wb.phases || [])) {
|
|
105
|
-
const phaseId = phase.id ?? '(no id)';
|
|
106
|
-
for (const field of REQUIRED_PHASE_FIELDS) {
|
|
107
|
-
if (!(field in phase)) {
|
|
108
|
-
fail(
|
|
109
|
-
'Check 2 — every phase has all REQUIRED_PHASE_FIELDS',
|
|
110
|
-
`Phase "${phaseId}" is missing field "${field}"`
|
|
111
|
-
);
|
|
112
|
-
ok = false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (ok) pass('Check 2 — every phase has all REQUIRED_PHASE_FIELDS');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ─── Check 3: every task has all REQUIRED_TASK_FIELDS ────────────────────────
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
let ok = true;
|
|
123
|
-
for (const phase of (wb.phases || [])) {
|
|
124
|
-
for (const task of (phase.tasks || [])) {
|
|
125
|
-
const taskId = task.id ?? '(no id)';
|
|
126
|
-
for (const field of REQUIRED_TASK_FIELDS) {
|
|
127
|
-
if (!(field in task)) {
|
|
128
|
-
fail(
|
|
129
|
-
'Check 3 — every task has all REQUIRED_TASK_FIELDS',
|
|
130
|
-
`Task "${taskId}" is missing field "${field}"`
|
|
131
|
-
);
|
|
132
|
-
ok = false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (ok) pass('Check 3 — every task has all REQUIRED_TASK_FIELDS');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ─── Check 4: every task.domain is in VALID_DOMAINS ──────────────────────────
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
let ok = true;
|
|
144
|
-
for (const phase of (wb.phases || [])) {
|
|
145
|
-
for (const task of (phase.tasks || [])) {
|
|
146
|
-
if (!VALID_DOMAINS.includes(task.domain)) {
|
|
147
|
-
fail(
|
|
148
|
-
'Check 4 — every task.domain is in VALID_DOMAINS',
|
|
149
|
-
`Task "${task.id}" has domain "${task.domain}"; allowed: ${VALID_DOMAINS.join(', ')}`
|
|
150
|
-
);
|
|
151
|
-
ok = false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (ok) pass('Check 4 — every task.domain is in VALID_DOMAINS');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ─── Check 5: every task.agentType is in VALID_AGENT_TYPES ───────────────────
|
|
159
|
-
|
|
160
|
-
{
|
|
161
|
-
let ok = true;
|
|
162
|
-
for (const phase of (wb.phases || [])) {
|
|
163
|
-
for (const task of (phase.tasks || [])) {
|
|
164
|
-
if (!VALID_AGENT_TYPES.includes(task.agentType)) {
|
|
165
|
-
fail(
|
|
166
|
-
'Check 5 — every task.agentType is in VALID_AGENT_TYPES',
|
|
167
|
-
`Task "${task.id}" has agentType "${task.agentType}"; allowed: ${VALID_AGENT_TYPES.join(', ')}`
|
|
168
|
-
);
|
|
169
|
-
ok = false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (ok) pass('Check 5 — every task.agentType is in VALID_AGENT_TYPES');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ─── Check 6: every task.estimate.agentMinutes exists and is ≤ 15 ────────────
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
let ok = true;
|
|
180
|
-
for (const phase of (wb.phases || [])) {
|
|
181
|
-
for (const task of (phase.tasks || [])) {
|
|
182
|
-
const minutes = task.estimate?.agentMinutes;
|
|
183
|
-
if (minutes == null) {
|
|
184
|
-
fail(
|
|
185
|
-
'Check 6 — task.estimate.agentMinutes exists and is <= 15',
|
|
186
|
-
`Task "${task.id}" is missing estimate.agentMinutes`
|
|
187
|
-
);
|
|
188
|
-
ok = false;
|
|
189
|
-
} else if (typeof minutes !== 'number') {
|
|
190
|
-
fail(
|
|
191
|
-
'Check 6 — task.estimate.agentMinutes exists and is <= 15',
|
|
192
|
-
`Task "${task.id}" has non-numeric estimate.agentMinutes: ${JSON.stringify(minutes)}`
|
|
193
|
-
);
|
|
194
|
-
ok = false;
|
|
195
|
-
} else if (minutes > 15) {
|
|
196
|
-
fail(
|
|
197
|
-
'Check 6 — task.estimate.agentMinutes exists and is <= 15',
|
|
198
|
-
`Task "${task.id}" has agentMinutes=${minutes} which exceeds the 15-minute target`
|
|
199
|
-
);
|
|
200
|
-
ok = false;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (ok) pass('Check 6 — every task.estimate.agentMinutes exists and is <= 15');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ─── Check 7: every task.verification.commands is a non-empty array ──────────
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
let ok = true;
|
|
211
|
-
for (const phase of (wb.phases || [])) {
|
|
212
|
-
for (const task of (phase.tasks || [])) {
|
|
213
|
-
const cmds = task.verification?.commands;
|
|
214
|
-
if (!Array.isArray(cmds)) {
|
|
215
|
-
fail(
|
|
216
|
-
'Check 7 — task.verification.commands is a non-empty array',
|
|
217
|
-
`Task "${task.id}" has verification.commands that is not an array: ${JSON.stringify(cmds)}`
|
|
218
|
-
);
|
|
219
|
-
ok = false;
|
|
220
|
-
} else if (cmds.length === 0) {
|
|
221
|
-
fail(
|
|
222
|
-
'Check 7 — task.verification.commands is a non-empty array',
|
|
223
|
-
`Task "${task.id}" has an empty verification.commands array`
|
|
224
|
-
);
|
|
225
|
-
ok = false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (ok) pass('Check 7 — every task.verification.commands is a non-empty array');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ─── Check 8: no task depends on an ID not present in the JSON ───────────────
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
let ok = true;
|
|
236
|
-
for (const phase of (wb.phases || [])) {
|
|
237
|
-
for (const task of (phase.tasks || [])) {
|
|
238
|
-
for (const dep of (task.dependsOn || [])) {
|
|
239
|
-
if (!allTaskIds.has(dep)) {
|
|
240
|
-
fail(
|
|
241
|
-
'Check 8 — no dangling dependsOn references',
|
|
242
|
-
`Task "${task.id}" depends on "${dep}" which does not exist in the JSON`
|
|
243
|
-
);
|
|
244
|
-
ok = false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (ok) pass('Check 8 — no dangling dependsOn references');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ─── Check 9: fixture has at least one phase and one task ────────────────────
|
|
253
|
-
// (sanity guard: catches a structurally empty fixture that would silently pass)
|
|
254
|
-
|
|
255
|
-
{
|
|
256
|
-
const phaseCount = (wb.phases || []).length;
|
|
257
|
-
const taskCount = allTaskIds.size;
|
|
258
|
-
if (phaseCount >= 1 && taskCount >= 1) {
|
|
259
|
-
pass(`Check 9 — fixture is non-trivial (${phaseCount} phases, ${taskCount} tasks)`);
|
|
260
|
-
} else {
|
|
261
|
-
fail(
|
|
262
|
-
'Check 9 — fixture is non-trivial',
|
|
263
|
-
`Expected at least 1 phase and 1 task; found ${phaseCount} phase(s) and ${taskCount} task(s)`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ─── Print report ─────────────────────────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
console.log('\n=== Schema v2 Compliance Report — TEST-001-Work-Breakdown.json ===\n');
|
|
271
|
-
console.log(`Fixture: ${FIXTURE_PATH}\n`);
|
|
272
|
-
|
|
273
|
-
for (const p of passes) {
|
|
274
|
-
console.log(` PASS ${p}`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (failures.length > 0) {
|
|
278
|
-
console.log('');
|
|
279
|
-
for (const f of failures) {
|
|
280
|
-
console.log(` FAIL ${f.label}`);
|
|
281
|
-
console.log(` ${f.detail}`);
|
|
282
|
-
}
|
|
283
|
-
console.log(`\nResult: FAIL — ${failures.length} failure(s), ${passes.length} check(s) passed\n`);
|
|
284
|
-
process.exit(1);
|
|
285
|
-
} else {
|
|
286
|
-
console.log(`\nResult: PASS — all ${passes.length} checks passed\n`);
|
|
287
|
-
process.exit(0);
|
|
288
|
-
}
|