@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.
Files changed (90) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.cursor-plugin/marketplace.json +2 -2
  6. package/.cursor-plugin/plugin.json +2 -2
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +3 -3
  9. package/CHANGELOG.md +69 -0
  10. package/GEMINI.md +1 -1
  11. package/HANDOFF.md +1 -1
  12. package/INSTALL.md +1 -1
  13. package/README.md +2 -2
  14. package/agents/contract-builder.md +6 -1
  15. package/docs/README_en.md +1 -1
  16. package/docs/solutions/INDEX.md +2 -0
  17. package/docs/solutions/cross-phase/2026-08-01-no-summary.md +17 -0
  18. package/docs/solutions/cross-phase/2026-08-03-no-summary.md +17 -0
  19. package/gemini-extension.json +2 -2
  20. package/hooks/pre-tool-use-guard +43 -2
  21. package/hooks/session-start +2 -2
  22. package/llms.txt +1 -1
  23. package/package.json +6 -2
  24. package/plugin.json +1 -1
  25. package/scripts/guard/checks/test-gate-exemptions.mjs +53 -0
  26. package/scripts/guard/checks/test-matrix-complete.mjs +54 -30
  27. package/scripts/guard/checks/test-matrix-ready.mjs +60 -0
  28. package/scripts/guard/checks/tests-passing.mjs +112 -24
  29. package/scripts/guard/guard.mjs +11 -2
  30. package/scripts/lib/cmd-doctor.mjs +43 -1
  31. package/scripts/lib/cmd-execution.mjs +24 -1
  32. package/scripts/lib/cmd-state.mjs +28 -5
  33. package/scripts/lib/cmd-validate.mjs +42 -0
  34. package/scripts/lib/cmd-version.mjs +26 -21
  35. package/scripts/lib/execution-plan.mjs +23 -0
  36. package/scripts/lib/state-loader.mjs +16 -2
  37. package/scripts/lib/test-record.mjs +212 -0
  38. package/scripts/team-flow.mjs +3 -0
  39. package/skills/contract-builder/SKILL.md +9 -10
  40. package/skills/release-archivist/SKILL.md +16 -9
  41. package/skills/session-handoff/SKILL.md +1 -0
  42. package/skills/workflow-start/SKILL.md +5 -3
  43. package/skills/workflow-start/references/routing-rules.md +10 -6
  44. package/tests/e2e.test.mjs +0 -266
  45. package/tests/lib/cmd-audit.test.mjs +0 -202
  46. package/tests/lib/cmd-checkpoint.test.mjs +0 -98
  47. package/tests/lib/cmd-config.test.mjs +0 -101
  48. package/tests/lib/cmd-doctor.test.mjs +0 -450
  49. package/tests/lib/cmd-execution.test.mjs +0 -647
  50. package/tests/lib/cmd-handoff.test.mjs +0 -137
  51. package/tests/lib/cmd-inject.test.mjs +0 -218
  52. package/tests/lib/cmd-install-workbuddy.test.mjs +0 -113
  53. package/tests/lib/cmd-install-zcode.test.mjs +0 -42
  54. package/tests/lib/cmd-isolate.test.mjs +0 -68
  55. package/tests/lib/cmd-list.test.mjs +0 -127
  56. package/tests/lib/cmd-runtime.test.mjs +0 -83
  57. package/tests/lib/cmd-state-missing.test.mjs +0 -65
  58. package/tests/lib/cmd-state.test.mjs +0 -365
  59. package/tests/lib/cmd-sync-paths.test.mjs +0 -84
  60. package/tests/lib/cmd-validate-paths.test.mjs +0 -90
  61. package/tests/lib/config-loader.test.mjs +0 -175
  62. package/tests/lib/ensure-branch.test.mjs +0 -110
  63. package/tests/lib/execution-control-plane.test.mjs +0 -168
  64. package/tests/lib/execution-plan.test.mjs +0 -474
  65. package/tests/lib/guard-compound-captured.test.mjs +0 -92
  66. package/tests/lib/guard-specs-merged.test.mjs +0 -110
  67. package/tests/lib/guard-tests-passing.test.mjs +0 -114
  68. package/tests/lib/guard-transitions.test.mjs +0 -262
  69. package/tests/lib/guard.test.mjs +0 -613
  70. package/tests/lib/hash.test.mjs +0 -138
  71. package/tests/lib/infer-workflow.test.mjs +0 -161
  72. package/tests/lib/install-git-hooks.test.mjs +0 -20
  73. package/tests/lib/marketplace-release-docs.test.mjs +0 -31
  74. package/tests/lib/minimality-discipline.test.mjs +0 -37
  75. package/tests/lib/model-profiles-docs.test.mjs +0 -33
  76. package/tests/lib/node20-compatibility.test.mjs +0 -73
  77. package/tests/lib/node20-test-entry.test.mjs +0 -17
  78. package/tests/lib/platform-runtime-distribution.test.mjs +0 -157
  79. package/tests/lib/raw-mode-smoke.test.mjs +0 -42
  80. package/tests/lib/sdd-overlay.test.mjs +0 -118
  81. package/tests/lib/solutions-capture.test.mjs +0 -108
  82. package/tests/lib/solutions-index-gen.test.mjs +0 -147
  83. package/tests/lib/solutions-inject.test.mjs +0 -115
  84. package/tests/lib/solutions-promote.test.mjs +0 -200
  85. package/tests/lib/spec-paths.test.mjs +0 -80
  86. package/tests/lib/state-loader.test.mjs +0 -251
  87. package/tests/lib/token-baseline.test.mjs +0 -72
  88. package/tests/lib/token-rules.test.mjs +0 -94
  89. package/tests/lib/verify-marketplace-release.test.mjs +0 -128
  90. package/tests/tsconfig.json +0 -12
