gswd 1.0.0 → 1.1.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/bin/gswd-tools.cjs +228 -0
- package/bin/install.js +8 -0
- package/commands/gswd/imagine.md +7 -1
- package/commands/gswd/start.md +507 -32
- package/dist/lib/audit.d.ts +205 -0
- package/dist/lib/audit.js +805 -0
- package/dist/lib/bootstrap.d.ts +103 -0
- package/dist/lib/bootstrap.js +563 -0
- package/dist/lib/compile.d.ts +239 -0
- package/dist/lib/compile.js +1152 -0
- package/dist/lib/config.d.ts +49 -0
- package/dist/lib/config.js +150 -0
- package/dist/lib/imagine-agents.d.ts +54 -0
- package/dist/lib/imagine-agents.js +185 -0
- package/dist/lib/imagine-gate.d.ts +47 -0
- package/dist/lib/imagine-gate.js +131 -0
- package/dist/lib/imagine-input.d.ts +46 -0
- package/dist/lib/imagine-input.js +233 -0
- package/dist/lib/imagine-synthesis.d.ts +90 -0
- package/dist/lib/imagine-synthesis.js +453 -0
- package/dist/lib/imagine.d.ts +56 -0
- package/dist/lib/imagine.js +413 -0
- package/dist/lib/intake.d.ts +27 -0
- package/dist/lib/intake.js +82 -0
- package/dist/lib/parse.d.ts +59 -0
- package/dist/lib/parse.js +171 -0
- package/dist/lib/render.d.ts +309 -0
- package/dist/lib/render.js +624 -0
- package/dist/lib/specify-agents.d.ts +120 -0
- package/dist/lib/specify-agents.js +269 -0
- package/dist/lib/specify-journeys.d.ts +124 -0
- package/dist/lib/specify-journeys.js +279 -0
- package/dist/lib/specify-nfr.d.ts +45 -0
- package/dist/lib/specify-nfr.js +159 -0
- package/dist/lib/specify-roles.d.ts +46 -0
- package/dist/lib/specify-roles.js +88 -0
- package/dist/lib/specify.d.ts +70 -0
- package/dist/lib/specify.js +676 -0
- package/dist/lib/state.d.ts +140 -0
- package/dist/lib/state.js +340 -0
- package/dist/tests/audit.test.d.ts +4 -0
- package/dist/tests/audit.test.js +1579 -0
- package/dist/tests/bootstrap.test.d.ts +5 -0
- package/dist/tests/bootstrap.test.js +611 -0
- package/dist/tests/compile.test.d.ts +4 -0
- package/dist/tests/compile.test.js +862 -0
- package/dist/tests/config.test.d.ts +4 -0
- package/dist/tests/config.test.js +191 -0
- package/dist/tests/imagine-agents.test.d.ts +6 -0
- package/dist/tests/imagine-agents.test.js +179 -0
- package/dist/tests/imagine-gate.test.d.ts +6 -0
- package/dist/tests/imagine-gate.test.js +264 -0
- package/dist/tests/imagine-input.test.d.ts +6 -0
- package/dist/tests/imagine-input.test.js +283 -0
- package/dist/tests/imagine-synthesis.test.d.ts +7 -0
- package/dist/tests/imagine-synthesis.test.js +380 -0
- package/dist/tests/imagine.test.d.ts +8 -0
- package/dist/tests/imagine.test.js +406 -0
- package/dist/tests/parse.test.d.ts +4 -0
- package/dist/tests/parse.test.js +285 -0
- package/dist/tests/render.test.d.ts +4 -0
- package/dist/tests/render.test.js +236 -0
- package/dist/tests/specify-agents.test.d.ts +4 -0
- package/dist/tests/specify-agents.test.js +352 -0
- package/dist/tests/specify-journeys.test.d.ts +5 -0
- package/dist/tests/specify-journeys.test.js +440 -0
- package/dist/tests/specify-nfr.test.d.ts +4 -0
- package/dist/tests/specify-nfr.test.js +205 -0
- package/dist/tests/specify-roles.test.d.ts +4 -0
- package/dist/tests/specify-roles.test.js +136 -0
- package/dist/tests/specify.test.d.ts +9 -0
- package/dist/tests/specify.test.js +544 -0
- package/dist/tests/state.test.d.ts +4 -0
- package/dist/tests/state.test.js +316 -0
- package/lib/bootstrap.ts +37 -11
- package/lib/compile.ts +426 -4
- package/lib/imagine-agents.ts +53 -7
- package/lib/imagine-synthesis.ts +170 -6
- package/lib/imagine.ts +59 -5
- package/lib/intake.ts +60 -0
- package/lib/parse.ts +2 -1
- package/lib/render.ts +566 -5
- package/lib/specify-agents.ts +25 -3
- package/lib/state.ts +115 -0
- package/package.json +4 -2
- package/templates/gswd/DECISIONS.template.md +3 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Config module tests — merge under gswd key, preserve existing keys, idempotent
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const assert = __importStar(require("node:assert"));
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const os = __importStar(require("node:os"));
|
|
44
|
+
const config_js_1 = require("../lib/config.js");
|
|
45
|
+
let tmpDir;
|
|
46
|
+
(0, node_test_1.beforeEach)(() => {
|
|
47
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gswd-config-test-'));
|
|
48
|
+
});
|
|
49
|
+
(0, node_test_1.afterEach)(() => {
|
|
50
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
// ─── readConfig ──────────────────────────────────────────────────────────────
|
|
53
|
+
(0, node_test_1.describe)('readConfig', () => {
|
|
54
|
+
(0, node_test_1.it)('returns empty object for missing file', () => {
|
|
55
|
+
const result = (0, config_js_1.readConfig)(path.join(tmpDir, 'nonexistent.json'));
|
|
56
|
+
assert.deepStrictEqual(result, {});
|
|
57
|
+
});
|
|
58
|
+
(0, node_test_1.it)('returns parsed JSON for valid file', () => {
|
|
59
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
60
|
+
fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
|
|
61
|
+
const result = (0, config_js_1.readConfig)(configPath);
|
|
62
|
+
assert.strictEqual(result.model_profile, 'balanced');
|
|
63
|
+
});
|
|
64
|
+
(0, node_test_1.it)('returns empty object for invalid JSON', () => {
|
|
65
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
66
|
+
fs.writeFileSync(configPath, 'not json');
|
|
67
|
+
assert.deepStrictEqual((0, config_js_1.readConfig)(configPath), {});
|
|
68
|
+
});
|
|
69
|
+
(0, node_test_1.it)('returns empty object for JSON array', () => {
|
|
70
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
71
|
+
fs.writeFileSync(configPath, '[1,2,3]');
|
|
72
|
+
assert.deepStrictEqual((0, config_js_1.readConfig)(configPath), {});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// ─── mergeGswdConfig ─────────────────────────────────────────────────────────
|
|
76
|
+
(0, node_test_1.describe)('mergeGswdConfig', () => {
|
|
77
|
+
(0, node_test_1.it)('creates gswd key with defaults when config has no gswd key', () => {
|
|
78
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
79
|
+
fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
|
|
80
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
81
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
82
|
+
assert.ok(config.gswd);
|
|
83
|
+
assert.strictEqual(config.gswd.mode, 'balanced');
|
|
84
|
+
assert.strictEqual(config.gswd.strict_gates, true);
|
|
85
|
+
assert.strictEqual(config.gswd.max_parallel_agents, 4);
|
|
86
|
+
});
|
|
87
|
+
(0, node_test_1.it)('preserves existing non-gswd keys (model_profile, commit_docs, etc.)', () => {
|
|
88
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
89
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
90
|
+
model_profile: 'balanced',
|
|
91
|
+
commit_docs: true,
|
|
92
|
+
research: true,
|
|
93
|
+
}));
|
|
94
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
95
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
96
|
+
assert.strictEqual(config.model_profile, 'balanced');
|
|
97
|
+
assert.strictEqual(config.commit_docs, true);
|
|
98
|
+
assert.strictEqual(config.research, true);
|
|
99
|
+
assert.ok(config.gswd); // gswd also exists
|
|
100
|
+
});
|
|
101
|
+
(0, node_test_1.it)('deep merges: user gswd values override defaults', () => {
|
|
102
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
103
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
104
|
+
gswd: {
|
|
105
|
+
mode: 'strict',
|
|
106
|
+
max_parallel_agents: 2,
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
110
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
111
|
+
assert.strictEqual(config.gswd.mode, 'strict'); // user value preserved
|
|
112
|
+
assert.strictEqual(config.gswd.max_parallel_agents, 2); // user value preserved
|
|
113
|
+
assert.strictEqual(config.gswd.strict_gates, true); // default filled in
|
|
114
|
+
});
|
|
115
|
+
(0, node_test_1.it)('fills missing defaults in partial gswd config', () => {
|
|
116
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
117
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
118
|
+
gswd: { mode: 'strict' }
|
|
119
|
+
}));
|
|
120
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
121
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
122
|
+
assert.strictEqual(config.gswd.mode, 'strict');
|
|
123
|
+
assert.strictEqual(config.gswd.phase_style, 'thin'); // default
|
|
124
|
+
assert.strictEqual(config.gswd.external_research, true); // default
|
|
125
|
+
});
|
|
126
|
+
(0, node_test_1.it)('idempotent: merging twice produces identical output', () => {
|
|
127
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
128
|
+
fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
|
|
129
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
130
|
+
const first = fs.readFileSync(configPath, 'utf-8');
|
|
131
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
132
|
+
const second = fs.readFileSync(configPath, 'utf-8');
|
|
133
|
+
assert.strictEqual(first, second);
|
|
134
|
+
});
|
|
135
|
+
(0, node_test_1.it)('handles nested auto config merge correctly', () => {
|
|
136
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
137
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
138
|
+
gswd: {
|
|
139
|
+
auto: {
|
|
140
|
+
policy: 'aggressive',
|
|
141
|
+
default_stack: 'python_django',
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
146
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
147
|
+
assert.strictEqual(config.gswd.auto.policy, 'aggressive'); // user
|
|
148
|
+
assert.strictEqual(config.gswd.auto.default_stack, 'python_django'); // user
|
|
149
|
+
assert.strictEqual(config.gswd.auto.default_auth_model, 'passwordless_email'); // default
|
|
150
|
+
assert.deepStrictEqual(config.gswd.auto.preapproved_integrations, []); // default
|
|
151
|
+
});
|
|
152
|
+
(0, node_test_1.it)('creates config.json from scratch when file does not exist', () => {
|
|
153
|
+
const configPath = path.join(tmpDir, 'new-config.json');
|
|
154
|
+
(0, config_js_1.mergeGswdConfig)(configPath);
|
|
155
|
+
assert.ok(fs.existsSync(configPath));
|
|
156
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
157
|
+
assert.ok(config.gswd);
|
|
158
|
+
assert.strictEqual(config.gswd.mode, 'balanced');
|
|
159
|
+
});
|
|
160
|
+
(0, node_test_1.it)('applies overrides on top of defaults', () => {
|
|
161
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
162
|
+
fs.writeFileSync(configPath, '{}');
|
|
163
|
+
(0, config_js_1.mergeGswdConfig)(configPath, { mode: 'strict', max_parallel_agents: 8 });
|
|
164
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
165
|
+
assert.strictEqual(config.gswd.mode, 'strict');
|
|
166
|
+
assert.strictEqual(config.gswd.max_parallel_agents, 8);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
// ─── getGswdConfig ───────────────────────────────────────────────────────────
|
|
170
|
+
(0, node_test_1.describe)('getGswdConfig', () => {
|
|
171
|
+
(0, node_test_1.it)('returns defaults when no gswd key exists', () => {
|
|
172
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
173
|
+
fs.writeFileSync(configPath, JSON.stringify({ model_profile: 'balanced' }));
|
|
174
|
+
const gswdConfig = (0, config_js_1.getGswdConfig)(configPath);
|
|
175
|
+
assert.strictEqual(gswdConfig.mode, config_js_1.GSWD_CONFIG_DEFAULTS.mode);
|
|
176
|
+
assert.strictEqual(gswdConfig.strict_gates, config_js_1.GSWD_CONFIG_DEFAULTS.strict_gates);
|
|
177
|
+
});
|
|
178
|
+
(0, node_test_1.it)('returns merged config when partial gswd key exists', () => {
|
|
179
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
180
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
181
|
+
gswd: { mode: 'strict' }
|
|
182
|
+
}));
|
|
183
|
+
const gswdConfig = (0, config_js_1.getGswdConfig)(configPath);
|
|
184
|
+
assert.strictEqual(gswdConfig.mode, 'strict'); // from file
|
|
185
|
+
assert.strictEqual(gswdConfig.phase_style, 'thin'); // from defaults
|
|
186
|
+
});
|
|
187
|
+
(0, node_test_1.it)('returns defaults for missing config file', () => {
|
|
188
|
+
const gswdConfig = (0, config_js_1.getGswdConfig)(path.join(tmpDir, 'missing.json'));
|
|
189
|
+
assert.deepStrictEqual(gswdConfig, config_js_1.GSWD_CONFIG_DEFAULTS);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for GSWD Imagine Agents Module
|
|
4
|
+
*
|
|
5
|
+
* Covers: IMAGINE_AGENTS constant, buildAgentPrompt, orchestrateAgents (batching, failures)
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const node_test_1 = require("node:test");
|
|
42
|
+
const assert = __importStar(require("node:assert"));
|
|
43
|
+
const imagine_agents_js_1 = require("../lib/imagine-agents.js");
|
|
44
|
+
// ─── Test Fixtures ───────────────────────────────────────────────────────────
|
|
45
|
+
const TEST_BRIEF = {
|
|
46
|
+
vision: 'A CLI tool that turns ideas into execution-grade specs',
|
|
47
|
+
target_user: 'Solo founders building SaaS products',
|
|
48
|
+
why_now: 'LLMs can now understand product requirements deeply',
|
|
49
|
+
raw_themes: ['CLI', 'specifications', 'automation', 'SaaS'],
|
|
50
|
+
source: 'intake',
|
|
51
|
+
};
|
|
52
|
+
/** Mock spawn function that returns agent name as content */
|
|
53
|
+
function mockSpawnFn(prompt) {
|
|
54
|
+
// Extract agent name from prompt for identification
|
|
55
|
+
const nameMatch = prompt.match(/name:\s*(\S+)/);
|
|
56
|
+
const name = nameMatch ? nameMatch[1] : 'unknown';
|
|
57
|
+
return Promise.resolve(`## Output from ${name}\n\nContent here.`);
|
|
58
|
+
}
|
|
59
|
+
/** Mock spawn function that fails for specific agents */
|
|
60
|
+
function failingSpawnFn(failAgents) {
|
|
61
|
+
return (prompt) => {
|
|
62
|
+
for (const name of failAgents) {
|
|
63
|
+
if (prompt.includes(name)) {
|
|
64
|
+
return Promise.reject(new Error(`Agent ${name} failed`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve('## Success\n\nContent here.');
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
71
|
+
(0, node_test_1.describe)('IMAGINE_AGENTS constant', () => {
|
|
72
|
+
(0, node_test_1.it)('has exactly 5 agents', () => {
|
|
73
|
+
assert.strictEqual(imagine_agents_js_1.IMAGINE_AGENTS.length, 5);
|
|
74
|
+
});
|
|
75
|
+
(0, node_test_1.it)('all agent names match GSWD_SPEC file names', () => {
|
|
76
|
+
const expectedNames = [
|
|
77
|
+
'market-researcher',
|
|
78
|
+
'icp-persona',
|
|
79
|
+
'positioning',
|
|
80
|
+
'brainstorm-alternatives',
|
|
81
|
+
'devils-advocate',
|
|
82
|
+
];
|
|
83
|
+
const actualNames = imagine_agents_js_1.IMAGINE_AGENTS.map(a => a.name);
|
|
84
|
+
assert.deepStrictEqual(actualNames, expectedNames);
|
|
85
|
+
});
|
|
86
|
+
(0, node_test_1.it)('each has non-empty definitionPath, outputArtifact, requiredHeadings', () => {
|
|
87
|
+
for (const agent of imagine_agents_js_1.IMAGINE_AGENTS) {
|
|
88
|
+
assert.ok(agent.definitionPath.length > 0, `${agent.name} should have definitionPath`);
|
|
89
|
+
assert.ok(agent.outputArtifact.length > 0, `${agent.name} should have outputArtifact`);
|
|
90
|
+
assert.ok(agent.requiredHeadings.length > 0, `${agent.name} should have requiredHeadings`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
(0, node_test_1.describe)('buildAgentPrompt', () => {
|
|
95
|
+
(0, node_test_1.it)('includes StarterBrief as JSON in starter_brief tags', () => {
|
|
96
|
+
const agent = imagine_agents_js_1.IMAGINE_AGENTS[0];
|
|
97
|
+
const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
|
|
98
|
+
assert.ok(prompt.includes('<starter_brief>'), 'Should include starter_brief tags');
|
|
99
|
+
assert.ok(prompt.includes(TEST_BRIEF.vision), 'Should include brief vision');
|
|
100
|
+
assert.ok(prompt.includes('</starter_brief>'), 'Should close starter_brief tags');
|
|
101
|
+
});
|
|
102
|
+
(0, node_test_1.it)('mentions required headings', () => {
|
|
103
|
+
const agent = imagine_agents_js_1.IMAGINE_AGENTS[0]; // market-researcher
|
|
104
|
+
const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
|
|
105
|
+
assert.ok(prompt.includes('## Market Overview'), 'Should mention required headings');
|
|
106
|
+
assert.ok(prompt.includes('## Competitors'), 'Should mention required headings');
|
|
107
|
+
});
|
|
108
|
+
(0, node_test_1.it)('includes agent definition content', () => {
|
|
109
|
+
const agent = imagine_agents_js_1.IMAGINE_AGENTS[0]; // market-researcher
|
|
110
|
+
const prompt = (0, imagine_agents_js_1.buildAgentPrompt)(agent, TEST_BRIEF);
|
|
111
|
+
// Agent definition file should be included (it exists on disk)
|
|
112
|
+
assert.ok(prompt.includes('market') || prompt.includes('Market') || prompt.includes('researcher'), 'Should include content from agent definition');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
(0, node_test_1.describe)('orchestrateAgents', () => {
|
|
116
|
+
(0, node_test_1.it)('with maxParallel=5, all agents spawn in 1 batch', async () => {
|
|
117
|
+
const callOrder = [];
|
|
118
|
+
const trackingSpawn = async (prompt) => {
|
|
119
|
+
const nameMatch = prompt.match(/name:\s*(\S+)/);
|
|
120
|
+
callOrder.push(nameMatch ? nameMatch[1] : 'unknown');
|
|
121
|
+
return '## Output\nContent';
|
|
122
|
+
};
|
|
123
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, trackingSpawn);
|
|
124
|
+
assert.strictEqual(results.length, 5);
|
|
125
|
+
assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
|
|
126
|
+
});
|
|
127
|
+
(0, node_test_1.it)('with maxParallel=4, agents spawn in 2 batches (4 then 1)', async () => {
|
|
128
|
+
let batchCount = 0;
|
|
129
|
+
let currentBatchSize = 0;
|
|
130
|
+
const batchSizes = [];
|
|
131
|
+
const batchTrackingSpawn = async () => {
|
|
132
|
+
currentBatchSize++;
|
|
133
|
+
// Small delay to ensure batch tracking works
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
135
|
+
return '## Output\nContent';
|
|
136
|
+
};
|
|
137
|
+
// We need to track batches differently — count calls between sequential awaits
|
|
138
|
+
// Since orchestrateAgents uses Promise.all per batch, we can track via the results
|
|
139
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 4, TEST_BRIEF, batchTrackingSpawn);
|
|
140
|
+
assert.strictEqual(results.length, 5, 'Should have 5 results');
|
|
141
|
+
assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
|
|
142
|
+
});
|
|
143
|
+
(0, node_test_1.it)('with maxParallel=2, agents spawn in 3 batches (2, 2, 1)', async () => {
|
|
144
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 2, TEST_BRIEF, mockSpawnFn);
|
|
145
|
+
assert.strictEqual(results.length, 5, 'Should have 5 results');
|
|
146
|
+
assert.strictEqual(results.filter(r => r.status === 'complete').length, 5);
|
|
147
|
+
});
|
|
148
|
+
(0, node_test_1.it)('failed spawnFn marks agent as failed, does not crash orchestration', async () => {
|
|
149
|
+
const failSpawn = failingSpawnFn(['devils-advocate']);
|
|
150
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, failSpawn);
|
|
151
|
+
assert.strictEqual(results.length, 5, 'Should still have 5 results');
|
|
152
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
153
|
+
assert.strictEqual(failed.length, 1, 'Should have 1 failure');
|
|
154
|
+
assert.strictEqual(failed[0].agent, 'devils-advocate');
|
|
155
|
+
assert.ok(failed[0].error, 'Failed result should have error message');
|
|
156
|
+
const complete = results.filter(r => r.status === 'complete');
|
|
157
|
+
assert.strictEqual(complete.length, 4, 'Should have 4 successes');
|
|
158
|
+
});
|
|
159
|
+
(0, node_test_1.it)('mixed results (some complete, some failed) all returned', async () => {
|
|
160
|
+
const failSpawn = failingSpawnFn(['market-researcher', 'positioning']);
|
|
161
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 5, TEST_BRIEF, failSpawn);
|
|
162
|
+
assert.strictEqual(results.length, 5);
|
|
163
|
+
assert.strictEqual(results.filter(r => r.status === 'complete').length, 3);
|
|
164
|
+
assert.strictEqual(results.filter(r => r.status === 'failed').length, 2);
|
|
165
|
+
});
|
|
166
|
+
(0, node_test_1.it)('batch ordering is preserved (agents run in definition order)', async () => {
|
|
167
|
+
const order = [];
|
|
168
|
+
const orderTrackingSpawn = async (prompt) => {
|
|
169
|
+
const nameMatch = prompt.match(/name:\s*(\S+)/);
|
|
170
|
+
order.push(nameMatch ? nameMatch[1] : 'unknown');
|
|
171
|
+
return '## Output\nContent';
|
|
172
|
+
};
|
|
173
|
+
const results = await (0, imagine_agents_js_1.orchestrateAgents)(imagine_agents_js_1.IMAGINE_AGENTS, 1, TEST_BRIEF, orderTrackingSpawn);
|
|
174
|
+
// With maxParallel=1, agents run sequentially in definition order
|
|
175
|
+
const resultOrder = results.map(r => r.agent);
|
|
176
|
+
const expectedOrder = imagine_agents_js_1.IMAGINE_AGENTS.map(a => a.name);
|
|
177
|
+
assert.deepStrictEqual(resultOrder, expectedOrder);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for GSWD Imagine Gate Module
|
|
4
|
+
*
|
|
5
|
+
* Covers: countListItems, validateDecisionGate (passing, heading failures, count failures, combined)
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const node_test_1 = require("node:test");
|
|
42
|
+
const assert = __importStar(require("node:assert"));
|
|
43
|
+
const imagine_gate_js_1 = require("../lib/imagine-gate.js");
|
|
44
|
+
// ─── Test Fixtures ───────────────────────────────────────────────────────────
|
|
45
|
+
/** Full valid DECISIONS.md with all 5 sections and sufficient counts */
|
|
46
|
+
const VALID_DECISIONS = `# Decisions
|
|
47
|
+
|
|
48
|
+
## Frozen Decisions
|
|
49
|
+
|
|
50
|
+
- ICP: Solo founders building SaaS products
|
|
51
|
+
- Problem: Founders build before they think
|
|
52
|
+
- Wedge: CLI-first spec generator
|
|
53
|
+
- MVP: Four-stage pipeline (Imagine, Specify, Audit, Compile)
|
|
54
|
+
- Auth: Passwordless email
|
|
55
|
+
- Stack: Node.js + TypeScript
|
|
56
|
+
- Data store: SQLite for v1
|
|
57
|
+
- Success metric: Compile produces deterministic output
|
|
58
|
+
- Distribution: Open source CLI tool
|
|
59
|
+
- Pricing: Free for v1
|
|
60
|
+
|
|
61
|
+
## Success Metrics
|
|
62
|
+
|
|
63
|
+
- 80% of users complete the full pipeline on first run
|
|
64
|
+
- Compile produces byte-identical output across runs
|
|
65
|
+
|
|
66
|
+
## Out of Scope
|
|
67
|
+
|
|
68
|
+
- Web UI / dashboard
|
|
69
|
+
- Real-time collaborative editing
|
|
70
|
+
- Automatic GitHub/Jira ticket creation
|
|
71
|
+
- Cost/timeline estimation
|
|
72
|
+
|
|
73
|
+
## Risks & Mitigations
|
|
74
|
+
|
|
75
|
+
- Risk: LLM output non-determinism — Mitigation: Compile uses zero LLM calls
|
|
76
|
+
- Risk: Agent parallelism limits — Mitigation: Configurable max_parallel_agents
|
|
77
|
+
- Risk: Incomplete specs pass audit — Mitigation: Strict coverage matrix
|
|
78
|
+
- Risk: State corruption on crash — Mitigation: Atomic write pattern
|
|
79
|
+
- Risk: Spec format drift — Mitigation: Required heading enforcement
|
|
80
|
+
- Risk: Users skip stages — Mitigation: Hard gates between stages
|
|
81
|
+
|
|
82
|
+
## Open Questions
|
|
83
|
+
|
|
84
|
+
- What is the optimal max_parallel_agents default?
|
|
85
|
+
- Should we support custom auto policies?
|
|
86
|
+
`;
|
|
87
|
+
/** Minimal valid: exactly 8 frozen decisions, 1 metric, 5 risks, 1 out-of-scope */
|
|
88
|
+
const MINIMAL_VALID = `# Decisions
|
|
89
|
+
|
|
90
|
+
## Frozen Decisions
|
|
91
|
+
|
|
92
|
+
- Decision one
|
|
93
|
+
- Decision two
|
|
94
|
+
- Decision three
|
|
95
|
+
- Decision four
|
|
96
|
+
- Decision five
|
|
97
|
+
- Decision six
|
|
98
|
+
- Decision seven
|
|
99
|
+
- Decision eight
|
|
100
|
+
|
|
101
|
+
## Success Metrics
|
|
102
|
+
|
|
103
|
+
- One metric
|
|
104
|
+
|
|
105
|
+
## Out of Scope
|
|
106
|
+
|
|
107
|
+
- One exclusion
|
|
108
|
+
|
|
109
|
+
## Risks & Mitigations
|
|
110
|
+
|
|
111
|
+
- Risk one
|
|
112
|
+
- Risk two
|
|
113
|
+
- Risk three
|
|
114
|
+
- Risk four
|
|
115
|
+
- Risk five
|
|
116
|
+
|
|
117
|
+
## Open Questions
|
|
118
|
+
|
|
119
|
+
`;
|
|
120
|
+
/** Maximum metrics (3) — still valid */
|
|
121
|
+
const MAX_METRICS = MINIMAL_VALID.replace('- One metric', '- Metric one\n- Metric two\n- Metric three');
|
|
122
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
123
|
+
(0, node_test_1.describe)('countListItems', () => {
|
|
124
|
+
(0, node_test_1.it)('counts bullet items (- prefix)', () => {
|
|
125
|
+
const content = '- item one\n- item two\n- item three';
|
|
126
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 3);
|
|
127
|
+
});
|
|
128
|
+
(0, node_test_1.it)('counts numbered items (1. prefix)', () => {
|
|
129
|
+
const content = '1. first\n2. second\n3. third';
|
|
130
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 3);
|
|
131
|
+
});
|
|
132
|
+
(0, node_test_1.it)('counts sub-heading items (### prefix)', () => {
|
|
133
|
+
const content = '### Decision A\nDetails...\n### Decision B\nMore details...';
|
|
134
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 2);
|
|
135
|
+
});
|
|
136
|
+
(0, node_test_1.it)('returns 0 for empty string', () => {
|
|
137
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(''), 0);
|
|
138
|
+
});
|
|
139
|
+
(0, node_test_1.it)('returns 0 for null/undefined', () => {
|
|
140
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(null), 0);
|
|
141
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(undefined), 0);
|
|
142
|
+
});
|
|
143
|
+
(0, node_test_1.it)('handles mixed list styles in same section', () => {
|
|
144
|
+
const content = '- bullet one\n1. numbered one\n### heading one\n* star bullet';
|
|
145
|
+
assert.strictEqual((0, imagine_gate_js_1.countListItems)(content), 4);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
(0, node_test_1.describe)('validateDecisionGate — passing', () => {
|
|
149
|
+
(0, node_test_1.it)('full valid DECISIONS.md passes', () => {
|
|
150
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(VALID_DECISIONS);
|
|
151
|
+
assert.strictEqual(result.passed, true);
|
|
152
|
+
assert.deepStrictEqual(result.missing_sections, []);
|
|
153
|
+
assert.deepStrictEqual(result.insufficient_counts, []);
|
|
154
|
+
assert.ok(result.summary.includes('PASS'));
|
|
155
|
+
});
|
|
156
|
+
(0, node_test_1.it)('minimal valid (exactly 8 decisions, 1 metric, 5 risks, 1 out-of-scope) passes', () => {
|
|
157
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(MINIMAL_VALID);
|
|
158
|
+
assert.strictEqual(result.passed, true);
|
|
159
|
+
assert.deepStrictEqual(result.missing_sections, []);
|
|
160
|
+
assert.deepStrictEqual(result.insufficient_counts, []);
|
|
161
|
+
});
|
|
162
|
+
(0, node_test_1.it)('maximum metrics (exactly 3) passes', () => {
|
|
163
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(MAX_METRICS);
|
|
164
|
+
assert.strictEqual(result.passed, true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
(0, node_test_1.describe)('validateDecisionGate — heading failures', () => {
|
|
168
|
+
(0, node_test_1.it)('missing ## Frozen Decisions heading fails', () => {
|
|
169
|
+
const content = VALID_DECISIONS.replace('## Frozen Decisions', '## Decisions Made');
|
|
170
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
171
|
+
assert.strictEqual(result.passed, false);
|
|
172
|
+
assert.ok(result.missing_sections.includes('## Frozen Decisions'));
|
|
173
|
+
});
|
|
174
|
+
(0, node_test_1.it)('missing ## Success Metrics heading fails', () => {
|
|
175
|
+
const content = VALID_DECISIONS.replace('## Success Metrics', '## Metrics');
|
|
176
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
177
|
+
assert.strictEqual(result.passed, false);
|
|
178
|
+
assert.ok(result.missing_sections.includes('## Success Metrics'));
|
|
179
|
+
});
|
|
180
|
+
(0, node_test_1.it)('missing all headings fails with all 5 in missing_sections', () => {
|
|
181
|
+
const content = '# Some Document\n\nJust plain text with no required headings.';
|
|
182
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
183
|
+
assert.strictEqual(result.passed, false);
|
|
184
|
+
assert.strictEqual(result.missing_sections.length, 5);
|
|
185
|
+
assert.ok(result.missing_sections.includes('## Frozen Decisions'));
|
|
186
|
+
assert.ok(result.missing_sections.includes('## Success Metrics'));
|
|
187
|
+
assert.ok(result.missing_sections.includes('## Out of Scope'));
|
|
188
|
+
assert.ok(result.missing_sections.includes('## Risks & Mitigations'));
|
|
189
|
+
assert.ok(result.missing_sections.includes('## Open Questions'));
|
|
190
|
+
});
|
|
191
|
+
(0, node_test_1.it)('missing ## Open Questions heading fails (section must exist even if empty)', () => {
|
|
192
|
+
const content = VALID_DECISIONS.replace('## Open Questions', '## Questions');
|
|
193
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
194
|
+
assert.strictEqual(result.passed, false);
|
|
195
|
+
assert.ok(result.missing_sections.includes('## Open Questions'));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
(0, node_test_1.describe)('validateDecisionGate — count failures', () => {
|
|
199
|
+
(0, node_test_1.it)('only 7 frozen decisions fails (need >= 8)', () => {
|
|
200
|
+
// Remove one decision from MINIMAL_VALID
|
|
201
|
+
const content = MINIMAL_VALID.replace('- Decision eight\n', '');
|
|
202
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
203
|
+
assert.strictEqual(result.passed, false);
|
|
204
|
+
const frozenIssue = result.insufficient_counts.find(c => c.section === 'Frozen Decisions');
|
|
205
|
+
assert.ok(frozenIssue, 'Should have Frozen Decisions issue');
|
|
206
|
+
assert.strictEqual(frozenIssue.actual, 7);
|
|
207
|
+
assert.strictEqual(frozenIssue.required, 8);
|
|
208
|
+
});
|
|
209
|
+
(0, node_test_1.it)('0 success metrics fails (need >= 1)', () => {
|
|
210
|
+
const content = MINIMAL_VALID.replace('- One metric\n', '');
|
|
211
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
212
|
+
assert.strictEqual(result.passed, false);
|
|
213
|
+
const metricsIssue = result.insufficient_counts.find(c => c.section === 'Success Metrics');
|
|
214
|
+
assert.ok(metricsIssue, 'Should have Success Metrics issue');
|
|
215
|
+
assert.strictEqual(metricsIssue.actual, 0);
|
|
216
|
+
assert.ok(metricsIssue.constraint === '1-3');
|
|
217
|
+
});
|
|
218
|
+
(0, node_test_1.it)('4 success metrics fails (need <= 3)', () => {
|
|
219
|
+
const content = MINIMAL_VALID.replace('- One metric', '- Metric one\n- Metric two\n- Metric three\n- Metric four');
|
|
220
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
221
|
+
assert.strictEqual(result.passed, false);
|
|
222
|
+
const metricsIssue = result.insufficient_counts.find(c => c.section === 'Success Metrics');
|
|
223
|
+
assert.ok(metricsIssue, 'Should have Success Metrics issue');
|
|
224
|
+
assert.strictEqual(metricsIssue.actual, 4);
|
|
225
|
+
assert.ok(metricsIssue.constraint === '1-3');
|
|
226
|
+
});
|
|
227
|
+
(0, node_test_1.it)('only 4 risks fails (need >= 5)', () => {
|
|
228
|
+
const content = MINIMAL_VALID.replace('- Risk five\n', '');
|
|
229
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
230
|
+
assert.strictEqual(result.passed, false);
|
|
231
|
+
const risksIssue = result.insufficient_counts.find(c => c.section === 'Risks & Mitigations');
|
|
232
|
+
assert.ok(risksIssue, 'Should have Risks & Mitigations issue');
|
|
233
|
+
assert.strictEqual(risksIssue.actual, 4);
|
|
234
|
+
assert.strictEqual(risksIssue.required, 5);
|
|
235
|
+
});
|
|
236
|
+
(0, node_test_1.it)('empty Out of Scope section fails (need >= 1 item)', () => {
|
|
237
|
+
const content = MINIMAL_VALID.replace('- One exclusion\n', '');
|
|
238
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
239
|
+
assert.strictEqual(result.passed, false);
|
|
240
|
+
const scopeIssue = result.insufficient_counts.find(c => c.section === 'Out of Scope');
|
|
241
|
+
assert.ok(scopeIssue, 'Should have Out of Scope issue');
|
|
242
|
+
assert.strictEqual(scopeIssue.actual, 0);
|
|
243
|
+
assert.strictEqual(scopeIssue.required, 1);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
(0, node_test_1.describe)('validateDecisionGate — combined failures', () => {
|
|
247
|
+
(0, node_test_1.it)('missing heading AND insufficient count both reported', () => {
|
|
248
|
+
// Remove Frozen Decisions heading AND reduce risks
|
|
249
|
+
const content = VALID_DECISIONS
|
|
250
|
+
.replace('## Frozen Decisions', '## Decisions Made')
|
|
251
|
+
.replace('- Risk: Spec format drift — Mitigation: Required heading enforcement\n', '')
|
|
252
|
+
.replace('- Risk: Users skip stages — Mitigation: Hard gates between stages\n', '');
|
|
253
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
254
|
+
assert.strictEqual(result.passed, false);
|
|
255
|
+
assert.ok(result.missing_sections.length > 0, 'Should have missing sections');
|
|
256
|
+
assert.ok(result.insufficient_counts.length > 0, 'Should have insufficient counts');
|
|
257
|
+
});
|
|
258
|
+
(0, node_test_1.it)('summary includes count of total issues', () => {
|
|
259
|
+
const content = '# Some Document\n\nNo required headings at all.';
|
|
260
|
+
const result = (0, imagine_gate_js_1.validateDecisionGate)(content);
|
|
261
|
+
assert.ok(result.summary.includes('FAIL'));
|
|
262
|
+
assert.ok(result.summary.includes('issue'), 'Summary should mention issues');
|
|
263
|
+
});
|
|
264
|
+
});
|