@dtlabs/ai-toolkit 0.9.0 → 0.10.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/.claude/agents/generate-work-breakdown.md +212 -192
- package/.claude/agents/install-toolkit.md +6 -1
- package/.claude/agents/validate-work-breakdown-semantic.md +96 -0
- package/.claude/scripts/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/ac-table-format.md +48 -0
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-Work-Breakdown.json +234 -0
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-semantic-output.json +4 -0
- package/.claude/scripts/tests/fixtures/test-feature/feature.md +45 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-bad-domain.json +39 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-cycle.json +61 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-duplicate-id.json +72 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-missing-field.json +38 -0
- package/.claude/scripts/tests/fixtures/wb-valid.json +189 -0
- package/.claude/scripts/tests/helpers/buildWaves.js +21 -0
- package/.claude/scripts/tests/helpers/pm-phase2-flow.js +29 -0
- package/.claude/scripts/tests/helpers/pm-phase2-gate2.js +55 -0
- package/.claude/scripts/tests/helpers/pm-phase2-ledger.js +108 -0
- package/.claude/scripts/tests/installer.scripts-distribution.test.js +131 -0
- package/.claude/scripts/tests/pm-phase2.gate2-payload.test.js +353 -0
- package/.claude/scripts/tests/pm-phase2.ledger-transitions.test.js +346 -0
- package/.claude/scripts/tests/pm-phase2.orchestration-flow.test.js +276 -0
- package/.claude/scripts/tests/verify-us-01-t03.js +288 -0
- package/.claude/scripts/tests/verify-us-03-t04.js +262 -0
- package/.claude/scripts/tests/wb-render.checks-md-csv-deps.test.js +337 -0
- package/.claude/scripts/tests/wb-render.checks-sanitize-commit-paths.test.js +352 -0
- package/.claude/scripts/tests/wb-render.regression-pm-phase3-csv.test.js +219 -0
- package/.claude/scripts/tests/wb-validate.checks-1-7.test.js +330 -0
- package/.claude/scripts/tests/wb-validate.checks-11.test.js +166 -0
- package/.claude/scripts/tests/wb-validate.checks-12-14.test.js +222 -0
- package/.claude/scripts/tests/wb-validate.checks-15-18.test.js +359 -0
- package/.claude/scripts/tests/wb-validate.checks-19-21.test.js +362 -0
- package/.claude/scripts/tests/wb-validate.checks-22-23-report.test.js +297 -0
- package/.claude/scripts/tests/wb-validate.checks-8-10.test.js +243 -0
- package/.claude/scripts/wb-render.js +309 -0
- package/.claude/scripts/wb-validate.js +1007 -0
- package/.claude/workflows/pm-phase2.js +289 -28
- package/CHANGELOG.md +67 -0
- package/bin/cli.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"feature": "FTR-014",
|
|
4
|
+
"phases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "INFRA",
|
|
7
|
+
"type": "infrastructure",
|
|
8
|
+
"title": "Shared infrastructure setup",
|
|
9
|
+
"commit": {
|
|
10
|
+
"type": "feat",
|
|
11
|
+
"subject": "add shared infrastructure scaffolding"
|
|
12
|
+
},
|
|
13
|
+
"tasks": [
|
|
14
|
+
{
|
|
15
|
+
"id": "INFRA-TASK-INFRA-01",
|
|
16
|
+
"title": "Create scripts directory structure",
|
|
17
|
+
"outcome": "The .claude/scripts directory exists and is included in installer copy paths",
|
|
18
|
+
"domain": "INFRA",
|
|
19
|
+
"agentType": "developer-backend",
|
|
20
|
+
"dependsOn": [],
|
|
21
|
+
"acceptanceCriteria": [],
|
|
22
|
+
"verification": {
|
|
23
|
+
"commands": [
|
|
24
|
+
"ls .claude/scripts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"estimate": {
|
|
28
|
+
"agentMinutes": 5,
|
|
29
|
+
"tokens": 8000
|
|
30
|
+
},
|
|
31
|
+
"outputCount": 1,
|
|
32
|
+
"groupingRationale": "Single responsibility",
|
|
33
|
+
"commit": {
|
|
34
|
+
"type": "feat",
|
|
35
|
+
"subject": "create .claude/scripts directory"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "INFRA-TASK-INFRA-02",
|
|
40
|
+
"title": "Define schema v2 constants in wb-validate.js",
|
|
41
|
+
"outcome": "Constants block (SCHEMA_VERSION, REQUIRED_PHASE_FIELDS, REQUIRED_TASK_FIELDS, VALID_DOMAINS, VALID_AGENT_TYPES) is present at the top of wb-validate.js",
|
|
42
|
+
"domain": "INFRA",
|
|
43
|
+
"agentType": "developer-backend",
|
|
44
|
+
"dependsOn": [
|
|
45
|
+
"INFRA-TASK-INFRA-01"
|
|
46
|
+
],
|
|
47
|
+
"acceptanceCriteria": [],
|
|
48
|
+
"verification": {
|
|
49
|
+
"commands": [
|
|
50
|
+
"node -e \"const v = require('./.claude/scripts/wb-validate.js'); console.log('ok')\""
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"estimate": {
|
|
54
|
+
"agentMinutes": 10,
|
|
55
|
+
"tokens": 15000
|
|
56
|
+
},
|
|
57
|
+
"outputCount": 1,
|
|
58
|
+
"groupingRationale": "Single responsibility",
|
|
59
|
+
"commit": {
|
|
60
|
+
"type": "feat",
|
|
61
|
+
"subject": "add schema v2 constants to wb-validate.js"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "INFRA-TASK-TEST-01",
|
|
66
|
+
"title": "Create test fixture for AC table parser",
|
|
67
|
+
"outcome": "tests/fixtures/ac-table-format.md exists with a valid AC table in the Section 3.2 contract format",
|
|
68
|
+
"domain": "TEST",
|
|
69
|
+
"agentType": "developer-testing",
|
|
70
|
+
"dependsOn": [],
|
|
71
|
+
"acceptanceCriteria": [],
|
|
72
|
+
"verification": {
|
|
73
|
+
"commands": [
|
|
74
|
+
"ls .claude/scripts/tests/fixtures/ac-table-format.md"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"estimate": {
|
|
78
|
+
"agentMinutes": 8,
|
|
79
|
+
"tokens": 12000
|
|
80
|
+
},
|
|
81
|
+
"outputCount": 1,
|
|
82
|
+
"groupingRationale": "Single responsibility",
|
|
83
|
+
"commit": {
|
|
84
|
+
"type": "test",
|
|
85
|
+
"subject": "add AC table parser fixture"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "US-01",
|
|
92
|
+
"type": "user-story",
|
|
93
|
+
"title": "Implement deterministic structural validator wb-validate.js",
|
|
94
|
+
"commit": {
|
|
95
|
+
"type": "feat",
|
|
96
|
+
"subject": "implement wb-validate.js structural validator"
|
|
97
|
+
},
|
|
98
|
+
"tasks": [
|
|
99
|
+
{
|
|
100
|
+
"id": "US-01-TASK-BE-01",
|
|
101
|
+
"title": "Implement wb-validate.js entry point with JSON parse and schema version check",
|
|
102
|
+
"outcome": "wb-validate.js parses the input JSON file and exits with code 2 if the file is missing or malformed, and exits with code 1 if schemaVersion is not exactly 2",
|
|
103
|
+
"domain": "BE",
|
|
104
|
+
"agentType": "developer-backend",
|
|
105
|
+
"dependsOn": [
|
|
106
|
+
"INFRA-TASK-INFRA-02"
|
|
107
|
+
],
|
|
108
|
+
"acceptanceCriteria": [
|
|
109
|
+
"AC-05"
|
|
110
|
+
],
|
|
111
|
+
"verification": {
|
|
112
|
+
"commands": [
|
|
113
|
+
"node .claude/scripts/wb-validate.js --help",
|
|
114
|
+
"npm test -- tests/cli/wb-validate.test.js --testNamePattern='schema version'"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"estimate": {
|
|
118
|
+
"agentMinutes": 10,
|
|
119
|
+
"tokens": 20000
|
|
120
|
+
},
|
|
121
|
+
"outputCount": 1,
|
|
122
|
+
"groupingRationale": "Single responsibility",
|
|
123
|
+
"commit": {
|
|
124
|
+
"type": "feat",
|
|
125
|
+
"subject": "add wb-validate.js entry point with JSON parse and schema check"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "US-01-TASK-BE-02",
|
|
130
|
+
"title": "Implement unique ID and required field validation in wb-validate.js",
|
|
131
|
+
"outcome": "wb-validate.js detects duplicate task IDs across all phases and reports missing or null required phase/task fields with category unique_id_violation or missing_field",
|
|
132
|
+
"domain": "BE",
|
|
133
|
+
"agentType": "developer-backend",
|
|
134
|
+
"dependsOn": [
|
|
135
|
+
"US-01-TASK-BE-01"
|
|
136
|
+
],
|
|
137
|
+
"acceptanceCriteria": [
|
|
138
|
+
"AC-05"
|
|
139
|
+
],
|
|
140
|
+
"verification": {
|
|
141
|
+
"commands": [
|
|
142
|
+
"npm test -- tests/cli/wb-validate.test.js --testNamePattern='unique ID'",
|
|
143
|
+
"npm test -- tests/cli/wb-validate.test.js --testNamePattern='required field'"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"estimate": {
|
|
147
|
+
"agentMinutes": 8,
|
|
148
|
+
"tokens": 18000
|
|
149
|
+
},
|
|
150
|
+
"outputCount": 1,
|
|
151
|
+
"groupingRationale": "Single responsibility",
|
|
152
|
+
"commit": {
|
|
153
|
+
"type": "feat",
|
|
154
|
+
"subject": "add unique ID and required field validation to wb-validate.js"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "US-01-TASK-TEST-01",
|
|
159
|
+
"title": "Write Jest tests for wb-validate.js schema version and unique ID checks",
|
|
160
|
+
"outcome": "tests/cli/wb-validate.test.js exists and all schema version and unique ID test cases pass under npm test",
|
|
161
|
+
"domain": "TEST",
|
|
162
|
+
"agentType": "developer-testing",
|
|
163
|
+
"dependsOn": [
|
|
164
|
+
"US-01-TASK-BE-01",
|
|
165
|
+
"US-01-TASK-BE-02"
|
|
166
|
+
],
|
|
167
|
+
"acceptanceCriteria": [
|
|
168
|
+
"AC-05"
|
|
169
|
+
],
|
|
170
|
+
"verification": {
|
|
171
|
+
"commands": [
|
|
172
|
+
"npm test -- tests/cli/wb-validate.test.js"
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
"estimate": {
|
|
176
|
+
"agentMinutes": 15,
|
|
177
|
+
"tokens": 30000
|
|
178
|
+
},
|
|
179
|
+
"outputCount": 1,
|
|
180
|
+
"groupingRationale": "Single responsibility",
|
|
181
|
+
"commit": {
|
|
182
|
+
"type": "test",
|
|
183
|
+
"subject": "add wb-validate.js schema version and unique ID tests"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Extracted verbatim from pm-phase3.js — must remain in sync
|
|
2
|
+
|
|
3
|
+
const buildWaves = (phases) => {
|
|
4
|
+
const done = new Set()
|
|
5
|
+
const waves = []
|
|
6
|
+
let remaining = [...phases]
|
|
7
|
+
while (remaining.length > 0) {
|
|
8
|
+
const ready = remaining.filter(p => p.depends_on.every(d => done.has(d)))
|
|
9
|
+
if (ready.length === 0) {
|
|
10
|
+
// Circular or unresolvable deps — run the rest sequentially to avoid deadlock
|
|
11
|
+
waves.push(remaining)
|
|
12
|
+
break
|
|
13
|
+
}
|
|
14
|
+
waves.push(ready)
|
|
15
|
+
ready.forEach(p => done.add(p.phase_id))
|
|
16
|
+
remaining = remaining.filter(p => !done.has(p.phase_id))
|
|
17
|
+
}
|
|
18
|
+
return waves
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { buildWaves }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Pure orchestration predicates extracted from pm-phase2 for testability
|
|
4
|
+
|
|
5
|
+
function wbValidatorPassed(validateFailed, wbValidatorReport) {
|
|
6
|
+
return !validateFailed
|
|
7
|
+
&& wbValidatorReport !== null
|
|
8
|
+
&& wbValidatorReport.valid === true
|
|
9
|
+
&& Array.isArray(wbValidatorReport.errors)
|
|
10
|
+
&& wbValidatorReport.errors.length === 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function canRunSemantic(validateFailed, wbValidatorReport) {
|
|
14
|
+
return wbValidatorPassed(validateFailed, wbValidatorReport)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function canRunRender(validateFailed, wbValidatorReport, semanticFailed) {
|
|
18
|
+
return wbValidatorPassed(validateFailed, wbValidatorReport) && !semanticFailed
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function gate2IsBlocked(validateFailed, wbValidatorReport, semanticFailed, renderFailed, semanticResult) {
|
|
22
|
+
return validateFailed
|
|
23
|
+
|| !wbValidatorPassed(validateFailed, wbValidatorReport)
|
|
24
|
+
|| semanticFailed
|
|
25
|
+
|| renderFailed
|
|
26
|
+
|| Boolean(semanticResult && semanticResult.findings && semanticResult.findings.some(f => f.blocking))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { wbValidatorPassed, canRunSemantic, canRunRender, gate2IsBlocked }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function buildGate2Payload(options) {
|
|
4
|
+
const {
|
|
5
|
+
wbValidatorReport = null,
|
|
6
|
+
validateFailed = false,
|
|
7
|
+
semanticResult = null,
|
|
8
|
+
semanticFailed = false,
|
|
9
|
+
renderResult = null,
|
|
10
|
+
renderFailed = false,
|
|
11
|
+
} = options
|
|
12
|
+
|
|
13
|
+
const wbValidatorPassed = !validateFailed
|
|
14
|
+
&& wbValidatorReport !== null
|
|
15
|
+
&& wbValidatorReport.valid === true
|
|
16
|
+
&& Array.isArray(wbValidatorReport.errors)
|
|
17
|
+
&& wbValidatorReport.errors.length === 0
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
js_validator_report: wbValidatorReport,
|
|
21
|
+
js_validator_failed: validateFailed,
|
|
22
|
+
semantic_validator_result: semanticResult,
|
|
23
|
+
semantic_validator_failed: semanticFailed,
|
|
24
|
+
renderer_result: renderResult,
|
|
25
|
+
renderer_failed: renderFailed,
|
|
26
|
+
duration_bands: wbValidatorReport ? wbValidatorReport.durationBands : null,
|
|
27
|
+
domain_distribution: wbValidatorReport ? wbValidatorReport.domainDistribution : null,
|
|
28
|
+
warning_band_tasks: wbValidatorReport
|
|
29
|
+
? (wbValidatorReport.warnings || [])
|
|
30
|
+
.filter(w => w.category === 'duration_warning')
|
|
31
|
+
.map(w => ({ taskId: w.taskId, agentMinutes: w.details ? w.details.agentMinutes : null }))
|
|
32
|
+
: [],
|
|
33
|
+
split_required_tasks: wbValidatorReport
|
|
34
|
+
? (wbValidatorReport.errors || [])
|
|
35
|
+
.filter(e => e.category === 'split_required')
|
|
36
|
+
.map(e => ({ taskId: e.taskId, agentMinutes: e.details ? e.details.agentMinutes : null }))
|
|
37
|
+
: [],
|
|
38
|
+
must_ac_uncovered: wbValidatorReport
|
|
39
|
+
? (wbValidatorReport.errors || [])
|
|
40
|
+
.filter(e => e.category === 'must_ac_uncovered')
|
|
41
|
+
.map(e => ({ acId: e.details ? e.details.acId : null }))
|
|
42
|
+
: [],
|
|
43
|
+
phase_unschedulable: wbValidatorReport && wbValidatorReport.dependencies
|
|
44
|
+
? (wbValidatorReport.dependencies.phaseUnschedulable || [])
|
|
45
|
+
: [],
|
|
46
|
+
gate2_blocked:
|
|
47
|
+
validateFailed ||
|
|
48
|
+
!wbValidatorPassed ||
|
|
49
|
+
semanticFailed ||
|
|
50
|
+
renderFailed ||
|
|
51
|
+
Boolean(semanticResult && semanticResult.findings && semanticResult.findings.some(f => f.blocking)),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { buildGate2Payload }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Pure simulation of the ledger state machine from pm-phase2.js
|
|
4
|
+
// Input: the scenario (which steps pass/fail/skip)
|
|
5
|
+
// Output: array of ledger entry specifications that pm-phase2 would write
|
|
6
|
+
|
|
7
|
+
function simulateLedgerEntries(scenario) {
|
|
8
|
+
const {
|
|
9
|
+
validateFailed = false,
|
|
10
|
+
validateExitCode = 0,
|
|
11
|
+
validateError = null,
|
|
12
|
+
validateTokens = 0,
|
|
13
|
+
wbValidatorReport = null,
|
|
14
|
+
semanticSkipped = false,
|
|
15
|
+
semanticFailed = false,
|
|
16
|
+
semanticError = null,
|
|
17
|
+
semanticTokens = 0,
|
|
18
|
+
renderSkipped = false,
|
|
19
|
+
renderFailed = false,
|
|
20
|
+
renderError = null,
|
|
21
|
+
renderExitCode = null,
|
|
22
|
+
renderTokens = 0,
|
|
23
|
+
} = scenario
|
|
24
|
+
|
|
25
|
+
const wbValidatorPassed = !validateFailed
|
|
26
|
+
&& wbValidatorReport !== null
|
|
27
|
+
&& wbValidatorReport.valid === true
|
|
28
|
+
&& Array.isArray(wbValidatorReport.errors)
|
|
29
|
+
&& wbValidatorReport.errors.length === 0
|
|
30
|
+
|
|
31
|
+
const entries = []
|
|
32
|
+
|
|
33
|
+
// Step 2: wb-validate
|
|
34
|
+
// Always appends 'running', then updates to 'done' or 'failed'
|
|
35
|
+
entries.push({
|
|
36
|
+
agent: 'wb-validate:phase2',
|
|
37
|
+
status_sequence: validateFailed ? ['running', 'failed'] : ['running', 'done'],
|
|
38
|
+
final_status: validateFailed ? 'failed' : 'done',
|
|
39
|
+
phase_delta_tokens: validateTokens,
|
|
40
|
+
error_summary: validateFailed ? (validateError || '<unknown error>') : undefined,
|
|
41
|
+
exit_code: validateFailed ? (validateExitCode ?? null) : undefined,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Step 3: semantic validator
|
|
45
|
+
if (!wbValidatorPassed) {
|
|
46
|
+
entries.push({
|
|
47
|
+
agent: 'validate-work-breakdown-semantic:phase2',
|
|
48
|
+
status_sequence: ['skipped'],
|
|
49
|
+
final_status: 'skipped',
|
|
50
|
+
phase_delta_tokens: 0,
|
|
51
|
+
error_summary: undefined,
|
|
52
|
+
exit_code: undefined,
|
|
53
|
+
})
|
|
54
|
+
} else if (semanticFailed) {
|
|
55
|
+
entries.push({
|
|
56
|
+
agent: 'validate-work-breakdown-semantic:phase2',
|
|
57
|
+
status_sequence: ['running', 'failed'],
|
|
58
|
+
final_status: 'failed',
|
|
59
|
+
phase_delta_tokens: semanticTokens,
|
|
60
|
+
error_summary: semanticError || '<unknown error>',
|
|
61
|
+
exit_code: null,
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
entries.push({
|
|
65
|
+
agent: 'validate-work-breakdown-semantic:phase2',
|
|
66
|
+
status_sequence: ['running', 'done'],
|
|
67
|
+
final_status: 'done',
|
|
68
|
+
phase_delta_tokens: semanticTokens,
|
|
69
|
+
error_summary: undefined,
|
|
70
|
+
exit_code: undefined,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 4: wb-render
|
|
75
|
+
const canRender = wbValidatorPassed && !semanticFailed
|
|
76
|
+
if (!canRender) {
|
|
77
|
+
entries.push({
|
|
78
|
+
agent: 'wb-render:phase2',
|
|
79
|
+
status_sequence: ['skipped'],
|
|
80
|
+
final_status: 'skipped',
|
|
81
|
+
phase_delta_tokens: 0,
|
|
82
|
+
error_summary: undefined,
|
|
83
|
+
exit_code: undefined,
|
|
84
|
+
})
|
|
85
|
+
} else if (renderFailed) {
|
|
86
|
+
entries.push({
|
|
87
|
+
agent: 'wb-render:phase2',
|
|
88
|
+
status_sequence: ['running', 'failed'],
|
|
89
|
+
final_status: 'failed',
|
|
90
|
+
phase_delta_tokens: renderTokens,
|
|
91
|
+
error_summary: renderError || '<unknown error>',
|
|
92
|
+
exit_code: renderExitCode,
|
|
93
|
+
})
|
|
94
|
+
} else {
|
|
95
|
+
entries.push({
|
|
96
|
+
agent: 'wb-render:phase2',
|
|
97
|
+
status_sequence: ['running', 'done'],
|
|
98
|
+
final_status: 'done',
|
|
99
|
+
phase_delta_tokens: renderTokens,
|
|
100
|
+
error_summary: undefined,
|
|
101
|
+
exit_code: undefined,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return entries
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { simulateLedgerEntries }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ROOT = path.resolve(__dirname, '../../..'); // toolkit root
|
|
8
|
+
const SCRIPTS = path.join(ROOT, '.claude', 'scripts');
|
|
9
|
+
const WB_VALIDATE = path.join(SCRIPTS, 'wb-validate.js');
|
|
10
|
+
const WB_RENDER = path.join(SCRIPTS, 'wb-render.js');
|
|
11
|
+
const CLI_SRC = fs.readFileSync(path.join(ROOT, 'bin', 'cli.js'), 'utf8');
|
|
12
|
+
const INSTALL_TOOLKIT_SRC = fs.readFileSync(
|
|
13
|
+
path.join(ROOT, '.claude', 'agents', 'install-toolkit.md'), 'utf8'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Helper: extract the body of installLocal from CLI_SRC (up to installGlobal)
|
|
17
|
+
function extractInstallLocalBody() {
|
|
18
|
+
const localStart = CLI_SRC.indexOf('async function installLocal');
|
|
19
|
+
const globalStart = CLI_SRC.indexOf('async function installGlobal');
|
|
20
|
+
return CLI_SRC.slice(localStart, globalStart);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Helper: extract the body of installGlobal from CLI_SRC (up to function help)
|
|
24
|
+
function extractInstallGlobalBody() {
|
|
25
|
+
const globalStart = CLI_SRC.indexOf('async function installGlobal');
|
|
26
|
+
const helpStart = CLI_SRC.indexOf('\nfunction help(');
|
|
27
|
+
return helpStart === -1
|
|
28
|
+
? CLI_SRC.slice(globalStart)
|
|
29
|
+
: CLI_SRC.slice(globalStart, helpStart);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// Group 1: Source files exist (precondition)
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
describe('installer.scripts-distribution — Group 1: source files exist', () => {
|
|
37
|
+
test('wb-validate.js exists at .claude/scripts/wb-validate.js in the toolkit root', () => {
|
|
38
|
+
expect(fs.existsSync(WB_VALIDATE)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('wb-render.js exists at .claude/scripts/wb-render.js in the toolkit root', () => {
|
|
42
|
+
expect(fs.existsSync(WB_RENDER)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('wb-validate.js passes Node.js syntax check (node --check)', () => {
|
|
46
|
+
const result = spawnSync(process.execPath, ['--check', WB_VALIDATE], { encoding: 'utf8' });
|
|
47
|
+
expect(result.status).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('wb-render.js passes Node.js syntax check (node --check)', () => {
|
|
51
|
+
const result = spawnSync(process.execPath, ['--check', WB_RENDER], { encoding: 'utf8' });
|
|
52
|
+
expect(result.status).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
// Group 2: Local install includes scripts (static verification)
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe('installer.scripts-distribution — Group 2: local install includes scripts', () => {
|
|
61
|
+
test('installLocal in bin/cli.js maps the entire .claude directory (which includes .claude/scripts/)', () => {
|
|
62
|
+
// The local install maps { src: path.join(packageRoot, '.claude'), dest: ... }
|
|
63
|
+
// so the whole .claude/ tree — including .claude/scripts/ — is included automatically.
|
|
64
|
+
const localBody = extractInstallLocalBody();
|
|
65
|
+
expect(localBody).toContain("path.join(packageRoot, '.claude')");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('.claude/scripts/ directory contains wb-validate.js and wb-render.js', () => {
|
|
69
|
+
const files = fs.readdirSync(SCRIPTS);
|
|
70
|
+
expect(files).toContain('wb-validate.js');
|
|
71
|
+
expect(files).toContain('wb-render.js');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('wb-validate.js with no args exits with code 2 (usage error) and writes to stderr', () => {
|
|
75
|
+
// Arrange: no arguments — the script should emit a usage error
|
|
76
|
+
// Act
|
|
77
|
+
const result = spawnSync(process.execPath, [WB_VALIDATE], { encoding: 'utf8' });
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result.status).toBe(2);
|
|
80
|
+
expect(result.stderr.length).toBeGreaterThan(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('wb-render.js with no args exits with code 1 (usage error) and writes to stderr', () => {
|
|
84
|
+
// Arrange: no arguments — the script should emit a usage message
|
|
85
|
+
// Act
|
|
86
|
+
const result = spawnSync(process.execPath, [WB_RENDER], { encoding: 'utf8' });
|
|
87
|
+
// Assert
|
|
88
|
+
expect(result.status).toBe(1);
|
|
89
|
+
expect(result.stderr.length).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
// Group 3: Global install mapping includes scripts (static verification)
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
describe('installer.scripts-distribution — Group 3: global install mapping includes scripts', () => {
|
|
98
|
+
test('installGlobal in bin/cli.js contains a mapping entry for .claude/scripts', () => {
|
|
99
|
+
// The mapping line is: { src: path.join(packageRoot, '.claude', 'scripts'), ... }
|
|
100
|
+
const globalBody = extractInstallGlobalBody();
|
|
101
|
+
expect(globalBody).toContain("'scripts'");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('global install destination path for scripts is derived from target + scripts', () => {
|
|
105
|
+
// The mapping line is: { ..., dest: path.join(target, 'scripts') }
|
|
106
|
+
// where target = path.join(homedir, '.claude') → dest = ~/.claude/scripts
|
|
107
|
+
expect(CLI_SRC).toContain("path.join(target, 'scripts')");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
// Group 4: install-toolkit.md agent mentions scripts (static verification)
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
describe('installer.scripts-distribution — Group 4: install-toolkit.md mentions scripts', () => {
|
|
116
|
+
test('install-toolkit.md contains the string .claude/scripts/', () => {
|
|
117
|
+
expect(INSTALL_TOOLKIT_SRC).toContain('.claude/scripts/');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('install-toolkit.md contains wb-validate.js', () => {
|
|
121
|
+
expect(INSTALL_TOOLKIT_SRC).toContain('wb-validate.js');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('install-toolkit.md contains wb-render.js', () => {
|
|
125
|
+
expect(INSTALL_TOOLKIT_SRC).toContain('wb-render.js');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('install-toolkit.md mentions "Six source directories" (confirming the scripts table row was added)', () => {
|
|
129
|
+
expect(INSTALL_TOOLKIT_SRC).toContain('Six source directories');
|
|
130
|
+
});
|
|
131
|
+
});
|