@xulthekl/team-flow 0.31.0 → 0.32.2
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/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/marketplace.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +69 -0
- package/GEMINI.md +1 -1
- package/HANDOFF.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +2 -2
- package/agents/contract-builder.md +6 -1
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +2 -0
- package/docs/solutions/cross-phase/2026-08-01-no-summary.md +17 -0
- package/docs/solutions/cross-phase/2026-08-03-no-summary.md +17 -0
- package/gemini-extension.json +2 -2
- package/hooks/pre-tool-use-guard +43 -2
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +6 -2
- package/plugin.json +1 -1
- package/scripts/guard/checks/test-gate-exemptions.mjs +53 -0
- package/scripts/guard/checks/test-matrix-complete.mjs +54 -30
- package/scripts/guard/checks/test-matrix-ready.mjs +60 -0
- package/scripts/guard/checks/tests-passing.mjs +112 -24
- package/scripts/guard/guard.mjs +11 -2
- package/scripts/lib/cmd-doctor.mjs +43 -1
- package/scripts/lib/cmd-execution.mjs +24 -1
- package/scripts/lib/cmd-state.mjs +28 -5
- package/scripts/lib/cmd-validate.mjs +42 -0
- package/scripts/lib/cmd-version.mjs +26 -21
- package/scripts/lib/execution-plan.mjs +23 -0
- package/scripts/lib/state-loader.mjs +16 -2
- package/scripts/lib/test-record.mjs +212 -0
- package/scripts/team-flow.mjs +3 -0
- package/skills/contract-builder/SKILL.md +9 -10
- package/skills/release-archivist/SKILL.md +16 -9
- package/skills/session-handoff/SKILL.md +1 -0
- package/skills/workflow-start/SKILL.md +5 -3
- package/skills/workflow-start/references/routing-rules.md +10 -6
- package/tests/e2e.test.mjs +0 -266
- package/tests/lib/cmd-audit.test.mjs +0 -202
- package/tests/lib/cmd-checkpoint.test.mjs +0 -98
- package/tests/lib/cmd-config.test.mjs +0 -101
- package/tests/lib/cmd-doctor.test.mjs +0 -450
- package/tests/lib/cmd-execution.test.mjs +0 -647
- package/tests/lib/cmd-handoff.test.mjs +0 -137
- package/tests/lib/cmd-inject.test.mjs +0 -218
- package/tests/lib/cmd-install-workbuddy.test.mjs +0 -113
- package/tests/lib/cmd-install-zcode.test.mjs +0 -42
- package/tests/lib/cmd-isolate.test.mjs +0 -68
- package/tests/lib/cmd-list.test.mjs +0 -127
- package/tests/lib/cmd-runtime.test.mjs +0 -83
- package/tests/lib/cmd-state-missing.test.mjs +0 -65
- package/tests/lib/cmd-state.test.mjs +0 -365
- package/tests/lib/cmd-sync-paths.test.mjs +0 -84
- package/tests/lib/cmd-validate-paths.test.mjs +0 -90
- package/tests/lib/config-loader.test.mjs +0 -175
- package/tests/lib/ensure-branch.test.mjs +0 -110
- package/tests/lib/execution-control-plane.test.mjs +0 -168
- package/tests/lib/execution-plan.test.mjs +0 -474
- package/tests/lib/guard-compound-captured.test.mjs +0 -92
- package/tests/lib/guard-specs-merged.test.mjs +0 -110
- package/tests/lib/guard-tests-passing.test.mjs +0 -114
- package/tests/lib/guard-transitions.test.mjs +0 -262
- package/tests/lib/guard.test.mjs +0 -613
- package/tests/lib/hash.test.mjs +0 -138
- package/tests/lib/infer-workflow.test.mjs +0 -161
- package/tests/lib/install-git-hooks.test.mjs +0 -20
- package/tests/lib/marketplace-release-docs.test.mjs +0 -31
- package/tests/lib/minimality-discipline.test.mjs +0 -37
- package/tests/lib/model-profiles-docs.test.mjs +0 -33
- package/tests/lib/node20-compatibility.test.mjs +0 -73
- package/tests/lib/node20-test-entry.test.mjs +0 -17
- package/tests/lib/platform-runtime-distribution.test.mjs +0 -157
- package/tests/lib/raw-mode-smoke.test.mjs +0 -42
- package/tests/lib/sdd-overlay.test.mjs +0 -118
- package/tests/lib/solutions-capture.test.mjs +0 -108
- package/tests/lib/solutions-index-gen.test.mjs +0 -147
- package/tests/lib/solutions-inject.test.mjs +0 -115
- package/tests/lib/solutions-promote.test.mjs +0 -200
- package/tests/lib/spec-paths.test.mjs +0 -80
- package/tests/lib/state-loader.test.mjs +0 -251
- package/tests/lib/token-baseline.test.mjs +0 -72
- package/tests/lib/token-rules.test.mjs +0 -94
- package/tests/lib/verify-marketplace-release.test.mjs +0 -128
- package/tests/tsconfig.json +0 -12
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
// tests/lib/cmd-doctor.test.mjs
|
|
2
|
-
// Tests for scripts/lib/cmd-doctor.mjs check functions
|
|
3
|
-
import { describe, it, before, after } from 'node:test';
|
|
4
|
-
import assert from 'node:assert/strict';
|
|
5
|
-
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { tmpdir } from 'node:os';
|
|
8
|
-
|
|
9
|
-
let tempDir;
|
|
10
|
-
|
|
11
|
-
describe('cmd-doctor: checkVersionConsistency()', () => {
|
|
12
|
-
let checkVersionConsistency;
|
|
13
|
-
|
|
14
|
-
before(async () => {
|
|
15
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-ver-'));
|
|
16
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
17
|
-
const mod = await import(modulePath);
|
|
18
|
-
checkVersionConsistency = mod.checkVersionConsistency;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
after(() => {
|
|
22
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('passes when all manifests have same version', () => {
|
|
26
|
-
const version = '1.0.0';
|
|
27
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version }));
|
|
28
|
-
writeFileSync(join(tempDir, 'plugin.json'), JSON.stringify({ version }));
|
|
29
|
-
mkdirSync(join(tempDir, '.claude-plugin'), { recursive: true });
|
|
30
|
-
writeFileSync(join(tempDir, '.claude-plugin', 'plugin.json'), JSON.stringify({ version }));
|
|
31
|
-
|
|
32
|
-
const result = checkVersionConsistency(tempDir);
|
|
33
|
-
assert.equal(result.pass, true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('fails when manifests have different versions', () => {
|
|
37
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version: '1.0.0' }));
|
|
38
|
-
writeFileSync(join(tempDir, 'plugin.json'), JSON.stringify({ version: '0.9.0' }));
|
|
39
|
-
|
|
40
|
-
const result = checkVersionConsistency(tempDir);
|
|
41
|
-
assert.equal(result.pass, false);
|
|
42
|
-
assert.ok(result.message.includes('mismatch'));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('handles missing manifests gracefully', () => {
|
|
46
|
-
// Clean up any lingering files from previous tests
|
|
47
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
48
|
-
mkdirSync(tempDir, { recursive: true });
|
|
49
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version: '1.0.0' }));
|
|
50
|
-
// Don't create other manifests — should still pass (unique versions <= 1)
|
|
51
|
-
|
|
52
|
-
const result = checkVersionConsistency(tempDir);
|
|
53
|
-
assert.equal(result.pass, true, `Expected pass but got: ${result.message}`);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('fails when a marketplace metadata version differs from the plugin version', () => {
|
|
57
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
58
|
-
mkdirSync(join(tempDir, '.github', 'plugin'), { recursive: true });
|
|
59
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version: '1.0.0' }));
|
|
60
|
-
writeFileSync(join(tempDir, '.github', 'plugin', 'marketplace.json'), JSON.stringify({
|
|
61
|
-
metadata: { version: '0.9.0' },
|
|
62
|
-
plugins: [{ version: '1.0.0' }],
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
const result = checkVersionConsistency(tempDir);
|
|
66
|
-
assert.equal(result.pass, false);
|
|
67
|
-
assert.ok(result.message.includes('.github/plugin/marketplace.json'));
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('cmd-doctor: checkHooks()', () => {
|
|
72
|
-
let checkHooks;
|
|
73
|
-
|
|
74
|
-
before(async () => {
|
|
75
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-hooks-'));
|
|
76
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
77
|
-
const mod = await import(modulePath);
|
|
78
|
-
checkHooks = mod.checkHooks;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
after(() => {
|
|
82
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('fails when hooks.json does not exist', () => {
|
|
86
|
-
const result = checkHooks(tempDir);
|
|
87
|
-
assert.equal(result.pass, false);
|
|
88
|
-
assert.ok(result.message.includes('not found'));
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('passes when hooks.json has valid record format', () => {
|
|
92
|
-
mkdirSync(join(tempDir, 'hooks'), { recursive: true });
|
|
93
|
-
writeFileSync(join(tempDir, 'hooks', 'hooks.json'), JSON.stringify({
|
|
94
|
-
hooks: { SessionStart: [{ command: 'bash hooks/session-start' }] },
|
|
95
|
-
}));
|
|
96
|
-
|
|
97
|
-
const result = checkHooks(tempDir);
|
|
98
|
-
assert.equal(result.pass, true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('fails when hooks.json has array format (legacy)', () => {
|
|
102
|
-
writeFileSync(join(tempDir, 'hooks', 'hooks.json'), JSON.stringify({
|
|
103
|
-
hooks: [{ event: 'SessionStart', command: 'bash hooks/session-start' }],
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
const result = checkHooks(tempDir);
|
|
107
|
-
assert.equal(result.pass, false);
|
|
108
|
-
assert.ok(result.message.includes('invalid format'));
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('fails on invalid JSON', () => {
|
|
112
|
-
writeFileSync(join(tempDir, 'hooks', 'hooks.json'), 'not json {{{');
|
|
113
|
-
|
|
114
|
-
const result = checkHooks(tempDir);
|
|
115
|
-
assert.equal(result.pass, false);
|
|
116
|
-
assert.ok(result.message.includes('parse error'));
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('cmd-doctor: checkCodexManifest()', () => {
|
|
121
|
-
let checkCodexManifest;
|
|
122
|
-
|
|
123
|
-
before(async () => {
|
|
124
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-codex-'));
|
|
125
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
126
|
-
const mod = await import(modulePath);
|
|
127
|
-
checkCodexManifest = mod.checkCodexManifest;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
after(() => {
|
|
131
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('fails when Codex manifest is missing', () => {
|
|
135
|
-
const result = checkCodexManifest(tempDir);
|
|
136
|
-
assert.equal(result.pass, false);
|
|
137
|
-
assert.ok(result.message.includes('not found'));
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('fails when hooks is absent because Codex auto-discovers hooks/hooks.json', () => {
|
|
141
|
-
mkdirSync(join(tempDir, '.codex-plugin'), { recursive: true });
|
|
142
|
-
writeFileSync(join(tempDir, '.codex-plugin', 'plugin.json'), JSON.stringify({
|
|
143
|
-
interface: { category: 'Developer Tools' },
|
|
144
|
-
}));
|
|
145
|
-
|
|
146
|
-
const result = checkCodexManifest(tempDir);
|
|
147
|
-
assert.equal(result.pass, false);
|
|
148
|
-
assert.ok(result.message.includes('hooks to {}'));
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('fails when category is not Developer Tools', () => {
|
|
152
|
-
writeFileSync(join(tempDir, '.codex-plugin', 'plugin.json'), JSON.stringify({
|
|
153
|
-
hooks: {},
|
|
154
|
-
interface: { category: 'Productivity' },
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
const result = checkCodexManifest(tempDir);
|
|
158
|
-
assert.equal(result.pass, false);
|
|
159
|
-
assert.ok(result.message.includes('Developer Tools'));
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('passes when hooks are explicitly suppressed and category is Developer Tools', () => {
|
|
163
|
-
writeFileSync(join(tempDir, '.codex-plugin', 'plugin.json'), JSON.stringify({
|
|
164
|
-
hooks: {},
|
|
165
|
-
interface: { category: 'Developer Tools' },
|
|
166
|
-
}));
|
|
167
|
-
|
|
168
|
-
const result = checkCodexManifest(tempDir);
|
|
169
|
-
assert.equal(result.pass, true);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('cmd-doctor: checkSkills()', () => {
|
|
174
|
-
let checkSkills;
|
|
175
|
-
|
|
176
|
-
before(async () => {
|
|
177
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-skills-'));
|
|
178
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
179
|
-
const mod = await import(modulePath);
|
|
180
|
-
checkSkills = mod.checkSkills;
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
after(() => {
|
|
184
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('fails when skills/ directory does not exist', () => {
|
|
188
|
-
const result = checkSkills(tempDir);
|
|
189
|
-
assert.equal(result.pass, false);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('passes when all skill dirs have SKILL.md', () => {
|
|
193
|
-
const skillsDir = join(tempDir, 'skills');
|
|
194
|
-
mkdirSync(join(skillsDir, 'workflow-start'), { recursive: true });
|
|
195
|
-
mkdirSync(join(skillsDir, 'need-explorer'), { recursive: true });
|
|
196
|
-
writeFileSync(join(skillsDir, 'workflow-start', 'SKILL.md'), '# Skill');
|
|
197
|
-
writeFileSync(join(skillsDir, 'need-explorer', 'SKILL.md'), '# Skill');
|
|
198
|
-
|
|
199
|
-
const result = checkSkills(tempDir);
|
|
200
|
-
assert.equal(result.pass, true);
|
|
201
|
-
assert.ok(result.message.includes('2/2'));
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('fails when some skills missing SKILL.md', () => {
|
|
205
|
-
const skillsDir = join(tempDir, 'skills');
|
|
206
|
-
rmSync(skillsDir, { recursive: true, force: true });
|
|
207
|
-
mkdirSync(join(skillsDir, 'workflow-start'), { recursive: true });
|
|
208
|
-
mkdirSync(join(skillsDir, 'empty-skill'), { recursive: true });
|
|
209
|
-
writeFileSync(join(skillsDir, 'workflow-start', 'SKILL.md'), '# Skill');
|
|
210
|
-
// empty-skill has no SKILL.md
|
|
211
|
-
|
|
212
|
-
const result = checkSkills(tempDir);
|
|
213
|
-
assert.equal(result.pass, false);
|
|
214
|
-
assert.ok(result.message.includes('empty-skill'));
|
|
215
|
-
assert.ok(result.message.includes('1/2'));
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('cmd-doctor: checkDist()', () => {
|
|
220
|
-
let checkDist;
|
|
221
|
-
|
|
222
|
-
before(async () => {
|
|
223
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-dist-'));
|
|
224
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
225
|
-
const mod = await import(modulePath);
|
|
226
|
-
checkDist = mod.checkDist;
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
after(() => {
|
|
230
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('fails when dist/ directory does not exist', () => {
|
|
234
|
-
const result = checkDist(tempDir);
|
|
235
|
-
assert.equal(result.pass, false);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('fails when dist/index.js missing', () => {
|
|
239
|
-
mkdirSync(join(tempDir, 'dist'), { recursive: true });
|
|
240
|
-
const result = checkDist(tempDir);
|
|
241
|
-
assert.equal(result.pass, false);
|
|
242
|
-
assert.ok(result.message.includes('not found'));
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('passes when dist/index.js exists', () => {
|
|
246
|
-
writeFileSync(join(tempDir, 'dist', 'index.js'), '// compiled');
|
|
247
|
-
const result = checkDist(tempDir);
|
|
248
|
-
assert.equal(result.pass, true);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe('cmd-doctor: checkRootPluginAuthor()', () => {
|
|
253
|
-
let checkRootPluginAuthor;
|
|
254
|
-
|
|
255
|
-
before(async () => {
|
|
256
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-author-'));
|
|
257
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
258
|
-
const mod = await import(modulePath);
|
|
259
|
-
checkRootPluginAuthor = mod.checkRootPluginAuthor;
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
after(() => {
|
|
263
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('fails when plugin.json does not exist', () => {
|
|
267
|
-
const result = checkRootPluginAuthor(tempDir);
|
|
268
|
-
assert.equal(result.pass, false);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('fails when author is a string (Copilot strict validation)', () => {
|
|
272
|
-
writeFileSync(join(tempDir, 'plugin.json'), JSON.stringify({
|
|
273
|
-
author: 'MageByte',
|
|
274
|
-
}));
|
|
275
|
-
|
|
276
|
-
const result = checkRootPluginAuthor(tempDir);
|
|
277
|
-
assert.equal(result.pass, false);
|
|
278
|
-
assert.ok(result.message.includes('must be an object'));
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('passes when author is an object with name', () => {
|
|
282
|
-
writeFileSync(join(tempDir, 'plugin.json'), JSON.stringify({
|
|
283
|
-
author: { name: 'MageByte' },
|
|
284
|
-
}));
|
|
285
|
-
|
|
286
|
-
const result = checkRootPluginAuthor(tempDir);
|
|
287
|
-
assert.equal(result.pass, true);
|
|
288
|
-
assert.ok(result.message.includes('MageByte'));
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('fails when author object missing name', () => {
|
|
292
|
-
writeFileSync(join(tempDir, 'plugin.json'), JSON.stringify({
|
|
293
|
-
author: { email: 'test@example.com' },
|
|
294
|
-
}));
|
|
295
|
-
|
|
296
|
-
const result = checkRootPluginAuthor(tempDir);
|
|
297
|
-
assert.equal(result.pass, false);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
describe('cmd-doctor: checkNodeVersion()', () => {
|
|
302
|
-
let checkNodeVersion;
|
|
303
|
-
|
|
304
|
-
before(async () => {
|
|
305
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
306
|
-
const mod = await import(modulePath);
|
|
307
|
-
checkNodeVersion = mod.checkNodeVersion;
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('accepts Node 20 as the minimum supported runtime', () => {
|
|
311
|
-
const result = checkNodeVersion('v20.0.0');
|
|
312
|
-
assert.equal(result.pass, true);
|
|
313
|
-
assert.equal(result.message, 'v20.0.0');
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('rejects Node 19 with the Node 20 repair guidance', () => {
|
|
317
|
-
const result = checkNodeVersion('v19.9.0');
|
|
318
|
-
assert.equal(result.pass, false);
|
|
319
|
-
assert.match(result.message, /v19\.9\.0 \(requires >= 20\)/);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('checks the current Node.js version when no version is supplied', () => {
|
|
323
|
-
const result = checkNodeVersion();
|
|
324
|
-
assert.ok(result.message.includes(process.version));
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
describe('cmd-doctor: checkDocs()', () => {
|
|
329
|
-
let checkDocs;
|
|
330
|
-
|
|
331
|
-
before(async () => {
|
|
332
|
-
tempDir = mkdtempSync(join(tmpdir(), 'tf-doctor-docs-'));
|
|
333
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
334
|
-
const mod = await import(modulePath);
|
|
335
|
-
checkDocs = mod.checkDocs;
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
after(() => {
|
|
339
|
-
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('returns skipped when no package.json', () => {
|
|
343
|
-
const result = checkDocs(tempDir);
|
|
344
|
-
assert.equal(result.pass, true);
|
|
345
|
-
assert.ok(result.message.includes('skipped'));
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('warns when CHANGELOG missing current version entry', () => {
|
|
349
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
350
|
-
writeFileSync(join(tempDir, 'CHANGELOG.md'), '# Changelog\n\n## [1.0.0]\n- old stuff');
|
|
351
|
-
|
|
352
|
-
const result = checkDocs(tempDir);
|
|
353
|
-
assert.equal(result.pass, false);
|
|
354
|
-
assert.ok(result.message.includes('1.2.3'));
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('passes when CHANGELOG has current version', () => {
|
|
358
|
-
writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
359
|
-
writeFileSync(join(tempDir, 'CHANGELOG.md'), '# Changelog\n\n## [1.2.3]\n- new stuff');
|
|
360
|
-
|
|
361
|
-
const result = checkDocs(tempDir);
|
|
362
|
-
assert.equal(result.pass, true);
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
describe('cmd-doctor: checkRuntimeDistribution()', () => {
|
|
367
|
-
let checkRuntimeDistribution;
|
|
368
|
-
|
|
369
|
-
before(async () => {
|
|
370
|
-
const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs');
|
|
371
|
-
const mod = await import(modulePath);
|
|
372
|
-
checkRuntimeDistribution = mod.checkRuntimeDistribution;
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('passes for the canonical package and reports platform coverage', () => {
|
|
376
|
-
const result = checkRuntimeDistribution(process.cwd());
|
|
377
|
-
|
|
378
|
-
assert.equal(result.pass, true, result.message);
|
|
379
|
-
assert.match(result.message, /17 platforms/);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it('reports an unrewritten plugin-root placeholder as an upgrade issue', () => {
|
|
383
|
-
const root = mkdtempSync(join(tmpdir(), 'tf-doctor-runtime-'));
|
|
384
|
-
try {
|
|
385
|
-
mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true });
|
|
386
|
-
writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
387
|
-
writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'),
|
|
388
|
-
'node "${CLAUDE_PLUGIN_ROOT}/scripts/team-flow.mjs" state get demo');
|
|
389
|
-
|
|
390
|
-
const result = checkRuntimeDistribution(root);
|
|
391
|
-
assert.equal(result.pass, false);
|
|
392
|
-
assert.match(result.message, /plugin-root placeholder|reinstall/i);
|
|
393
|
-
} finally {
|
|
394
|
-
rmSync(root, { recursive: true, force: true });
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it('checks code-reviewer as a portable runtime skill', () => {
|
|
399
|
-
const root = mkdtempSync(join(tmpdir(), 'tf-doctor-code-reviewer-'));
|
|
400
|
-
try {
|
|
401
|
-
mkdirSync(join(root, 'skills', 'code-reviewer'), { recursive: true });
|
|
402
|
-
writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
403
|
-
writeFileSync(join(root, 'skills', 'code-reviewer', 'SKILL.md'),
|
|
404
|
-
'node "${CLAUDE_PLUGIN_ROOT}/scripts/team-flow.mjs" execution review demo');
|
|
405
|
-
|
|
406
|
-
const result = checkRuntimeDistribution(root);
|
|
407
|
-
assert.equal(result.pass, false);
|
|
408
|
-
assert.match(result.message, /code-reviewer: plugin-root placeholder remains/);
|
|
409
|
-
} finally {
|
|
410
|
-
rmSync(root, { recursive: true, force: true });
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it('rejects a local command whose referenced runtime file is missing', () => {
|
|
415
|
-
const root = mkdtempSync(join(tmpdir(), 'tf-doctor-stale-runtime-'));
|
|
416
|
-
try {
|
|
417
|
-
mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true });
|
|
418
|
-
mkdirSync(join(root, 'scripts'), { recursive: true });
|
|
419
|
-
writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
420
|
-
writeFileSync(join(root, 'scripts', 'team-flow.mjs'), '// unrelated bundled CLI');
|
|
421
|
-
writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'),
|
|
422
|
-
'node "/tmp/missing-runtime/scripts/team-flow.mjs" state get demo');
|
|
423
|
-
|
|
424
|
-
const result = checkRuntimeDistribution(root);
|
|
425
|
-
assert.equal(result.pass, false);
|
|
426
|
-
assert.match(result.message, /workflow-start: local runtime tree is missing/);
|
|
427
|
-
} finally {
|
|
428
|
-
rmSync(root, { recursive: true, force: true });
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it('accepts a POSIX-escaped local runtime path containing a single quote', () => {
|
|
433
|
-
const root = mkdtempSync(join(tmpdir(), "tf-doctor-quote-'"));
|
|
434
|
-
try {
|
|
435
|
-
const localCli = join(root, 'scripts', 'team-flow.mjs');
|
|
436
|
-
mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true });
|
|
437
|
-
mkdirSync(join(root, 'scripts'), { recursive: true });
|
|
438
|
-
writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' }));
|
|
439
|
-
writeFileSync(localCli, '// bundled CLI');
|
|
440
|
-
const quotedCli = `'${localCli.replace(/'/g, "'\\''")}'`;
|
|
441
|
-
writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'),
|
|
442
|
-
`node ${quotedCli} state get demo`);
|
|
443
|
-
|
|
444
|
-
const result = checkRuntimeDistribution(root);
|
|
445
|
-
assert.equal(result.pass, true, result.message);
|
|
446
|
-
} finally {
|
|
447
|
-
rmSync(root, { recursive: true, force: true });
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
});
|