@@ -1,114 +0,0 @@
1
- // tests/lib/guard-tests-passing.test.mjs
2
- // Regression for BUG-A: the `executing → closing` transition was unreachable
3
- // because `test_result` was never written, yet the tests-passing guard required it.
4
- // Now the guard also accepts `dp_6_result` starting with "pass".
5
-
6
- import { describe, it, before, after } from 'node:test';
7
- import assert from 'node:assert/strict';
8
- import { execFileSync } from 'node:child_process';
9
- import { mkdtempSync, writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs';
10
- import { join } from 'node:path';
11
- import { tmpdir } from 'node:os';
12
- import { fileURLToPath } from 'node:url';
13
- import { dirname } from 'node:path';
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const ROOT = join(__dirname, '..', '..');
17
- const GUARD = join(ROOT, 'scripts', 'guard', 'guard.mjs');
18
- const CLI = join(ROOT, 'scripts', 'team-flow.mjs');
19
-
20
- function makeChangeDir() {
21
- const dir = mkdtempSync(join(tmpdir(), 'tf-tests-passing-'));
22
- mkdirSync(join(dir, 'specs'), { recursive: true });
23
- writeFileSync(join(dir, 'proposal.md'), '# Test\n\n## Why\nTest.\n\n## What Changes\n- Test.\n');
24
- writeFileSync(join(dir, 'design.md'), '# Design\n\n## Context\nTest.\n\n## Goals\nTest.\n\n## Decisions\n\n### Decision 1\n- Choice: Test\n- Rationale: Test\n\n## Risks And Trade-Offs\nNone.\n');
25
- writeFileSync(join(dir, 'tasks.md'), '# Tasks\n\n- [x] Task 1\n- [x] Task 2\n');
26
- writeFileSync(join(dir, 'specs', 'test.md'), '## ADDED Requirements\n\n### Requirement: Test\n\nThe system SHALL test.\n\n#### Scenario: Test\n- **WHEN** test\n- **THEN** test\n');
27
- writeFileSync(join(dir, 'execution-contract.md'), '# Execution Contract\n\n## Intent Lock\nTest.\n');
28
- // v0.24.0: compound-captured dimension requires learnings.md
29
- writeFileSync(join(dir, 'learnings.md'), '# Session Learnings\n\n## Lesson 1\nTest.\n');
30
- initializeGitRepository(dir);
31
- return dir;
32
- }
33
-
34
- function runGit(directory, args) {
35
- return execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim();
36
- }
37
-
38
- function initializeGitRepository(directory) {
39
- runGit(directory, ['init', '--quiet']);
40
- runGit(directory, ['config', 'user.email', 'tests@example.invalid']);
41
- runGit(directory, ['config', 'user.name', 'Guard Tests Passing Test']);
42
- runGit(directory, ['add', '--all']);
43
- runGit(directory, ['commit', '--quiet', '--message', 'initial closing guard change']);
44
- const base = runGit(directory, ['rev-parse', 'HEAD']);
45
- writeFileSync(join(directory, 'git-range-marker.txt'), 'second commit\n');
46
- runGit(directory, ['add', 'git-range-marker.txt']);
47
- runGit(directory, ['commit', '--quiet', '--message', 'second closing guard change']);
48
- return { base, head: runGit(directory, ['rev-parse', 'HEAD']) };
49
- }
50
-
51
- function cleanup(dir) {
52
- if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
53
- }
54
-
55
- // Writes a state file in `executing` and runs the guard for executing -> closing.
56
- // The guard emits the structured result to stdout (--json mode), so capture both
57
- // stdout and stderr for assertions.
58
- function runClosingGuard(dir, extraState = '') {
59
- try {
60
- // `spec_merged: true` is in the base state so the #28 specs-merged gate
61
- // passes — these tests isolate the tests-passing dimension only.
62
- // (The specs-merged dimension is covered separately in
63
- // tests/lib/guard-specs-merged.test.mjs.)
64
- writeFileSync(
65
- join(dir, '.team-flow.yaml'),
66
- `state: executing\nworkflow: full\nchange_name: test\nspec_merged: true\n${extraState}`,
67
- );
68
- rmSync(join(dir, '.superpowers'), { recursive: true, force: true });
69
- execFileSync('node', [CLI, 'execution', 'recommend', dir,
70
- '--wave', 'close:serial:1.1'], { stdio: 'pipe', timeout: 5000 });
71
- execFileSync('node', [CLI, 'execution', 'plan', dir, '--mode', 'sdd',
72
- '--confirm', '--acknowledge-recommendation', '--reason', 'closing guard regression fixture',
73
- '--wave', 'close:serial:1.1'], { stdio: 'pipe', timeout: 5000 });
74
- const report = join(dir, '.superpowers', 'sdd', 'reviews', 'close-review.md');
75
- mkdirSync(join(dir, '.superpowers', 'sdd', 'reviews'), { recursive: true });
76
- writeFileSync(report, 'review passed\n');
77
- const base = runGit(dir, ['rev-parse', 'HEAD~1']);
78
- const head = runGit(dir, ['rev-parse', 'HEAD']);
79
- execFileSync('node', [CLI, 'execution', 'review', dir, '--wave', 'close',
80
- '--base', base, '--head', head, '--report', report, '--verdict', 'pass'], { stdio: 'pipe', timeout: 5000 });
81
- execFileSync('node', [GUARD, 'check', dir, 'executing', 'closing', '--json'], { stdio: 'pipe', timeout: 5000 });
82
- return { ok: true, stderr: '' };
83
- } catch (e) {
84
- const out = `${e.stdout?.toString() || ''}\n${e.stderr?.toString() || ''}`;
85
- return { ok: false, stderr: out || e.message };
86
- }
87
- }
88
-
89
- describe('BUG-A: executing -> closing reachable once verification recorded', () => {
90
- let dir;
91
- before(() => { dir = makeChangeDir(); });
92
- after(() => { cleanup(dir); });
93
-
94
- it('SHALL fail when no verification outcome recorded', () => {
95
- const r = runClosingGuard(dir);
96
- assert.equal(r.ok, false, 'closing must be blocked without a verification result');
97
- assert.match(r.stderr, /verification outcome missing/i);
98
- });
99
-
100
- it('SHALL pass when dp_6_result starts with "pass" (the release-archivist signal)', () => {
101
- const r = runClosingGuard(dir, 'dp_6_result: pass: all green\n');
102
- assert.equal(r.ok, true, `closing should be allowed via dp_6_result, got: ${r.stderr || ''}`);
103
- });
104
-
105
- it('SHALL pass when test_result is explicitly "pass"', () => {
106
- const r = runClosingGuard(dir, 'test_result: pass\n');
107
- assert.equal(r.ok, true, `closing should be allowed via test_result, got: ${r.stderr || ''}`);
108
- });
109
-
110
- it('SHALL fail when dp_6_result is "fail" (not a pass signal)', () => {
111
- const r = runClosingGuard(dir, 'dp_6_result: fail: broken\n');
112
- assert.equal(r.ok, false, 'closing must be blocked when verification failed');
113
- });
114
- });
@@ -1,262 +0,0 @@
1
- // tests/lib/guard-transitions.test.mjs
2
- // Regression tests for state machine transition guard matrix
3
- // Covers specs/guard-transitions/spec.md requirements
4
-
5
- import { describe, it, before, after } from 'node:test';
6
- import assert from 'node:assert/strict';
7
- import { execSync } from 'node:child_process';
8
- import { mkdtempSync, writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { tmpdir } from 'node:os';
11
- import { fileURLToPath } from 'node:url';
12
- import { dirname } from 'node:path';
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const ROOT = join(__dirname, '..', '..');
16
- const GUARD = join(ROOT, 'scripts', 'guard', 'guard.mjs');
17
-
18
- // All legal transitions (from docs/state-machine.md)
19
- const ALL_TRANSITIONS = [
20
- // Mainline
21
- { from: 'exploring', to: 'specifying' },
22
- { from: 'specifying', to: 'bridging' },
23
- { from: 'bridging', to: 'approved-for-build' },
24
- { from: 'approved-for-build', to: 'executing' },
25
- { from: 'executing', to: 'closing' },
26
- // Fast-path
27
- { from: 'exploring', to: 'bridging', workflow: 'hotfix' },
28
- { from: 'exploring', to: 'approved-for-build', workflow: 'tweak' },
29
- // Rewind
30
- { from: 'executing', to: 'specifying' },
31
- { from: 'executing', to: 'bridging' },
32
- { from: 'approved-for-build', to: 'specifying' },
33
- { from: 'approved-for-build', to: 'bridging' },
34
- { from: 'specifying', to: 'exploring' },
35
- { from: 'bridging', to: 'specifying' },
36
- // Debugging round-trip
37
- { from: 'executing', to: 'debugging' },
38
- { from: 'debugging', to: 'executing' },
39
- // Abandon
40
- { from: 'exploring', to: 'abandoned' },
41
- { from: 'specifying', to: 'abandoned' },
42
- { from: 'bridging', to: 'abandoned' },
43
- { from: 'approved-for-build', to: 'abandoned' },
44
- { from: 'executing', to: 'abandoned' },
45
- { from: 'debugging', to: 'abandoned' },
46
- ];
47
-
48
- // Illegal transitions (should be rejected)
49
- const ILLEGAL_TRANSITIONS = [
50
- { from: 'exploring', to: 'closing' },
51
- { from: 'exploring', to: 'executing' },
52
- { from: 'specifying', to: 'executing' },
53
- { from: 'specifying', to: 'closing' },
54
- { from: 'bridging', to: 'closing' },
55
- { from: 'closing', to: 'abandoned' },
56
- { from: 'abandoned', to: 'exploring' },
57
- { from: 'abandoned', to: 'specifying' },
58
- ];
59
-
60
- function makeChangeDir() {
61
- const dir = mkdtempSync(join(tmpdir(), 'tf-guard-test-'));
62
- mkdirSync(join(dir, 'specs', 'test'), { recursive: true });
63
- return dir;
64
- }
65
-
66
- function cleanup(dir) {
67
- if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
68
- }
69
-
70
- function runGuard(from, to, dir, workflow = 'full') {
71
- try {
72
- // Write a minimal .team-flow.yaml so guard can read state
73
- const stateFile = join(dir, '.team-flow.yaml');
74
- writeFileSync(stateFile, `state: ${from}\nworkflow: ${workflow}\nchange_name: test\n`);
75
-
76
- // Also need a minimal proposal.md for artifacts-exist check
77
- writeFileSync(join(dir, 'proposal.md'), '# Test\n\n## Why\n\nTest change for guard matrix audit.\n\n## What Changes\n\n- Test.\n');
78
- writeFileSync(join(dir, 'design.md'), '# Design\n\n## Context\n\nTest.\n\n## Goals\n\nTest.\n\n## Decisions\n\n### Decision 1\n- Choice: Test\n- Rationale: Test\n\n## Risks And Trade-Offs\n\nNone.\n');
79
- writeFileSync(join(dir, 'tasks.md'), '# Tasks\n\n- [x] Task 1\n- [x] Task 2\n');
80
- writeFileSync(join(dir, 'specs', 'test', 'spec.md'), '## ADDED Requirements\n\n### Requirement: Test\n\nThe system SHALL test.\n\n#### Scenario: Test\n- **WHEN** test\n- **THEN** test\n');
81
- writeFileSync(join(dir, 'execution-contract.md'), '# Execution Contract\n\n## Intent Lock\n\nTest.\n');
82
-
83
- const cmd = `node ${GUARD} check "${dir}" ${from} ${to}`;
84
- if (workflow !== 'full') {
85
- execSync(`${cmd} --workflow ${workflow} --json`, { stdio: 'pipe', timeout: 5000 });
86
- } else {
87
- execSync(`${cmd} --json`, { stdio: 'pipe', timeout: 5000 });
88
- }
89
- return { ok: true };
90
- } catch (e) {
91
- return { ok: false, stdout: e.stdout?.toString() || '', stderr: e.stderr?.toString() || e.message };
92
- }
93
- }
94
-
95
- function guardOutput(result) {
96
- return `${result.stdout || ''}\n${result.stderr || ''}`;
97
- }
98
-
99
- // ─── D1: Guard 检查矩阵完整性 ───
100
- // Requirement: Guard 检查矩阵完整性
101
- // Scenario: 主线 transition 全覆盖
102
-
103
- describe('Guard transition matrix completeness', () => {
104
- let dir;
105
- before(() => { dir = makeChangeDir(); });
106
- after(() => { cleanup(dir); });
107
-
108
- for (const t of ALL_TRANSITIONS) {
109
- const label = `${t.from} → ${t.to}${t.workflow ? ' (' + t.workflow + ')' : ''}`;
110
- it(`SHALL define checks for ${label}`, () => {
111
- const result = runGuard(t.from, t.to, dir, t.workflow || 'full');
112
- // The transition should be handled (either pass or fail with specific reason, but not "unknown transition")
113
- if (!result.ok) {
114
- const output = guardOutput(result);
115
- assert.ok(
116
- !output.includes('unknown transition') && !output.includes('Unknown transition'),
117
- `Transition ${label} is not defined in guard matrix: ${output}`
118
- );
119
- }
120
- });
121
- }
122
- });
123
-
124
- // Requirement: Guard 检查矩阵完整性
125
- // Scenario: 非法 transition 被拦截
126
- describe('Illegal transition rejection', () => {
127
- let dir;
128
- before(() => { dir = makeChangeDir(); });
129
- after(() => { cleanup(dir); });
130
-
131
- for (const t of ILLEGAL_TRANSITIONS) {
132
- it(`SHALL reject illegal transition ${t.from} → ${t.to}`, () => {
133
- const result = runGuard(t.from, t.to, dir);
134
- assert.equal(result.ok, false, `Illegal transition ${t.from} → ${t.to} should be rejected`);
135
- });
136
- }
137
- });
138
-
139
- // Requirement: Guard 脚本错误处理
140
- // Scenario: guard 脚本异常时报告给用户
141
- describe('Guard error handling', () => {
142
- it('SHALL return non-zero exit when called without arguments', () => {
143
- try {
144
- execSync(`node ${GUARD} check`, { stdio: 'pipe', timeout: 5000 });
145
- assert.fail('Should have thrown');
146
- } catch (e) {
147
- assert.ok(e.status !== 0, 'Guard should exit non-zero for missing arguments');
148
- }
149
- });
150
-
151
- it('SHALL output error for nonexistent change directory', () => {
152
- try {
153
- execSync(`node ${GUARD} check /nonexistent/path exploring specifying`, { stdio: 'pipe', timeout: 5000 });
154
- assert.fail('Should have thrown');
155
- } catch (e) {
156
- assert.ok(e.status !== 0, 'Guard should exit non-zero for nonexistent directory');
157
- }
158
- });
159
- });
160
-
161
- // ─── D1: Debugging 状态往返 ───
162
- // Requirement: Debugging 状态往返无副作用
163
- // Scenario: debugging 返回 executing 后进度保留
164
- describe('Debugging round-trip', () => {
165
- let dir;
166
- before(() => { dir = makeChangeDir(); });
167
- after(() => { cleanup(dir); });
168
-
169
- it('SHALL allow executing → debugging transition', () => {
170
- const result = runGuard('executing', 'debugging', dir);
171
- // The transition may fail due to missing execution artifacts, but should NOT be "unknown transition"
172
- if (!result.ok) {
173
- const output = guardOutput(result);
174
- assert.ok(!output.includes('unknown'), `Should not reject with unknown: ${output}`);
175
- }
176
- });
177
-
178
- it('SHALL allow debugging → executing transition', () => {
179
- const result = runGuard('debugging', 'executing', dir);
180
- if (!result.ok) {
181
- const output = guardOutput(result);
182
- assert.ok(!output.includes('unknown'), `Should not reject with unknown: ${output}`);
183
- }
184
- });
185
- });
186
-
187
- // ─── D1: Terminal state protection ───
188
- // Requirement: 废弃状态转换禁止
189
- // Scenario: abandoned 后拒绝所有 transition
190
- describe('Terminal state protection — abandoned', () => {
191
- let dir;
192
- before(() => { dir = makeChangeDir(); });
193
- after(() => { cleanup(dir); });
194
-
195
- const allTargets = ['exploring', 'specifying', 'bridging', 'approved-for-build', 'executing', 'debugging', 'closing'];
196
-
197
- for (const target of allTargets) {
198
- it(`SHALL reject abandoned → ${target}`, () => {
199
- const result = runGuard('abandoned', target, dir);
200
- assert.equal(result.ok, false, `Abandoned → ${target} must be rejected (terminal state)`);
201
- });
202
- }
203
- });
204
-
205
- // Scenario: 从 closing 不能进入 abandoned
206
- describe('Terminal state protection — closing', () => {
207
- let dir;
208
- before(() => { dir = makeChangeDir(); });
209
- after(() => { cleanup(dir); });
210
-
211
- it('SHALL reject closing → abandoned', () => {
212
- const result = runGuard('closing', 'abandoned', dir);
213
- assert.equal(result.ok, false, 'Closing → abandoned must be rejected (both terminal)');
214
- });
215
- });
216
-
217
- // ─── D1: Fast-path validation ───
218
- // Requirement: Guard 检查矩阵完整性
219
- // Scenario: fast-path 有降级保护
220
- describe('Fast-path validation', () => {
221
- let dir;
222
- before(() => { dir = makeChangeDir(); });
223
- after(() => { cleanup(dir); });
224
-
225
- it('SHALL support hotfix fast-path (exploring → bridging)', () => {
226
- const result = runGuard('exploring', 'bridging', dir, 'hotfix');
227
- // May fail on schema-valid or other checks, but should NOT be "unknown transition"
228
- if (!result.ok) {
229
- const output = guardOutput(result);
230
- assert.ok(!output.includes('unknown'), `Hotfix fast-path should be defined: ${output}`);
231
- }
232
- });
233
-
234
- it('SHALL support tweak fast-path (exploring → approved-for-build)', () => {
235
- const result = runGuard('exploring', 'approved-for-build', dir, 'tweak');
236
- if (!result.ok) {
237
- const output = guardOutput(result);
238
- assert.ok(!output.includes('unknown'), `Tweak fast-path should be defined: ${output}`);
239
- }
240
- });
241
-
242
- it('SHALL reject hotfix fast-path when workflow is full', () => {
243
- const result = runGuard('exploring', 'bridging', dir, 'full');
244
- const output = guardOutput(result);
245
- assert.equal(result.ok, false, 'exploring -> bridging must be rejected in full workflow');
246
- assert.match(output, /workflow-mode|fast-path|hotfix|tweak/i);
247
- });
248
-
249
- it('SHALL reject tweak route to bridging', () => {
250
- const result = runGuard('exploring', 'bridging', dir, 'tweak');
251
- const output = guardOutput(result);
252
- assert.equal(result.ok, false, 'tweak should use exploring -> approved-for-build, not bridging');
253
- assert.match(output, /workflow-mode|hotfix/i);
254
- });
255
-
256
- it('SHALL reject tweak fast-path when workflow is full', () => {
257
- const result = runGuard('exploring', 'approved-for-build', dir, 'full');
258
- const output = guardOutput(result);
259
- assert.equal(result.ok, false, 'exploring -> approved-for-build must be rejected in full workflow');
260
- assert.match(output, /workflow-mode|fast-path|tweak/i);
261
- });
262
- });