@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.
Files changed (39) hide show
  1. package/.claude/agents/generate-work-breakdown.md +212 -192
  2. package/.claude/agents/install-toolkit.md +6 -1
  3. package/.claude/agents/validate-work-breakdown-semantic.md +96 -0
  4. package/.claude/scripts/.gitkeep +0 -0
  5. package/.claude/scripts/tests/fixtures/ac-table-format.md +48 -0
  6. package/.claude/scripts/tests/fixtures/test-feature/TEST-001-Work-Breakdown.json +234 -0
  7. package/.claude/scripts/tests/fixtures/test-feature/TEST-001-semantic-output.json +4 -0
  8. package/.claude/scripts/tests/fixtures/test-feature/feature.md +45 -0
  9. package/.claude/scripts/tests/fixtures/wb-invalid-bad-domain.json +39 -0
  10. package/.claude/scripts/tests/fixtures/wb-invalid-cycle.json +61 -0
  11. package/.claude/scripts/tests/fixtures/wb-invalid-duplicate-id.json +72 -0
  12. package/.claude/scripts/tests/fixtures/wb-invalid-missing-field.json +38 -0
  13. package/.claude/scripts/tests/fixtures/wb-valid.json +189 -0
  14. package/.claude/scripts/tests/helpers/buildWaves.js +21 -0
  15. package/.claude/scripts/tests/helpers/pm-phase2-flow.js +29 -0
  16. package/.claude/scripts/tests/helpers/pm-phase2-gate2.js +55 -0
  17. package/.claude/scripts/tests/helpers/pm-phase2-ledger.js +108 -0
  18. package/.claude/scripts/tests/installer.scripts-distribution.test.js +131 -0
  19. package/.claude/scripts/tests/pm-phase2.gate2-payload.test.js +353 -0
  20. package/.claude/scripts/tests/pm-phase2.ledger-transitions.test.js +346 -0
  21. package/.claude/scripts/tests/pm-phase2.orchestration-flow.test.js +276 -0
  22. package/.claude/scripts/tests/verify-us-01-t03.js +288 -0
  23. package/.claude/scripts/tests/verify-us-03-t04.js +262 -0
  24. package/.claude/scripts/tests/wb-render.checks-md-csv-deps.test.js +337 -0
  25. package/.claude/scripts/tests/wb-render.checks-sanitize-commit-paths.test.js +352 -0
  26. package/.claude/scripts/tests/wb-render.regression-pm-phase3-csv.test.js +219 -0
  27. package/.claude/scripts/tests/wb-validate.checks-1-7.test.js +330 -0
  28. package/.claude/scripts/tests/wb-validate.checks-11.test.js +166 -0
  29. package/.claude/scripts/tests/wb-validate.checks-12-14.test.js +222 -0
  30. package/.claude/scripts/tests/wb-validate.checks-15-18.test.js +359 -0
  31. package/.claude/scripts/tests/wb-validate.checks-19-21.test.js +362 -0
  32. package/.claude/scripts/tests/wb-validate.checks-22-23-report.test.js +297 -0
  33. package/.claude/scripts/tests/wb-validate.checks-8-10.test.js +243 -0
  34. package/.claude/scripts/wb-render.js +309 -0
  35. package/.claude/scripts/wb-validate.js +1007 -0
  36. package/.claude/workflows/pm-phase2.js +289 -28
  37. package/CHANGELOG.md +67 -0
  38. package/bin/cli.js +1 -0
  39. 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
+ });