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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSWD State Module — STATE.json CRUD with atomic writes
|
|
3
|
+
*
|
|
4
|
+
* All file writes use the atomic pattern: write to .tmp -> verify -> rename.
|
|
5
|
+
* STATE.json is updated only AFTER artifact writes complete.
|
|
6
|
+
*
|
|
7
|
+
* Schema: GSWD_SPEC.md Section 5.1
|
|
8
|
+
*/
|
|
9
|
+
export interface StageStatus {
|
|
10
|
+
imagine: 'not_started' | 'in_progress' | 'done';
|
|
11
|
+
specify: 'not_started' | 'in_progress' | 'done';
|
|
12
|
+
audit: 'not_started' | 'in_progress' | 'pass' | 'fail';
|
|
13
|
+
compile: 'not_started' | 'in_progress' | 'done';
|
|
14
|
+
}
|
|
15
|
+
export interface Checkpoint {
|
|
16
|
+
workflow: string;
|
|
17
|
+
checkpoint_id: string;
|
|
18
|
+
timestamp: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PaidIntegration {
|
|
21
|
+
integration_id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
monthly_cost_usd: number;
|
|
24
|
+
status: 'approved' | 'rejected';
|
|
25
|
+
approved_at: string;
|
|
26
|
+
}
|
|
27
|
+
export interface IdRange {
|
|
28
|
+
agent: string;
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
}
|
|
32
|
+
export interface IdRegistry {
|
|
33
|
+
allocated_ranges: Record<string, IdRange[]>;
|
|
34
|
+
}
|
|
35
|
+
export interface ManualModeState {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
reruns: {
|
|
38
|
+
imagine: number;
|
|
39
|
+
specify: number;
|
|
40
|
+
audit: number;
|
|
41
|
+
compile: number;
|
|
42
|
+
};
|
|
43
|
+
last_checkpoint_stage: string | null;
|
|
44
|
+
interrupt_type: 'mid_stage' | 'between_stages' | null;
|
|
45
|
+
}
|
|
46
|
+
export interface GswdState {
|
|
47
|
+
version: number;
|
|
48
|
+
project_slug: string;
|
|
49
|
+
created_at: string;
|
|
50
|
+
updated_at: string;
|
|
51
|
+
stage: 'init' | 'imagine' | 'specify' | 'audit' | 'compile' | 'done';
|
|
52
|
+
stage_status: StageStatus;
|
|
53
|
+
last_checkpoint: Checkpoint | null;
|
|
54
|
+
first_run_complete?: boolean;
|
|
55
|
+
auto: {
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
policy: 'strict' | 'balanced' | 'aggressive';
|
|
58
|
+
interrupt_reasons: string[];
|
|
59
|
+
decisions?: Array<{
|
|
60
|
+
type: string;
|
|
61
|
+
chosen: string;
|
|
62
|
+
rationale: string;
|
|
63
|
+
score?: number;
|
|
64
|
+
recorded_at: string;
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
approvals: {
|
|
68
|
+
auth_model: string;
|
|
69
|
+
data_store: string;
|
|
70
|
+
paid_integrations: PaidIntegration[];
|
|
71
|
+
};
|
|
72
|
+
id_registry?: IdRegistry;
|
|
73
|
+
manual_mode?: ManualModeState;
|
|
74
|
+
}
|
|
75
|
+
export declare const STAGE_ORDER: readonly ["imagine", "specify", "audit", "compile"];
|
|
76
|
+
export type StageName = typeof STAGE_ORDER[number];
|
|
77
|
+
/**
|
|
78
|
+
* Atomically write a file: write to .tmp, verify, rename.
|
|
79
|
+
* Cleans up .tmp on any failure.
|
|
80
|
+
*/
|
|
81
|
+
export declare function safeWriteFile(filePath: string, content: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Atomically write JSON: pretty-print, atomic write, then verify round-trip.
|
|
84
|
+
*/
|
|
85
|
+
export declare function safeWriteJson(filePath: string, data: unknown): void;
|
|
86
|
+
/**
|
|
87
|
+
* Create a default STATE.json object matching GSWD_SPEC Section 5.1.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createDefaultState(projectSlug: string): GswdState;
|
|
90
|
+
/**
|
|
91
|
+
* Read STATE.json. Returns null if file doesn't exist or is invalid JSON.
|
|
92
|
+
*/
|
|
93
|
+
export declare function readState(statePath: string): GswdState | null;
|
|
94
|
+
/**
|
|
95
|
+
* Write STATE.json atomically. Updates updated_at timestamp.
|
|
96
|
+
*/
|
|
97
|
+
export declare function writeState(statePath: string, state: GswdState): void;
|
|
98
|
+
/**
|
|
99
|
+
* Idempotent init: create STATE.json only if missing or corrupt.
|
|
100
|
+
* If valid state exists, returns it unchanged (no timestamp update).
|
|
101
|
+
*/
|
|
102
|
+
export declare function initState(stateDir: string, projectSlug: string): GswdState;
|
|
103
|
+
/**
|
|
104
|
+
* Update a specific stage status. Read-modify-write pattern.
|
|
105
|
+
*/
|
|
106
|
+
export declare function updateStageStatus(statePath: string, stage: keyof StageStatus, status: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Write a checkpoint to STATE.json.
|
|
109
|
+
*/
|
|
110
|
+
export declare function writeCheckpoint(statePath: string, workflow: string, checkpointId: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Allocate an ID range for an agent. Ranges are non-overlapping per ID type.
|
|
113
|
+
*/
|
|
114
|
+
export declare function allocateIdRange(statePath: string, idType: string, agentName: string, rangeSize?: number): {
|
|
115
|
+
start: number;
|
|
116
|
+
end: number;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Get allocated ID ranges, optionally filtered by type.
|
|
120
|
+
*/
|
|
121
|
+
export declare function getIdRanges(statePath: string, idType?: string): Record<string, IdRange[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Reset all allocated ID ranges.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resetIdRegistry(statePath: string): void;
|
|
126
|
+
/**
|
|
127
|
+
* Mark all stages downstream of the re-run stage as 'not_started'.
|
|
128
|
+
* Also increments the re-run counter for the given stage.
|
|
129
|
+
* Performs a single atomic STATE.json write.
|
|
130
|
+
*/
|
|
131
|
+
export declare function cascadeInvalidate(statePath: string, fromStage: StageName): void;
|
|
132
|
+
/**
|
|
133
|
+
* Update the manual mode checkpoint tracking in STATE.json.
|
|
134
|
+
* Called when entering or completing a stage boundary checkpoint.
|
|
135
|
+
*/
|
|
136
|
+
export declare function updateManualCheckpoint(statePath: string, stage: StageName | null, interruptType: 'mid_stage' | 'between_stages' | null): void;
|
|
137
|
+
/**
|
|
138
|
+
* Set manual mode enabled/disabled in STATE.json.
|
|
139
|
+
*/
|
|
140
|
+
export declare function setManualMode(statePath: string, enabled: boolean): void;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GSWD State Module — STATE.json CRUD with atomic writes
|
|
4
|
+
*
|
|
5
|
+
* All file writes use the atomic pattern: write to .tmp -> verify -> rename.
|
|
6
|
+
* STATE.json is updated only AFTER artifact writes complete.
|
|
7
|
+
*
|
|
8
|
+
* Schema: GSWD_SPEC.md Section 5.1
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.STAGE_ORDER = void 0;
|
|
45
|
+
exports.safeWriteFile = safeWriteFile;
|
|
46
|
+
exports.safeWriteJson = safeWriteJson;
|
|
47
|
+
exports.createDefaultState = createDefaultState;
|
|
48
|
+
exports.readState = readState;
|
|
49
|
+
exports.writeState = writeState;
|
|
50
|
+
exports.initState = initState;
|
|
51
|
+
exports.updateStageStatus = updateStageStatus;
|
|
52
|
+
exports.writeCheckpoint = writeCheckpoint;
|
|
53
|
+
exports.allocateIdRange = allocateIdRange;
|
|
54
|
+
exports.getIdRanges = getIdRanges;
|
|
55
|
+
exports.resetIdRegistry = resetIdRegistry;
|
|
56
|
+
exports.cascadeInvalidate = cascadeInvalidate;
|
|
57
|
+
exports.updateManualCheckpoint = updateManualCheckpoint;
|
|
58
|
+
exports.setManualMode = setManualMode;
|
|
59
|
+
const fs = __importStar(require("node:fs"));
|
|
60
|
+
const path = __importStar(require("node:path"));
|
|
61
|
+
// ─── Stage Order ─────────────────────────────────────────────────────────────
|
|
62
|
+
exports.STAGE_ORDER = ['imagine', 'specify', 'audit', 'compile'];
|
|
63
|
+
// ─── Atomic File I/O ─────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Atomically write a file: write to .tmp, verify, rename.
|
|
66
|
+
* Cleans up .tmp on any failure.
|
|
67
|
+
*/
|
|
68
|
+
function safeWriteFile(filePath, content) {
|
|
69
|
+
const tmpPath = filePath + '.tmp';
|
|
70
|
+
const dir = path.dirname(filePath);
|
|
71
|
+
// Ensure parent directory exists
|
|
72
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
73
|
+
try {
|
|
74
|
+
// Write to temp file
|
|
75
|
+
fs.writeFileSync(tmpPath, content, 'utf-8');
|
|
76
|
+
// Verify temp file was written correctly
|
|
77
|
+
const written = fs.readFileSync(tmpPath, 'utf-8');
|
|
78
|
+
if (written !== content) {
|
|
79
|
+
throw new Error(`Write verification failed for ${filePath}: content mismatch`);
|
|
80
|
+
}
|
|
81
|
+
// Atomic rename (atomic on Unix/macOS)
|
|
82
|
+
fs.renameSync(tmpPath, filePath);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// Clean up .tmp on any failure
|
|
86
|
+
try {
|
|
87
|
+
if (fs.existsSync(tmpPath)) {
|
|
88
|
+
fs.unlinkSync(tmpPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Ignore cleanup errors
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Atomically write JSON: pretty-print, atomic write, then verify round-trip.
|
|
99
|
+
*/
|
|
100
|
+
function safeWriteJson(filePath, data) {
|
|
101
|
+
const content = JSON.stringify(data, null, 2) + '\n';
|
|
102
|
+
safeWriteFile(filePath, content);
|
|
103
|
+
// Post-rename verification: re-read and parse
|
|
104
|
+
const verify = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
105
|
+
if (JSON.stringify(verify) !== JSON.stringify(data)) {
|
|
106
|
+
throw new Error(`JSON round-trip verification failed for ${filePath}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ─── State CRUD ──────────────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Create a default STATE.json object matching GSWD_SPEC Section 5.1.
|
|
112
|
+
*/
|
|
113
|
+
function createDefaultState(projectSlug) {
|
|
114
|
+
const now = new Date().toISOString();
|
|
115
|
+
return {
|
|
116
|
+
version: 1,
|
|
117
|
+
project_slug: projectSlug,
|
|
118
|
+
created_at: now,
|
|
119
|
+
updated_at: now,
|
|
120
|
+
stage: 'init',
|
|
121
|
+
stage_status: {
|
|
122
|
+
imagine: 'not_started',
|
|
123
|
+
specify: 'not_started',
|
|
124
|
+
audit: 'not_started',
|
|
125
|
+
compile: 'not_started',
|
|
126
|
+
},
|
|
127
|
+
last_checkpoint: null,
|
|
128
|
+
first_run_complete: false,
|
|
129
|
+
auto: {
|
|
130
|
+
enabled: false,
|
|
131
|
+
policy: 'balanced',
|
|
132
|
+
interrupt_reasons: [],
|
|
133
|
+
decisions: [],
|
|
134
|
+
},
|
|
135
|
+
approvals: {
|
|
136
|
+
auth_model: 'unapproved',
|
|
137
|
+
data_store: 'unapproved',
|
|
138
|
+
paid_integrations: [],
|
|
139
|
+
},
|
|
140
|
+
manual_mode: {
|
|
141
|
+
enabled: false,
|
|
142
|
+
reruns: { imagine: 0, specify: 0, audit: 0, compile: 0 },
|
|
143
|
+
last_checkpoint_stage: null,
|
|
144
|
+
interrupt_type: null,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read STATE.json. Returns null if file doesn't exist or is invalid JSON.
|
|
150
|
+
*/
|
|
151
|
+
function readState(statePath) {
|
|
152
|
+
try {
|
|
153
|
+
const content = fs.readFileSync(statePath, 'utf-8');
|
|
154
|
+
const parsed = JSON.parse(content);
|
|
155
|
+
// Basic validation: check required top-level fields
|
|
156
|
+
if (typeof parsed.version !== 'number' || typeof parsed.project_slug !== 'string') {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return parsed;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Write STATE.json atomically. Updates updated_at timestamp.
|
|
167
|
+
*/
|
|
168
|
+
function writeState(statePath, state) {
|
|
169
|
+
state.updated_at = new Date().toISOString();
|
|
170
|
+
safeWriteJson(statePath, state);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Idempotent init: create STATE.json only if missing or corrupt.
|
|
174
|
+
* If valid state exists, returns it unchanged (no timestamp update).
|
|
175
|
+
*/
|
|
176
|
+
function initState(stateDir, projectSlug) {
|
|
177
|
+
const statePath = path.join(stateDir, 'STATE.json');
|
|
178
|
+
// Try reading existing state
|
|
179
|
+
const existing = readState(statePath);
|
|
180
|
+
if (existing !== null) {
|
|
181
|
+
return existing;
|
|
182
|
+
}
|
|
183
|
+
// Create new state
|
|
184
|
+
const state = createDefaultState(projectSlug);
|
|
185
|
+
safeWriteJson(statePath, state);
|
|
186
|
+
return state;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Update a specific stage status. Read-modify-write pattern.
|
|
190
|
+
*/
|
|
191
|
+
function updateStageStatus(statePath, stage, status) {
|
|
192
|
+
const state = readState(statePath);
|
|
193
|
+
if (!state) {
|
|
194
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
195
|
+
}
|
|
196
|
+
state.stage_status[stage] = status;
|
|
197
|
+
writeState(statePath, state);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Write a checkpoint to STATE.json.
|
|
201
|
+
*/
|
|
202
|
+
function writeCheckpoint(statePath, workflow, checkpointId) {
|
|
203
|
+
const state = readState(statePath);
|
|
204
|
+
if (!state) {
|
|
205
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
206
|
+
}
|
|
207
|
+
state.last_checkpoint = {
|
|
208
|
+
workflow,
|
|
209
|
+
checkpoint_id: checkpointId,
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
};
|
|
212
|
+
writeState(statePath, state);
|
|
213
|
+
}
|
|
214
|
+
// ─── ID Registry ─────────────────────────────────────────────────────────────
|
|
215
|
+
/**
|
|
216
|
+
* Allocate an ID range for an agent. Ranges are non-overlapping per ID type.
|
|
217
|
+
*/
|
|
218
|
+
function allocateIdRange(statePath, idType, agentName, rangeSize = 50) {
|
|
219
|
+
const state = readState(statePath);
|
|
220
|
+
if (!state) {
|
|
221
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
222
|
+
}
|
|
223
|
+
if (!state.id_registry) {
|
|
224
|
+
state.id_registry = { allocated_ranges: {} };
|
|
225
|
+
}
|
|
226
|
+
if (!state.id_registry.allocated_ranges[idType]) {
|
|
227
|
+
state.id_registry.allocated_ranges[idType] = [];
|
|
228
|
+
}
|
|
229
|
+
const ranges = state.id_registry.allocated_ranges[idType];
|
|
230
|
+
const lastEnd = ranges.length > 0
|
|
231
|
+
? Math.max(...ranges.map(r => r.end))
|
|
232
|
+
: 0;
|
|
233
|
+
const newRange = {
|
|
234
|
+
agent: agentName,
|
|
235
|
+
start: lastEnd + 1,
|
|
236
|
+
end: lastEnd + rangeSize,
|
|
237
|
+
};
|
|
238
|
+
ranges.push(newRange);
|
|
239
|
+
writeState(statePath, state);
|
|
240
|
+
return { start: newRange.start, end: newRange.end };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get allocated ID ranges, optionally filtered by type.
|
|
244
|
+
*/
|
|
245
|
+
function getIdRanges(statePath, idType) {
|
|
246
|
+
const state = readState(statePath);
|
|
247
|
+
if (!state || !state.id_registry) {
|
|
248
|
+
return {};
|
|
249
|
+
}
|
|
250
|
+
if (idType) {
|
|
251
|
+
const ranges = state.id_registry.allocated_ranges[idType];
|
|
252
|
+
return ranges ? { [idType]: ranges } : {};
|
|
253
|
+
}
|
|
254
|
+
return state.id_registry.allocated_ranges;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Reset all allocated ID ranges.
|
|
258
|
+
*/
|
|
259
|
+
function resetIdRegistry(statePath) {
|
|
260
|
+
const state = readState(statePath);
|
|
261
|
+
if (!state) {
|
|
262
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
263
|
+
}
|
|
264
|
+
state.id_registry = { allocated_ranges: {} };
|
|
265
|
+
writeState(statePath, state);
|
|
266
|
+
}
|
|
267
|
+
// ─── Manual Mode ──────────────────────────────────────────────────────────────
|
|
268
|
+
/**
|
|
269
|
+
* Mark all stages downstream of the re-run stage as 'not_started'.
|
|
270
|
+
* Also increments the re-run counter for the given stage.
|
|
271
|
+
* Performs a single atomic STATE.json write.
|
|
272
|
+
*/
|
|
273
|
+
function cascadeInvalidate(statePath, fromStage) {
|
|
274
|
+
const state = readState(statePath);
|
|
275
|
+
if (!state) {
|
|
276
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
277
|
+
}
|
|
278
|
+
const idx = exports.STAGE_ORDER.indexOf(fromStage);
|
|
279
|
+
if (idx === -1)
|
|
280
|
+
return;
|
|
281
|
+
// Invalidate all downstream stages
|
|
282
|
+
for (let i = idx + 1; i < exports.STAGE_ORDER.length; i++) {
|
|
283
|
+
state.stage_status[exports.STAGE_ORDER[i]] = 'not_started';
|
|
284
|
+
}
|
|
285
|
+
// Ensure manual_mode exists
|
|
286
|
+
if (!state.manual_mode) {
|
|
287
|
+
state.manual_mode = {
|
|
288
|
+
enabled: false,
|
|
289
|
+
reruns: { imagine: 0, specify: 0, audit: 0, compile: 0 },
|
|
290
|
+
last_checkpoint_stage: null,
|
|
291
|
+
interrupt_type: null,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
// Increment re-run counter for this stage
|
|
295
|
+
state.manual_mode.reruns[fromStage] = (state.manual_mode.reruns[fromStage] || 0) + 1;
|
|
296
|
+
// Single atomic write (re-run count + cascade in one operation)
|
|
297
|
+
writeState(statePath, state);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Update the manual mode checkpoint tracking in STATE.json.
|
|
301
|
+
* Called when entering or completing a stage boundary checkpoint.
|
|
302
|
+
*/
|
|
303
|
+
function updateManualCheckpoint(statePath, stage, interruptType) {
|
|
304
|
+
const state = readState(statePath);
|
|
305
|
+
if (!state) {
|
|
306
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
307
|
+
}
|
|
308
|
+
if (!state.manual_mode) {
|
|
309
|
+
state.manual_mode = {
|
|
310
|
+
enabled: false,
|
|
311
|
+
reruns: { imagine: 0, specify: 0, audit: 0, compile: 0 },
|
|
312
|
+
last_checkpoint_stage: null,
|
|
313
|
+
interrupt_type: null,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
state.manual_mode.last_checkpoint_stage = stage;
|
|
317
|
+
state.manual_mode.interrupt_type = interruptType;
|
|
318
|
+
writeState(statePath, state);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Set manual mode enabled/disabled in STATE.json.
|
|
322
|
+
*/
|
|
323
|
+
function setManualMode(statePath, enabled) {
|
|
324
|
+
const state = readState(statePath);
|
|
325
|
+
if (!state) {
|
|
326
|
+
throw new Error(`Cannot read STATE.json at ${statePath}`);
|
|
327
|
+
}
|
|
328
|
+
if (!state.manual_mode) {
|
|
329
|
+
state.manual_mode = {
|
|
330
|
+
enabled,
|
|
331
|
+
reruns: { imagine: 0, specify: 0, audit: 0, compile: 0 },
|
|
332
|
+
last_checkpoint_stage: null,
|
|
333
|
+
interrupt_type: null,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
state.manual_mode.enabled = enabled;
|
|
338
|
+
}
|
|
339
|
+
writeState(statePath, state);
|
|
340
|
+
}
|