gswd 1.0.1 → 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/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 +3 -2
- package/templates/gswd/DECISIONS.template.md +3 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSWD Bootstrap Module — Pipeline orchestrator, policy engine, resume gate
|
|
3
|
+
*
|
|
4
|
+
* Contains:
|
|
5
|
+
* - runBootstrap(): async orchestrator that wires imagine -> specify -> audit -> compile
|
|
6
|
+
* - Types: BootstrapOptions, BootstrapResult, BootstrapStage, PolicyCheckInput, PolicyCheckResult
|
|
7
|
+
* - Policy engine: checkPolicy() with 4 detection functions
|
|
8
|
+
* - Resume gate: validateStageArtifacts(), shouldSkipStage()
|
|
9
|
+
* - Interrupt handler: handleInterrupt()
|
|
10
|
+
* - Auto decision display: renderAutoDecisionSummary()
|
|
11
|
+
* - Stage artifacts map: STAGE_ARTIFACTS
|
|
12
|
+
*
|
|
13
|
+
* Schema: GSWD_SPEC.md Section 8.1, 10.1-10.4
|
|
14
|
+
*/
|
|
15
|
+
import type { StageStatus } from './state.js';
|
|
16
|
+
import type { GswdConfig } from './config.js';
|
|
17
|
+
import type { AutoDecision } from './imagine-synthesis.js';
|
|
18
|
+
export interface BootstrapOptions {
|
|
19
|
+
ideaFilePath?: string;
|
|
20
|
+
auto: boolean;
|
|
21
|
+
resume?: boolean;
|
|
22
|
+
skipResearch?: boolean;
|
|
23
|
+
policy?: 'strict' | 'balanced' | 'aggressive';
|
|
24
|
+
planningDir?: string;
|
|
25
|
+
configPath?: string;
|
|
26
|
+
spawnFn?: unknown;
|
|
27
|
+
}
|
|
28
|
+
export type BootstrapStage = 'imagine' | 'specify' | 'audit' | 'compile';
|
|
29
|
+
export interface BootstrapResult {
|
|
30
|
+
status: 'done' | 'failed' | 'interrupted';
|
|
31
|
+
stagesCompleted: BootstrapStage[];
|
|
32
|
+
artifactsWritten: string[];
|
|
33
|
+
autoDecisions: AutoDecision[];
|
|
34
|
+
interruptReasons: string[];
|
|
35
|
+
autoFixCycles: number;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface PolicyCheckInput {
|
|
39
|
+
config: GswdConfig;
|
|
40
|
+
integrationsContent?: string;
|
|
41
|
+
decisionsContent?: string;
|
|
42
|
+
nfrContent?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface PolicyCheckResult {
|
|
45
|
+
shouldInterrupt: boolean;
|
|
46
|
+
reasons: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Required artifact filenames per stage.
|
|
50
|
+
* Used by validateStageArtifacts() and shouldSkipStage().
|
|
51
|
+
*/
|
|
52
|
+
export declare const STAGE_ARTIFACTS: Record<BootstrapStage, string[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Check policy violations. Returns whether auto mode should be interrupted
|
|
55
|
+
* and the specific reasons.
|
|
56
|
+
*
|
|
57
|
+
* Checks:
|
|
58
|
+
* 1. Paid integrations not pre-approved
|
|
59
|
+
* 2. Credential requirements
|
|
60
|
+
* 3. Conflicting requirements (decisions vs integrations)
|
|
61
|
+
* 4. Security posture mismatches (NFR vs config)
|
|
62
|
+
*/
|
|
63
|
+
export declare function checkPolicy(input: PolicyCheckInput): PolicyCheckResult;
|
|
64
|
+
/**
|
|
65
|
+
* Validate that all expected artifacts for a stage exist on disk and are non-empty.
|
|
66
|
+
* Returns { complete, missing } where missing lists filenames not found or empty.
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateStageArtifacts(planningDir: string, stage: BootstrapStage): {
|
|
69
|
+
complete: boolean;
|
|
70
|
+
missing: string[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Determine if a stage can be skipped during resume.
|
|
74
|
+
* Only skips if: resume=true AND stage is done/pass AND all artifacts are present.
|
|
75
|
+
*/
|
|
76
|
+
export declare function shouldSkipStage(stage: BootstrapStage, state: {
|
|
77
|
+
stage_status: StageStatus | Record<string, string>;
|
|
78
|
+
}, planningDir: string, resume: boolean): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Handle a policy interrupt: write reasons to STATE.json, render checkpoint, return result.
|
|
81
|
+
* MUST write to STATE.json BEFORE rendering (Pitfall 6).
|
|
82
|
+
*/
|
|
83
|
+
export declare function handleInterrupt(reasons: string[], statePath: string, stagesCompleted: BootstrapStage[], allArtifacts: string[], allAutoDecisions: AutoDecision[], autoFixCycles: number): BootstrapResult;
|
|
84
|
+
/**
|
|
85
|
+
* Render a human-readable summary of auto-mode decisions.
|
|
86
|
+
* Returns empty string if no decisions.
|
|
87
|
+
*/
|
|
88
|
+
export declare function renderAutoDecisionSummary(decisions: AutoDecision[]): string;
|
|
89
|
+
/**
|
|
90
|
+
* Run the full bootstrap pipeline: init -> imagine -> specify -> audit -> compile.
|
|
91
|
+
*
|
|
92
|
+
* Implements GSWD_SPEC Section 8.1:
|
|
93
|
+
* - Auto mode threads autoMode/auto/autoFix to each stage
|
|
94
|
+
* - Resume mode skips completed stages with valid artifacts
|
|
95
|
+
* - Policy checks fire after imagine and after specify
|
|
96
|
+
* - Audit FAIL blocks compile (BOOT-03)
|
|
97
|
+
* - Auto-fix enabled with max 2 cycles when policy allows (BOOT-04)
|
|
98
|
+
* - state.stage = 'done' after successful compile (Pitfall 8)
|
|
99
|
+
*
|
|
100
|
+
* IMPORTANT: runImagine() and runSpecify() are ASYNC — use await.
|
|
101
|
+
* runAuditWorkflow() and runCompileWorkflow() are SYNC — no await.
|
|
102
|
+
*/
|
|
103
|
+
export declare function runBootstrap(options: BootstrapOptions): Promise<BootstrapResult>;
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GSWD Bootstrap Module — Pipeline orchestrator, policy engine, resume gate
|
|
4
|
+
*
|
|
5
|
+
* Contains:
|
|
6
|
+
* - runBootstrap(): async orchestrator that wires imagine -> specify -> audit -> compile
|
|
7
|
+
* - Types: BootstrapOptions, BootstrapResult, BootstrapStage, PolicyCheckInput, PolicyCheckResult
|
|
8
|
+
* - Policy engine: checkPolicy() with 4 detection functions
|
|
9
|
+
* - Resume gate: validateStageArtifacts(), shouldSkipStage()
|
|
10
|
+
* - Interrupt handler: handleInterrupt()
|
|
11
|
+
* - Auto decision display: renderAutoDecisionSummary()
|
|
12
|
+
* - Stage artifacts map: STAGE_ARTIFACTS
|
|
13
|
+
*
|
|
14
|
+
* Schema: GSWD_SPEC.md Section 8.1, 10.1-10.4
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.STAGE_ARTIFACTS = void 0;
|
|
51
|
+
exports.checkPolicy = checkPolicy;
|
|
52
|
+
exports.validateStageArtifacts = validateStageArtifacts;
|
|
53
|
+
exports.shouldSkipStage = shouldSkipStage;
|
|
54
|
+
exports.handleInterrupt = handleInterrupt;
|
|
55
|
+
exports.renderAutoDecisionSummary = renderAutoDecisionSummary;
|
|
56
|
+
exports.runBootstrap = runBootstrap;
|
|
57
|
+
const fs = __importStar(require("node:fs"));
|
|
58
|
+
const path = __importStar(require("node:path"));
|
|
59
|
+
const state_js_1 = require("./state.js");
|
|
60
|
+
const config_js_1 = require("./config.js");
|
|
61
|
+
const render_js_1 = require("./render.js");
|
|
62
|
+
const imagine_js_1 = require("./imagine.js");
|
|
63
|
+
const specify_js_1 = require("./specify.js");
|
|
64
|
+
const audit_js_1 = require("./audit.js");
|
|
65
|
+
const compile_js_1 = require("./compile.js");
|
|
66
|
+
// ─── Stage Artifacts ─────────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Required artifact filenames per stage.
|
|
69
|
+
* Used by validateStageArtifacts() and shouldSkipStage().
|
|
70
|
+
*/
|
|
71
|
+
exports.STAGE_ARTIFACTS = {
|
|
72
|
+
imagine: ['IMAGINE.md', 'ICP.md', 'GTM.md', 'COMPETITION.md', 'DECISIONS.md'],
|
|
73
|
+
specify: ['SPEC.md', 'JOURNEYS.md', 'NFR.md', 'ARCHITECTURE.md', 'INTEGRATIONS.md'],
|
|
74
|
+
audit: ['AUDIT.md'],
|
|
75
|
+
compile: ['PROJECT.md', 'REQUIREMENTS.md', 'ROADMAP.md', 'STATE.md'],
|
|
76
|
+
};
|
|
77
|
+
// ─── Policy Engine — Detection Functions ─────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Detect integrations with a positive monthly cost.
|
|
80
|
+
* Parses dollar amounts from integration sections (### I-NNN headings).
|
|
81
|
+
* Returns array of integration IDs with cost > $0.
|
|
82
|
+
*/
|
|
83
|
+
function detectPaidIntegrations(integrationsContent) {
|
|
84
|
+
const paidIds = [];
|
|
85
|
+
// Match integration section headings: ### I-001: Name or ## I-001: Name
|
|
86
|
+
const intRegex = /^#{2,3}\s+(I-\d{1,4})[:\s]/gm;
|
|
87
|
+
const positions = [];
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = intRegex.exec(integrationsContent)) !== null) {
|
|
90
|
+
positions.push({ id: match[1], start: match.index });
|
|
91
|
+
}
|
|
92
|
+
for (let i = 0; i < positions.length; i++) {
|
|
93
|
+
const end = i + 1 < positions.length ? positions[i + 1].start : integrationsContent.length;
|
|
94
|
+
const section = integrationsContent.slice(positions[i].start, end);
|
|
95
|
+
// Look for dollar amounts: $50, $25.99, 50 USD/month
|
|
96
|
+
const costMatch = section.match(/\$(\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)\s*(?:USD|usd)\/month/);
|
|
97
|
+
if (costMatch) {
|
|
98
|
+
const amount = parseFloat(costMatch[1] ?? costMatch[2] ?? '0');
|
|
99
|
+
if (amount > 0) {
|
|
100
|
+
paidIds.push(positions[i].id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return paidIds;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Detect integrations that require credentials (API keys, OAuth secrets, etc.).
|
|
108
|
+
* Returns array of { id, requirement } objects.
|
|
109
|
+
*/
|
|
110
|
+
function detectCredentialRequirements(integrationsContent) {
|
|
111
|
+
const credentials = [];
|
|
112
|
+
const intRegex = /^#{2,3}\s+(I-\d{1,4})[:\s]/gm;
|
|
113
|
+
const positions = [];
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = intRegex.exec(integrationsContent)) !== null) {
|
|
116
|
+
positions.push({ id: match[1], start: match.index });
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < positions.length; i++) {
|
|
119
|
+
const end = i + 1 < positions.length ? positions[i + 1].start : integrationsContent.length;
|
|
120
|
+
const section = integrationsContent.slice(positions[i].start, end);
|
|
121
|
+
// Look for credential-related patterns
|
|
122
|
+
const credPatterns = [
|
|
123
|
+
/\b(?:API\s*key)\s*required\b/i,
|
|
124
|
+
/\bOAuth\s*(?:secret|token|key)\b/i,
|
|
125
|
+
/\bcredentials?\s*(?:required|needed)\b/i,
|
|
126
|
+
/\bsecret\s*(?:key|required)\b/i,
|
|
127
|
+
];
|
|
128
|
+
for (const pattern of credPatterns) {
|
|
129
|
+
const credMatch = section.match(pattern);
|
|
130
|
+
if (credMatch) {
|
|
131
|
+
credentials.push({ id: positions[i].id, requirement: credMatch[0] });
|
|
132
|
+
break; // One match per integration is enough
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return credentials;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Detect contradictions between decisions and integrations.
|
|
140
|
+
* E.g., "no accounts" decision + Stripe integration = conflict.
|
|
141
|
+
*/
|
|
142
|
+
function detectConflicts(decisionsContent, integrationsContent) {
|
|
143
|
+
const conflicts = [];
|
|
144
|
+
const noAccountsDecision = /\bno\s+accounts?\b/i.test(decisionsContent) ||
|
|
145
|
+
/\bno\s+auth\b/i.test(decisionsContent);
|
|
146
|
+
const hasStripe = /stripe/i.test(integrationsContent);
|
|
147
|
+
const hasPayments = /payment/i.test(integrationsContent);
|
|
148
|
+
if (noAccountsDecision && (hasStripe || hasPayments)) {
|
|
149
|
+
conflicts.push('Conflicting requirements: decision says "no accounts" but integrations include payment processing (requires accounts)');
|
|
150
|
+
}
|
|
151
|
+
return conflicts;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Detect security posture mismatches between NFR content and config.
|
|
155
|
+
* E.g., HIPAA/SOC2/PCI requirements without explicit config approval.
|
|
156
|
+
*/
|
|
157
|
+
function detectSecurityPosture(nfrContent, config) {
|
|
158
|
+
const flags = [];
|
|
159
|
+
const securityPatterns = [
|
|
160
|
+
{ pattern: /\bHIPAA\b/i, label: 'HIPAA' },
|
|
161
|
+
{ pattern: /\bSOC\s*2\b/i, label: 'SOC2' },
|
|
162
|
+
{ pattern: /\bPCI[\s-]*DSS\b/i, label: 'PCI-DSS' },
|
|
163
|
+
{ pattern: /\bGDPR\b/i, label: 'GDPR' },
|
|
164
|
+
];
|
|
165
|
+
for (const { pattern, label } of securityPatterns) {
|
|
166
|
+
if (pattern.test(nfrContent)) {
|
|
167
|
+
// Check if config has explicit security approval
|
|
168
|
+
// For now, config has no explicit security_approvals field,
|
|
169
|
+
// so any compliance requirement triggers an interrupt
|
|
170
|
+
flags.push(`Security posture mismatch: ${label} compliance referenced in NFR but not explicitly approved in config — requires human review`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return flags;
|
|
174
|
+
}
|
|
175
|
+
// ─── Policy Engine — Main Function ───────────────────────────────────────────
|
|
176
|
+
/**
|
|
177
|
+
* Check policy violations. Returns whether auto mode should be interrupted
|
|
178
|
+
* and the specific reasons.
|
|
179
|
+
*
|
|
180
|
+
* Checks:
|
|
181
|
+
* 1. Paid integrations not pre-approved
|
|
182
|
+
* 2. Credential requirements
|
|
183
|
+
* 3. Conflicting requirements (decisions vs integrations)
|
|
184
|
+
* 4. Security posture mismatches (NFR vs config)
|
|
185
|
+
*/
|
|
186
|
+
function checkPolicy(input) {
|
|
187
|
+
const reasons = [];
|
|
188
|
+
const { config } = input;
|
|
189
|
+
// 1. Paid integrations not pre-approved
|
|
190
|
+
if (input.integrationsContent) {
|
|
191
|
+
const paidIntegrations = detectPaidIntegrations(input.integrationsContent);
|
|
192
|
+
const preapproved = new Set(config.auto.preapproved_integrations);
|
|
193
|
+
for (const intId of paidIntegrations) {
|
|
194
|
+
if (!preapproved.has(intId) && !config.auto.allow_paid_integrations_under_budget) {
|
|
195
|
+
reasons.push(`Paid integration ${intId} requires pre-approval (not in preapproved_integrations)`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 2. Credentials that cannot be inferred
|
|
200
|
+
if (input.integrationsContent) {
|
|
201
|
+
const credentialRequired = detectCredentialRequirements(input.integrationsContent);
|
|
202
|
+
for (const cred of credentialRequired) {
|
|
203
|
+
reasons.push(`Integration ${cred.id} requires credentials: ${cred.requirement}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 3. Conflicting requirements
|
|
207
|
+
if (input.decisionsContent && input.integrationsContent) {
|
|
208
|
+
const conflicts = detectConflicts(input.decisionsContent, input.integrationsContent);
|
|
209
|
+
reasons.push(...conflicts);
|
|
210
|
+
}
|
|
211
|
+
// 4. Security posture mismatches
|
|
212
|
+
if (input.nfrContent) {
|
|
213
|
+
const securityFlags = detectSecurityPosture(input.nfrContent, config);
|
|
214
|
+
reasons.push(...securityFlags);
|
|
215
|
+
}
|
|
216
|
+
return { shouldInterrupt: reasons.length > 0, reasons };
|
|
217
|
+
}
|
|
218
|
+
// ─── Resume Gate ─────────────────────────────────────────────────────────────
|
|
219
|
+
/**
|
|
220
|
+
* Validate that all expected artifacts for a stage exist on disk and are non-empty.
|
|
221
|
+
* Returns { complete, missing } where missing lists filenames not found or empty.
|
|
222
|
+
*/
|
|
223
|
+
function validateStageArtifacts(planningDir, stage) {
|
|
224
|
+
const required = exports.STAGE_ARTIFACTS[stage] ?? [];
|
|
225
|
+
const missing = [];
|
|
226
|
+
for (const filename of required) {
|
|
227
|
+
const filepath = path.join(planningDir, filename);
|
|
228
|
+
if (!fs.existsSync(filepath)) {
|
|
229
|
+
missing.push(filename);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Check non-zero bytes (Pitfall 2: empty files)
|
|
233
|
+
try {
|
|
234
|
+
const stat = fs.statSync(filepath);
|
|
235
|
+
if (stat.size === 0) {
|
|
236
|
+
missing.push(filename);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
missing.push(filename);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { complete: missing.length === 0, missing };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Determine if a stage can be skipped during resume.
|
|
248
|
+
* Only skips if: resume=true AND stage is done/pass AND all artifacts are present.
|
|
249
|
+
*/
|
|
250
|
+
function shouldSkipStage(stage, state, planningDir, resume) {
|
|
251
|
+
if (!resume)
|
|
252
|
+
return false;
|
|
253
|
+
const stageStatus = state.stage_status[stage];
|
|
254
|
+
const isDone = stageStatus === 'done' || stageStatus === 'pass';
|
|
255
|
+
if (!isDone)
|
|
256
|
+
return false;
|
|
257
|
+
// Validate artifacts before trusting checkpoint (RESM-03)
|
|
258
|
+
const { complete } = validateStageArtifacts(planningDir, stage);
|
|
259
|
+
return complete;
|
|
260
|
+
}
|
|
261
|
+
// ─── Interrupt Handler ───────────────────────────────────────────────────────
|
|
262
|
+
/**
|
|
263
|
+
* Handle a policy interrupt: write reasons to STATE.json, render checkpoint, return result.
|
|
264
|
+
* MUST write to STATE.json BEFORE rendering (Pitfall 6).
|
|
265
|
+
*/
|
|
266
|
+
function handleInterrupt(reasons, statePath, stagesCompleted, allArtifacts, allAutoDecisions, autoFixCycles) {
|
|
267
|
+
// Step 1: Write to STATE.json FIRST
|
|
268
|
+
const state = (0, state_js_1.readState)(statePath);
|
|
269
|
+
if (state) {
|
|
270
|
+
if (!state.auto.interrupt_reasons) {
|
|
271
|
+
state.auto.interrupt_reasons = [];
|
|
272
|
+
}
|
|
273
|
+
state.auto.interrupt_reasons = [...state.auto.interrupt_reasons, ...reasons];
|
|
274
|
+
(0, state_js_1.writeState)(statePath, state);
|
|
275
|
+
}
|
|
276
|
+
// Step 2: Render checkpoint box
|
|
277
|
+
const box = (0, render_js_1.renderCheckpoint)('Auto Mode Interrupted', 'Pipeline cannot continue automatically. Resolve:', reasons.map(r => r.length > 50 ? r.slice(0, 47) + '...' : r), 'Resolve issues above, then re-run /gswd:start');
|
|
278
|
+
console.log(box);
|
|
279
|
+
// Step 3: Return result
|
|
280
|
+
return {
|
|
281
|
+
status: 'interrupted',
|
|
282
|
+
stagesCompleted: [...stagesCompleted],
|
|
283
|
+
artifactsWritten: [...allArtifacts],
|
|
284
|
+
autoDecisions: [...allAutoDecisions],
|
|
285
|
+
interruptReasons: [...reasons],
|
|
286
|
+
autoFixCycles,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
// ─── Auto Decision Summary ───────────────────────────────────────────────────
|
|
290
|
+
/**
|
|
291
|
+
* Render a human-readable summary of auto-mode decisions.
|
|
292
|
+
* Returns empty string if no decisions.
|
|
293
|
+
*/
|
|
294
|
+
function renderAutoDecisionSummary(decisions) {
|
|
295
|
+
if (decisions.length === 0)
|
|
296
|
+
return '';
|
|
297
|
+
const lines = ['Auto-mode decisions:'];
|
|
298
|
+
for (const d of decisions) {
|
|
299
|
+
lines.push(` \u26A1 ${d.type}: ${d.chosen}`);
|
|
300
|
+
lines.push(` Why: ${d.rationale}`);
|
|
301
|
+
lines.push(` Recorded in: DECISIONS.md + STATE.json auto.decisions[]`);
|
|
302
|
+
}
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
306
|
+
/**
|
|
307
|
+
* Try to read a file, returning undefined if it doesn't exist.
|
|
308
|
+
*/
|
|
309
|
+
function tryReadFile(filePath) {
|
|
310
|
+
try {
|
|
311
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Derive a project slug from an idea file path or a fallback.
|
|
319
|
+
*/
|
|
320
|
+
function deriveProjectSlug(input) {
|
|
321
|
+
const base = path.basename(input, path.extname(input));
|
|
322
|
+
return base.replace(/[^a-z0-9-]/gi, '-').toLowerCase() || 'project';
|
|
323
|
+
}
|
|
324
|
+
// ─── Bootstrap Orchestrator ──────────────────────────────────────────────────
|
|
325
|
+
/**
|
|
326
|
+
* Run the full bootstrap pipeline: init -> imagine -> specify -> audit -> compile.
|
|
327
|
+
*
|
|
328
|
+
* Implements GSWD_SPEC Section 8.1:
|
|
329
|
+
* - Auto mode threads autoMode/auto/autoFix to each stage
|
|
330
|
+
* - Resume mode skips completed stages with valid artifacts
|
|
331
|
+
* - Policy checks fire after imagine and after specify
|
|
332
|
+
* - Audit FAIL blocks compile (BOOT-03)
|
|
333
|
+
* - Auto-fix enabled with max 2 cycles when policy allows (BOOT-04)
|
|
334
|
+
* - state.stage = 'done' after successful compile (Pitfall 8)
|
|
335
|
+
*
|
|
336
|
+
* IMPORTANT: runImagine() and runSpecify() are ASYNC — use await.
|
|
337
|
+
* runAuditWorkflow() and runCompileWorkflow() are SYNC — no await.
|
|
338
|
+
*/
|
|
339
|
+
async function runBootstrap(options) {
|
|
340
|
+
const planningDir = options.planningDir ?? path.join(process.cwd(), '.planning');
|
|
341
|
+
const gswdDir = path.join(planningDir, 'gswd');
|
|
342
|
+
const statePath = path.join(gswdDir, 'STATE.json');
|
|
343
|
+
const configPath = options.configPath ?? path.join(planningDir, 'config.json');
|
|
344
|
+
const stagesCompleted = [];
|
|
345
|
+
const allArtifacts = [];
|
|
346
|
+
let allAutoDecisions = [];
|
|
347
|
+
let autoFixCycles = 0;
|
|
348
|
+
// Early validation: auto mode requires @idea.md
|
|
349
|
+
if (options.auto && !options.ideaFilePath) {
|
|
350
|
+
return {
|
|
351
|
+
status: 'failed',
|
|
352
|
+
stagesCompleted,
|
|
353
|
+
artifactsWritten: allArtifacts,
|
|
354
|
+
autoDecisions: allAutoDecisions,
|
|
355
|
+
interruptReasons: [],
|
|
356
|
+
autoFixCycles,
|
|
357
|
+
error: 'Auto mode requires @idea.md — provide a file describing your idea',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// Step 1: Ensure STATE.json exists
|
|
361
|
+
if (!fs.existsSync(statePath)) {
|
|
362
|
+
fs.mkdirSync(gswdDir, { recursive: true });
|
|
363
|
+
(0, state_js_1.initState)(gswdDir, deriveProjectSlug(options.ideaFilePath ?? planningDir));
|
|
364
|
+
}
|
|
365
|
+
// Step 2: Load config
|
|
366
|
+
const config = (0, config_js_1.getGswdConfig)(configPath);
|
|
367
|
+
if (options.policy) {
|
|
368
|
+
config.auto.policy = options.policy;
|
|
369
|
+
}
|
|
370
|
+
// Step 3: Configure auto mode in state
|
|
371
|
+
const initStateObj = (0, state_js_1.readState)(statePath);
|
|
372
|
+
if (initStateObj) {
|
|
373
|
+
initStateObj.auto.enabled = options.auto;
|
|
374
|
+
initStateObj.auto.policy = config.auto.policy;
|
|
375
|
+
(0, state_js_1.writeState)(statePath, initStateObj);
|
|
376
|
+
}
|
|
377
|
+
console.log((0, render_js_1.renderBanner)('PIPELINE'));
|
|
378
|
+
// Extract product context for contextual banners
|
|
379
|
+
const productCtx = (0, render_js_1.extractProductContext)(planningDir);
|
|
380
|
+
// ── IMAGINE ──────────────────────────────────────────────────────────
|
|
381
|
+
const imagineState = (0, state_js_1.readState)(statePath);
|
|
382
|
+
if (!shouldSkipStage('imagine', imagineState, planningDir, options.resume ?? false)) {
|
|
383
|
+
(0, state_js_1.writeCheckpoint)(statePath, 'gswd/bootstrap', 'imagine-start');
|
|
384
|
+
const imagineCtxLine = render_js_1.STAGE_CONTEXT_TEMPLATES.imagine?.(productCtx) || '';
|
|
385
|
+
console.log((0, render_js_1.renderContextBanner)('IMAGINE', imagineCtxLine));
|
|
386
|
+
// runImagine is ASYNC — MUST await
|
|
387
|
+
const imagineResult = await (0, imagine_js_1.runImagine)({
|
|
388
|
+
ideaFilePath: options.ideaFilePath,
|
|
389
|
+
autoMode: options.auto,
|
|
390
|
+
skipResearch: options.skipResearch ?? false,
|
|
391
|
+
planningDir,
|
|
392
|
+
configPath,
|
|
393
|
+
spawnFn: options.spawnFn,
|
|
394
|
+
});
|
|
395
|
+
if (imagineResult.status !== 'complete') {
|
|
396
|
+
return {
|
|
397
|
+
status: 'failed',
|
|
398
|
+
stagesCompleted,
|
|
399
|
+
artifactsWritten: allArtifacts,
|
|
400
|
+
autoDecisions: allAutoDecisions,
|
|
401
|
+
interruptReasons: [],
|
|
402
|
+
autoFixCycles,
|
|
403
|
+
error: imagineResult.error ?? 'Imagine stage failed',
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
allArtifacts.push(...(imagineResult.artifacts_written ?? []));
|
|
407
|
+
if (imagineResult.auto_decisions) {
|
|
408
|
+
allAutoDecisions = [...allAutoDecisions, ...imagineResult.auto_decisions];
|
|
409
|
+
}
|
|
410
|
+
// Record auto decisions in STATE.json (AUTO-04)
|
|
411
|
+
if (options.auto && allAutoDecisions.length > 0) {
|
|
412
|
+
const stateAfterImagine = (0, state_js_1.readState)(statePath);
|
|
413
|
+
if (stateAfterImagine) {
|
|
414
|
+
stateAfterImagine.auto.decisions = allAutoDecisions;
|
|
415
|
+
(0, state_js_1.writeState)(statePath, stateAfterImagine);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
stagesCompleted.push('imagine');
|
|
420
|
+
// Policy check after imagine (conflicting requirements check)
|
|
421
|
+
if (options.auto) {
|
|
422
|
+
const decisionsContent = tryReadFile(path.join(planningDir, 'DECISIONS.md'));
|
|
423
|
+
const check = checkPolicy({ config, decisionsContent });
|
|
424
|
+
if (check.shouldInterrupt) {
|
|
425
|
+
return handleInterrupt(check.reasons, statePath, stagesCompleted, allArtifacts, allAutoDecisions, autoFixCycles);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// ── SPECIFY ──────────────────────────────────────────────────────────
|
|
429
|
+
const specifyState = (0, state_js_1.readState)(statePath);
|
|
430
|
+
if (!shouldSkipStage('specify', specifyState, planningDir, options.resume ?? false)) {
|
|
431
|
+
(0, state_js_1.writeCheckpoint)(statePath, 'gswd/bootstrap', 'specify-start');
|
|
432
|
+
// Re-extract after imagine (DECISIONS.md now exists with precise data)
|
|
433
|
+
const specifyCtx = (0, render_js_1.extractProductContext)(planningDir);
|
|
434
|
+
const specifyCtxLine = render_js_1.STAGE_CONTEXT_TEMPLATES.specify?.(specifyCtx) || '';
|
|
435
|
+
console.log((0, render_js_1.renderContextBanner)('SPECIFY', specifyCtxLine));
|
|
436
|
+
// runSpecify is ASYNC — MUST await
|
|
437
|
+
// When no spawnFn provided, set skipAgents: true (Pitfall 1)
|
|
438
|
+
const specifyResult = await (0, specify_js_1.runSpecify)({
|
|
439
|
+
auto: options.auto,
|
|
440
|
+
skipAgents: !options.spawnFn,
|
|
441
|
+
planningDir,
|
|
442
|
+
configPath,
|
|
443
|
+
spawnFn: options.spawnFn,
|
|
444
|
+
});
|
|
445
|
+
if (specifyResult.status === 'failed') {
|
|
446
|
+
return {
|
|
447
|
+
status: 'failed',
|
|
448
|
+
stagesCompleted,
|
|
449
|
+
artifactsWritten: allArtifacts,
|
|
450
|
+
autoDecisions: allAutoDecisions,
|
|
451
|
+
interruptReasons: [],
|
|
452
|
+
autoFixCycles,
|
|
453
|
+
error: (specifyResult.errors ?? []).join('; ') || 'Specify stage failed',
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
allArtifacts.push(...specifyResult.artifacts);
|
|
457
|
+
}
|
|
458
|
+
stagesCompleted.push('specify');
|
|
459
|
+
// Policy check after specify (check integrations, NFRs, decisions)
|
|
460
|
+
if (options.auto) {
|
|
461
|
+
const intContent = tryReadFile(path.join(planningDir, 'INTEGRATIONS.md'));
|
|
462
|
+
const nfrContent = tryReadFile(path.join(planningDir, 'NFR.md'));
|
|
463
|
+
const decisionsContent = tryReadFile(path.join(planningDir, 'DECISIONS.md'));
|
|
464
|
+
const check = checkPolicy({ config, integrationsContent: intContent, nfrContent, decisionsContent });
|
|
465
|
+
if (check.shouldInterrupt) {
|
|
466
|
+
return handleInterrupt(check.reasons, statePath, stagesCompleted, allArtifacts, allAutoDecisions, autoFixCycles);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// ── AUDIT ────────────────────────────────────────────────────────────
|
|
470
|
+
const auditState = (0, state_js_1.readState)(statePath);
|
|
471
|
+
if (!shouldSkipStage('audit', auditState, planningDir, options.resume ?? false)) {
|
|
472
|
+
(0, state_js_1.writeCheckpoint)(statePath, 'gswd/bootstrap', 'audit-start');
|
|
473
|
+
const auditCtx = (0, render_js_1.extractProductContext)(planningDir);
|
|
474
|
+
const auditCtxLine = render_js_1.STAGE_CONTEXT_TEMPLATES.audit?.(auditCtx) || '';
|
|
475
|
+
console.log((0, render_js_1.renderContextBanner)('AUDIT', auditCtxLine));
|
|
476
|
+
// runAuditWorkflow is SYNC — no await (Pitfall 7)
|
|
477
|
+
const auditResult = (0, audit_js_1.runAuditWorkflow)({
|
|
478
|
+
planningDir,
|
|
479
|
+
statePath,
|
|
480
|
+
autoFix: options.auto && config.auto.policy !== 'strict',
|
|
481
|
+
maxCycles: 2,
|
|
482
|
+
});
|
|
483
|
+
autoFixCycles = auditResult.autoFixCycles;
|
|
484
|
+
if (!auditResult.passed) {
|
|
485
|
+
// BOOT-03: Cannot finish if audit is FAIL
|
|
486
|
+
return {
|
|
487
|
+
status: 'failed',
|
|
488
|
+
stagesCompleted,
|
|
489
|
+
artifactsWritten: allArtifacts,
|
|
490
|
+
autoDecisions: allAutoDecisions,
|
|
491
|
+
interruptReasons: [],
|
|
492
|
+
autoFixCycles,
|
|
493
|
+
error: 'Audit FAIL — cannot compile',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
stagesCompleted.push('audit');
|
|
498
|
+
// ── COMPILE ──────────────────────────────────────────────────────────
|
|
499
|
+
const compileState = (0, state_js_1.readState)(statePath);
|
|
500
|
+
if (!shouldSkipStage('compile', compileState, planningDir, options.resume ?? false)) {
|
|
501
|
+
(0, state_js_1.writeCheckpoint)(statePath, 'gswd/bootstrap', 'compile-start');
|
|
502
|
+
console.log((0, render_js_1.renderBanner)('COMPILE'));
|
|
503
|
+
// runCompileWorkflow is SYNC — no await (Pitfall 7)
|
|
504
|
+
const compileResult = (0, compile_js_1.runCompileWorkflow)({
|
|
505
|
+
planningDir,
|
|
506
|
+
statePath,
|
|
507
|
+
});
|
|
508
|
+
if (!compileResult.passed) {
|
|
509
|
+
return {
|
|
510
|
+
status: 'failed',
|
|
511
|
+
stagesCompleted,
|
|
512
|
+
artifactsWritten: allArtifacts,
|
|
513
|
+
autoDecisions: allAutoDecisions,
|
|
514
|
+
interruptReasons: [],
|
|
515
|
+
autoFixCycles,
|
|
516
|
+
error: compileResult.error ?? 'Compile validator failed',
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
allArtifacts.push(...compileResult.filesWritten);
|
|
520
|
+
}
|
|
521
|
+
stagesCompleted.push('compile');
|
|
522
|
+
// Step final: Set stage = 'done' (Pitfall 8 — ONLY place 'done' is written)
|
|
523
|
+
const doneState = (0, state_js_1.readState)(statePath);
|
|
524
|
+
doneState.stage = 'done';
|
|
525
|
+
(0, state_js_1.writeState)(statePath, doneState);
|
|
526
|
+
(0, state_js_1.writeCheckpoint)(statePath, 'gswd/bootstrap', 'bootstrap-done');
|
|
527
|
+
// Auto decision summary (AUTO-06)
|
|
528
|
+
if (options.auto && allAutoDecisions.length > 0) {
|
|
529
|
+
console.log(renderAutoDecisionSummary(allAutoDecisions));
|
|
530
|
+
}
|
|
531
|
+
// Display dynamic file inventory (matches manual mode output)
|
|
532
|
+
const inventoryFiles = [
|
|
533
|
+
{ path: '.planning/IMAGINE.md', description: 'Product vision, target user, and direction' },
|
|
534
|
+
{ path: '.planning/DECISIONS.md', description: 'Frozen decisions, success metrics, risks' },
|
|
535
|
+
{ path: '.planning/SPEC.md', description: 'Functional requirements with acceptance criteria' },
|
|
536
|
+
{ path: '.planning/NFR.md', description: 'Non-functional requirements with thresholds' },
|
|
537
|
+
{ path: '.planning/JOURNEYS.md', description: 'User journeys with FR and integration linkages' },
|
|
538
|
+
{ path: '.planning/INTEGRATIONS.md', description: 'External integrations with status and fallbacks' },
|
|
539
|
+
{ path: '.planning/PROJECT.md', description: 'GSD project context (compiled)' },
|
|
540
|
+
{ path: '.planning/REQUIREMENTS.md', description: 'GSD requirements with traceability (compiled)' },
|
|
541
|
+
{ path: '.planning/ROADMAP.md', description: 'GSD roadmap with phased journeys (compiled)' },
|
|
542
|
+
{ path: '.planning/STATE.md', description: 'GSD project state and decisions (compiled)' },
|
|
543
|
+
{ path: '.planning/research/gswd/SUMMARY.md', description: 'GSD-format research bridge (compiled)' },
|
|
544
|
+
].filter(f => {
|
|
545
|
+
try {
|
|
546
|
+
fs.accessSync(path.join(process.cwd(), f.path));
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
console.log((0, render_js_1.renderFileInventory)(inventoryFiles));
|
|
554
|
+
console.log('\nNext up:\n /gsd:plan-phase 01\n\nTip:\n If context is getting crowded, run /clear first.');
|
|
555
|
+
return {
|
|
556
|
+
status: 'done',
|
|
557
|
+
stagesCompleted,
|
|
558
|
+
artifactsWritten: allArtifacts,
|
|
559
|
+
autoDecisions: allAutoDecisions,
|
|
560
|
+
interruptReasons: [],
|
|
561
|
+
autoFixCycles,
|
|
562
|
+
};
|
|
563
|
+
}
|