gsdd-cli 0.18.4 → 0.19.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/LICENSE +21 -21
- package/README.md +625 -607
- package/agents/DISTILLATION.md +421 -421
- package/agents/README.md +62 -62
- package/agents/approach-explorer.md +361 -361
- package/agents/debugger.md +82 -82
- package/agents/executor.md +394 -394
- package/agents/integration-checker.md +318 -318
- package/agents/mapper.md +103 -103
- package/agents/planner.md +313 -313
- package/agents/researcher.md +84 -84
- package/agents/roadmapper.md +296 -296
- package/agents/synthesizer.md +236 -236
- package/agents/verifier.md +337 -337
- package/bin/adapters/agents.mjs +34 -34
- package/bin/adapters/claude.mjs +191 -191
- package/bin/adapters/codex.mjs +85 -85
- package/bin/adapters/index.mjs +20 -20
- package/bin/adapters/opencode.mjs +278 -278
- package/bin/gsdd.mjs +123 -116
- package/bin/lib/cli-utils.mjs +28 -28
- package/bin/lib/evidence-contract.mjs +112 -112
- package/bin/lib/file-ops.mjs +209 -161
- package/bin/lib/health-truth.mjs +181 -178
- package/bin/lib/health.mjs +265 -235
- package/bin/lib/init-flow.mjs +252 -236
- package/bin/lib/init-prompts.mjs +248 -247
- package/bin/lib/init-runtime.mjs +225 -212
- package/bin/lib/init.mjs +17 -17
- package/bin/lib/lifecycle-preflight.mjs +360 -333
- package/bin/lib/lifecycle-state.mjs +351 -267
- package/bin/lib/manifest.mjs +116 -114
- package/bin/lib/models.mjs +411 -411
- package/bin/lib/phase.mjs +397 -378
- package/bin/lib/plan-constants.mjs +30 -30
- package/bin/lib/provenance.mjs +109 -106
- package/bin/lib/rendering.mjs +178 -130
- package/bin/lib/runtime-freshness.mjs +221 -219
- package/bin/lib/templates.mjs +225 -224
- package/bin/lib/workspace-root.mjs +132 -0
- package/distilled/DESIGN.md +2347 -2327
- package/distilled/EVIDENCE-INDEX.md +397 -394
- package/distilled/README.md +196 -193
- package/distilled/SKILL.md +86 -85
- package/distilled/templates/agents.block.md +21 -21
- package/distilled/templates/agents.md +6 -6
- package/distilled/templates/approach.md +232 -232
- package/distilled/templates/auth-matrix.md +78 -78
- package/distilled/templates/brownfield-change/CHANGE.md +99 -0
- package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
- package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
- package/distilled/templates/codebase/architecture.md +110 -110
- package/distilled/templates/codebase/concerns.md +95 -95
- package/distilled/templates/codebase/conventions.md +193 -193
- package/distilled/templates/codebase/stack.md +96 -96
- package/distilled/templates/delegates/approach-explorer.md +25 -25
- package/distilled/templates/delegates/mapper-arch.md +26 -26
- package/distilled/templates/delegates/mapper-concerns.md +27 -27
- package/distilled/templates/delegates/mapper-quality.md +28 -28
- package/distilled/templates/delegates/mapper-tech.md +25 -25
- package/distilled/templates/delegates/plan-checker.md +68 -68
- package/distilled/templates/delegates/researcher-architecture.md +30 -30
- package/distilled/templates/delegates/researcher-features.md +30 -30
- package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
- package/distilled/templates/delegates/researcher-stack.md +30 -30
- package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
- package/distilled/templates/research/architecture.md +57 -57
- package/distilled/templates/research/features.md +23 -23
- package/distilled/templates/research/pitfalls.md +46 -46
- package/distilled/templates/research/stack.md +45 -45
- package/distilled/templates/research/summary.md +67 -67
- package/distilled/templates/roadmap.md +74 -62
- package/distilled/templates/spec.md +110 -110
- package/distilled/workflows/audit-milestone.md +275 -271
- package/distilled/workflows/complete-milestone.md +336 -332
- package/distilled/workflows/execute.md +454 -449
- package/distilled/workflows/map-codebase.md +253 -253
- package/distilled/workflows/new-milestone.md +242 -238
- package/distilled/workflows/new-project.md +398 -398
- package/distilled/workflows/pause.md +160 -156
- package/distilled/workflows/plan-milestone-gaps.md +183 -183
- package/distilled/workflows/plan.md +451 -447
- package/distilled/workflows/progress.md +227 -223
- package/distilled/workflows/quick.md +351 -347
- package/distilled/workflows/resume.md +220 -212
- package/distilled/workflows/verify-work.md +260 -260
- package/distilled/workflows/verify.md +431 -429
- package/docs/BROWNFIELD-PROOF.md +95 -95
- package/docs/RUNTIME-SUPPORT.md +93 -75
- package/docs/USER-GUIDE.md +440 -399
- package/docs/VERIFICATION-DISCIPLINE.md +59 -59
- package/docs/claude/context-monitor.md +98 -98
- package/docs/proof/consumer-node-cli/README.md +37 -37
- package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
- package/docs/proof/consumer-node-cli/SPEC.md +17 -17
- package/docs/proof/consumer-node-cli/brief.md +9 -9
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
- package/package.json +62 -61
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
export const PLAN_CHECK_DIMENSIONS = [
|
|
2
|
-
'requirement_coverage',
|
|
3
|
-
'task_completeness',
|
|
4
|
-
'dependency_correctness',
|
|
5
|
-
'key_link_completeness',
|
|
6
|
-
'scope_sanity',
|
|
7
|
-
'must_have_quality',
|
|
8
|
-
'context_compliance',
|
|
9
|
-
'goal_achievement',
|
|
10
|
-
'approach_alignment',
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
export const MAX_CHECKER_CYCLES = 3;
|
|
14
|
-
|
|
15
|
-
export const CHECKER_STATUSES = ['passed', 'issues_found'];
|
|
16
|
-
|
|
17
|
-
export const CHECKER_JSON_SCHEMA = {
|
|
18
|
-
status: 'passed | issues_found',
|
|
19
|
-
summary: 'string',
|
|
20
|
-
issues: [
|
|
21
|
-
{
|
|
22
|
-
dimension: 'string',
|
|
23
|
-
severity: 'blocker | warning',
|
|
24
|
-
description: 'string',
|
|
25
|
-
plan: 'string',
|
|
26
|
-
task: 'string',
|
|
27
|
-
fix_hint: 'string',
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
};
|
|
1
|
+
export const PLAN_CHECK_DIMENSIONS = [
|
|
2
|
+
'requirement_coverage',
|
|
3
|
+
'task_completeness',
|
|
4
|
+
'dependency_correctness',
|
|
5
|
+
'key_link_completeness',
|
|
6
|
+
'scope_sanity',
|
|
7
|
+
'must_have_quality',
|
|
8
|
+
'context_compliance',
|
|
9
|
+
'goal_achievement',
|
|
10
|
+
'approach_alignment',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const MAX_CHECKER_CYCLES = 3;
|
|
14
|
+
|
|
15
|
+
export const CHECKER_STATUSES = ['passed', 'issues_found'];
|
|
16
|
+
|
|
17
|
+
export const CHECKER_JSON_SCHEMA = {
|
|
18
|
+
status: 'passed | issues_found',
|
|
19
|
+
summary: 'string',
|
|
20
|
+
issues: [
|
|
21
|
+
{
|
|
22
|
+
dimension: 'string',
|
|
23
|
+
severity: 'blocker | warning',
|
|
24
|
+
description: 'string',
|
|
25
|
+
plan: 'string',
|
|
26
|
+
task: 'string',
|
|
27
|
+
fix_hint: 'string',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
package/bin/lib/provenance.mjs
CHANGED
|
@@ -1,56 +1,59 @@
|
|
|
1
|
-
function normalizePrState(prState) {
|
|
2
|
-
if (!prState) return 'none';
|
|
3
|
-
return String(prState).trim().toLowerCase();
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
function normalizeCount(value) {
|
|
7
|
-
if (value === 'unknown' || value === null || value === undefined) return 'unknown';
|
|
8
|
-
const numeric = Number(value);
|
|
9
|
-
return Number.isFinite(numeric) ? numeric : 'unknown';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function normalizeCheckpointWorkflow(workflow) {
|
|
13
|
-
const normalized = String(workflow || 'generic').trim().toLowerCase();
|
|
14
|
-
return ['phase', 'quick', 'generic'].includes(normalized) ? normalized : 'generic';
|
|
15
|
-
}
|
|
16
|
-
|
|
1
|
+
function normalizePrState(prState) {
|
|
2
|
+
if (!prState) return 'none';
|
|
3
|
+
return String(prState).trim().toLowerCase();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function normalizeCount(value) {
|
|
7
|
+
if (value === 'unknown' || value === null || value === undefined) return 'unknown';
|
|
8
|
+
const numeric = Number(value);
|
|
9
|
+
return Number.isFinite(numeric) ? numeric : 'unknown';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeCheckpointWorkflow(workflow) {
|
|
13
|
+
const normalized = String(workflow || 'generic').trim().toLowerCase();
|
|
14
|
+
return ['phase', 'quick', 'generic'].includes(normalized) ? normalized : 'generic';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
17
|
export function classifyCheckpointRouting(workflow) {
|
|
18
18
|
const normalizedWorkflow = normalizeCheckpointWorkflow(workflow);
|
|
19
19
|
const progressBlocks = normalizedWorkflow === 'phase' || normalizedWorkflow === 'quick';
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
workflow: normalizedWorkflow,
|
|
23
|
-
routingClass: progressBlocks ? 'blocking' : 'informational',
|
|
24
|
-
progressBlocks,
|
|
25
|
-
resumeOwnsCleanup: true,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
workflow: normalizedWorkflow,
|
|
23
|
+
routingClass: progressBlocks ? 'blocking' : 'informational',
|
|
24
|
+
progressBlocks,
|
|
25
|
+
resumeOwnsCleanup: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
29
|
export function parseGitStatusShort(statusText = '') {
|
|
30
|
-
const lines = statusText
|
|
31
|
-
.replace(/\r\n/g, '\n')
|
|
32
|
-
.split('\n')
|
|
33
|
-
.map((line) => line.trimEnd())
|
|
34
|
-
.filter(Boolean);
|
|
35
|
-
|
|
36
|
-
const files = [];
|
|
37
|
-
for (const line of lines) {
|
|
38
|
-
const match = line.match(/^(.)(.)\s+(.+)$/);
|
|
39
|
-
if (!match) continue;
|
|
40
|
-
|
|
41
|
-
const indexStatus = match[1];
|
|
42
|
-
const worktreeStatus = match[2];
|
|
43
|
-
if (indexStatus === '!' && worktreeStatus === '!') continue;
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
30
|
+
const lines = statusText
|
|
31
|
+
.replace(/\r\n/g, '\n')
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map((line) => line.trimEnd())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
|
|
36
|
+
const files = [];
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const match = line.match(/^(.)(.)\s+(.+)$/);
|
|
39
|
+
if (!match) continue;
|
|
40
|
+
|
|
41
|
+
const indexStatus = match[1];
|
|
42
|
+
const worktreeStatus = match[2];
|
|
43
|
+
if (indexStatus === '!' && worktreeStatus === '!') continue;
|
|
44
|
+
|
|
45
|
+
const rawPath = match[3].replace(/\\/g, '/');
|
|
46
|
+
const renameMatch = rawPath.match(/^(.*?)\s+->\s+(.*?)$/);
|
|
47
|
+
const filePath = renameMatch ? renameMatch[2] : rawPath;
|
|
48
|
+
files.push({
|
|
49
|
+
path: filePath,
|
|
50
|
+
fromPath: renameMatch ? renameMatch[1] : null,
|
|
51
|
+
staged: indexStatus !== ' ' && indexStatus !== '?' && indexStatus !== '!',
|
|
52
|
+
unstaged: worktreeStatus !== ' ' && worktreeStatus !== '?' && worktreeStatus !== '!',
|
|
53
|
+
untracked: indexStatus === '?' || worktreeStatus === '?',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
54
57
|
return {
|
|
55
58
|
files,
|
|
56
59
|
stagedCount: files.filter((file) => file.staged).length,
|
|
@@ -206,55 +209,55 @@ export function buildProvenanceSnapshot({
|
|
|
206
209
|
});
|
|
207
210
|
|
|
208
211
|
const warnings = [];
|
|
209
|
-
|
|
210
|
-
if (status.dirty) {
|
|
211
|
-
warnings.push({
|
|
212
|
-
id: 'dirty_worktree',
|
|
213
|
-
severity: 'warning',
|
|
214
|
-
summary: 'Local worktree contains staged, unstaged, or untracked changes.',
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (commitsAheadOfMain !== 'unknown' && commitsAheadOfMain > 0) {
|
|
219
|
-
warnings.push({
|
|
220
|
-
id: 'ahead_of_main',
|
|
221
|
-
severity: 'warning',
|
|
222
|
-
summary: `${commitsAheadOfMain} commit(s) are ahead of main on the current branch.`,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (commitsAheadOfRemote !== 'unknown' && commitsAheadOfRemote > 0) {
|
|
227
|
-
warnings.push({
|
|
228
|
-
id: 'unpushed_commits',
|
|
229
|
-
severity: 'warning',
|
|
230
|
-
summary: `${commitsAheadOfRemote} commit(s) are ahead of the tracked remote branch.`,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (prState === 'none') {
|
|
235
|
-
warnings.push({
|
|
236
|
-
id: 'missing_pr',
|
|
237
|
-
severity: 'warning',
|
|
238
|
-
summary: 'No pull request is associated with the current branch.',
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (git.staleBranch) {
|
|
243
|
-
warnings.push({
|
|
244
|
-
id: 'stale_branch',
|
|
245
|
-
severity: 'warning',
|
|
246
|
-
summary: 'The current branch is stale or spent relative to the intended integration surface.',
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (git.mixedScope) {
|
|
251
|
-
warnings.push({
|
|
252
|
-
id: 'mixed_scope',
|
|
253
|
-
severity: 'warning',
|
|
254
|
-
summary: 'The current worktree appears to mix multiple write scopes or phases.',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
212
|
+
|
|
213
|
+
if (status.dirty) {
|
|
214
|
+
warnings.push({
|
|
215
|
+
id: 'dirty_worktree',
|
|
216
|
+
severity: 'warning',
|
|
217
|
+
summary: 'Local worktree contains staged, unstaged, or untracked changes.',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (commitsAheadOfMain !== 'unknown' && commitsAheadOfMain > 0) {
|
|
222
|
+
warnings.push({
|
|
223
|
+
id: 'ahead_of_main',
|
|
224
|
+
severity: 'warning',
|
|
225
|
+
summary: `${commitsAheadOfMain} commit(s) are ahead of main on the current branch.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (commitsAheadOfRemote !== 'unknown' && commitsAheadOfRemote > 0) {
|
|
230
|
+
warnings.push({
|
|
231
|
+
id: 'unpushed_commits',
|
|
232
|
+
severity: 'warning',
|
|
233
|
+
summary: `${commitsAheadOfRemote} commit(s) are ahead of the tracked remote branch.`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (prState === 'none') {
|
|
238
|
+
warnings.push({
|
|
239
|
+
id: 'missing_pr',
|
|
240
|
+
severity: 'warning',
|
|
241
|
+
summary: 'No pull request is associated with the current branch.',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (git.staleBranch) {
|
|
246
|
+
warnings.push({
|
|
247
|
+
id: 'stale_branch',
|
|
248
|
+
severity: 'warning',
|
|
249
|
+
summary: 'The current branch is stale or spent relative to the intended integration surface.',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (git.mixedScope) {
|
|
254
|
+
warnings.push({
|
|
255
|
+
id: 'mixed_scope',
|
|
256
|
+
severity: 'warning',
|
|
257
|
+
summary: 'The current worktree appears to mix multiple write scopes or phases.',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
258
261
|
if (git.materialCheckpointMismatch) {
|
|
259
262
|
warnings.push({
|
|
260
263
|
id: 'checkpoint_mismatch',
|
|
@@ -299,14 +302,14 @@ export function buildProvenanceSnapshot({
|
|
|
299
302
|
},
|
|
300
303
|
git: {
|
|
301
304
|
branch: git.branch || 'unknown',
|
|
302
|
-
prState,
|
|
303
|
-
commitsAheadOfMain,
|
|
304
|
-
commitsAheadOfRemote,
|
|
305
|
-
stagedCount: status.stagedCount,
|
|
306
|
-
unstagedCount: status.unstagedCount,
|
|
307
|
-
untrackedCount: status.untrackedCount,
|
|
308
|
-
dirty: status.dirty,
|
|
309
|
-
},
|
|
305
|
+
prState,
|
|
306
|
+
commitsAheadOfMain,
|
|
307
|
+
commitsAheadOfRemote,
|
|
308
|
+
stagedCount: status.stagedCount,
|
|
309
|
+
unstagedCount: status.unstagedCount,
|
|
310
|
+
untrackedCount: status.untrackedCount,
|
|
311
|
+
dirty: status.dirty,
|
|
312
|
+
},
|
|
310
313
|
integrationSurface: {
|
|
311
314
|
staleBranch: Boolean(git.staleBranch),
|
|
312
315
|
mixedScope: Boolean(git.mixedScope),
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -1,100 +1,128 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
|
-
import { dirname, join } from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = dirname(__filename);
|
|
7
|
-
const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
|
|
8
|
+
const HELPER_LIB_FILES = Object.freeze([
|
|
9
|
+
'cli-utils.mjs',
|
|
10
|
+
'evidence-contract.mjs',
|
|
11
|
+
'file-ops.mjs',
|
|
12
|
+
'lifecycle-preflight.mjs',
|
|
13
|
+
'lifecycle-state.mjs',
|
|
14
|
+
'phase.mjs',
|
|
15
|
+
'session-fingerprint.mjs',
|
|
16
|
+
'workspace-root.mjs',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function getWorkflowContent(workflowFile) {
|
|
20
|
+
const filePath = join(DISTILLED_DIR, 'workflows', workflowFile);
|
|
21
|
+
if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
|
|
22
|
+
return `<!-- Workflow file not found: ${workflowFile} -->\n`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getDelegateContent(delegateFile) {
|
|
26
|
+
const filePath = join(DISTILLED_DIR, 'templates', 'delegates', delegateFile);
|
|
27
|
+
if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
|
|
28
|
+
return `<!-- Delegate file not found: ${delegateFile} -->\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
function renderSkillContent(workflow) {
|
|
22
32
|
const workflowContent = getWorkflowContent(workflow.workflow);
|
|
23
33
|
return `---
|
|
24
34
|
name: ${workflow.name}
|
|
25
35
|
description: ${workflow.description}
|
|
26
|
-
context: fork
|
|
27
|
-
agent: ${workflow.agent}
|
|
28
|
-
---
|
|
29
|
-
|
|
36
|
+
context: fork
|
|
37
|
+
agent: ${workflow.agent}
|
|
38
|
+
---
|
|
39
|
+
|
|
30
40
|
${workflowContent}`;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
function renderPlanningCliLauncher(
|
|
34
|
-
|
|
35
|
-
throw new Error('renderPlanningCliLauncher requires packageVersion');
|
|
36
|
-
}
|
|
43
|
+
function renderPlanningCliLauncher() {
|
|
44
|
+
return `#!/usr/bin/env node
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
import { cmdFileOp } from './lib/file-ops.mjs';
|
|
47
|
+
import { cmdLifecyclePreflight } from './lib/lifecycle-preflight.mjs';
|
|
48
|
+
import { cmdPhaseStatus } from './lib/phase.mjs';
|
|
49
|
+
import { bootstrapHelperWorkspace, consumeWorkspaceRootArg, resolveWorkspaceContext } from './lib/workspace-root.mjs';
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
const COMMANDS = {
|
|
52
|
+
'file-op': cmdFileOp,
|
|
53
|
+
'lifecycle-preflight': cmdLifecyclePreflight,
|
|
54
|
+
'phase-status': cmdPhaseStatus,
|
|
55
|
+
};
|
|
41
56
|
|
|
42
|
-
|
|
57
|
+
function printHelp() {
|
|
58
|
+
console.log([
|
|
59
|
+
'Usage: node .planning/bin/gsdd.mjs [--workspace-root <path>] <command> [args]',
|
|
60
|
+
'',
|
|
61
|
+
'Local workflow helper commands:',
|
|
62
|
+
' file-op <copy|delete|regex-sub>',
|
|
63
|
+
' Run deterministic workspace-confined file operations',
|
|
64
|
+
' Example: node .planning/bin/gsdd.mjs file-op delete .planning/.continue-here.bak --missing ok',
|
|
65
|
+
' phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])',
|
|
66
|
+
' Example: node .planning/bin/gsdd.mjs phase-status 1 done',
|
|
67
|
+
' lifecycle-preflight <surface> [phase]',
|
|
68
|
+
' Inspect lifecycle gate results for a workflow surface',
|
|
69
|
+
' Example: node .planning/bin/gsdd.mjs lifecycle-preflight verify 1 --expects-mutation phase-status',
|
|
70
|
+
'',
|
|
71
|
+
'Advanced option:',
|
|
72
|
+
' --workspace-root <path> Override workspace root discovery before or after the subcommand',
|
|
73
|
+
].join('\\n'));
|
|
74
|
+
}
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
function applyWorkspaceRootOverride(workspaceRootArg) {
|
|
77
|
+
if (!workspaceRootArg) {
|
|
78
|
+
bootstrapHelperWorkspace(import.meta.url);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
47
81
|
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
console.error(
|
|
82
|
+
const context = resolveWorkspaceContext(['--workspace-root', workspaceRootArg]);
|
|
83
|
+
if (context.invalid) {
|
|
84
|
+
console.error(context.error);
|
|
51
85
|
process.exitCode = 1;
|
|
52
|
-
return;
|
|
86
|
+
return false;
|
|
53
87
|
}
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
process.env.GSDD_WORKSPACE_ROOT = context.workspaceRoot;
|
|
90
|
+
try {
|
|
91
|
+
process.chdir(context.workspaceRoot);
|
|
92
|
+
} catch {
|
|
93
|
+
// best-effort: command handlers also resolve from GSDD_WORKSPACE_ROOT
|
|
58
94
|
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
async function main() {
|
|
99
|
+
const parsed = consumeWorkspaceRootArg(process.argv.slice(2));
|
|
100
|
+
if (parsed.invalid) {
|
|
101
|
+
console.error('Usage: --workspace-root <path>');
|
|
61
102
|
process.exitCode = 1;
|
|
103
|
+
return;
|
|
62
104
|
}
|
|
63
|
-
}
|
|
64
105
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return spawnSync('powershell.exe', ['-NoProfile', '-Command', powershellScript], {
|
|
74
|
-
stdio: 'inherit',
|
|
75
|
-
env: {
|
|
76
|
-
...env,
|
|
77
|
-
GSDD_LAUNCH_ARGS: JSON.stringify(args),
|
|
78
|
-
},
|
|
79
|
-
});
|
|
106
|
+
if (!applyWorkspaceRootOverride(parsed.workspaceRootArg)) return;
|
|
107
|
+
|
|
108
|
+
const [command, ...args] = parsed.args;
|
|
109
|
+
|
|
110
|
+
if (!command || command === 'help' || command === '--help') {
|
|
111
|
+
printHelp();
|
|
112
|
+
return;
|
|
80
113
|
}
|
|
81
114
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
const handler = COMMANDS[command];
|
|
116
|
+
if (!handler) {
|
|
117
|
+
printHelp();
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
87
121
|
|
|
88
|
-
|
|
89
|
-
const localResult = spawnSync(process.execPath, [env.GSDD_CLI_PATH, ...args], {
|
|
90
|
-
stdio: 'inherit',
|
|
91
|
-
env,
|
|
92
|
-
});
|
|
93
|
-
forwardResult(localResult, 'Failed to run the local GSDD CLI path from GSDD_CLI_PATH.');
|
|
94
|
-
} else {
|
|
95
|
-
const packagedResult = runPackagedCli();
|
|
96
|
-
forwardResult(packagedResult, \`Failed to run \${packageSpec} via npm exec.\`);
|
|
122
|
+
await handler(...args);
|
|
97
123
|
}
|
|
124
|
+
|
|
125
|
+
await main();
|
|
98
126
|
`;
|
|
99
127
|
}
|
|
100
128
|
|
|
@@ -113,11 +141,23 @@ node "%~dp0gsdd.mjs" %*
|
|
|
113
141
|
`;
|
|
114
142
|
}
|
|
115
143
|
|
|
116
|
-
function
|
|
144
|
+
function renderPlanningCliPowerShellShim() {
|
|
145
|
+
return `#!/usr/bin/env pwsh
|
|
146
|
+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
147
|
+
node (Join-Path $scriptDir 'gsdd.mjs') @args
|
|
148
|
+
exit $LASTEXITCODE
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readHelperLibContent(fileName) {
|
|
153
|
+
return readFileSync(join(__dirname, fileName), 'utf-8');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildPlanningCliHelperEntries() {
|
|
117
157
|
return [
|
|
118
158
|
{
|
|
119
159
|
relativePath: 'bin/gsdd.mjs',
|
|
120
|
-
content: renderPlanningCliLauncher(
|
|
160
|
+
content: renderPlanningCliLauncher(),
|
|
121
161
|
},
|
|
122
162
|
{
|
|
123
163
|
relativePath: 'bin/gsdd',
|
|
@@ -127,6 +167,14 @@ function buildPlanningCliHelperEntries({ packageName = 'gsdd-cli', packageVersio
|
|
|
127
167
|
relativePath: 'bin/gsdd.cmd',
|
|
128
168
|
content: renderPlanningCliCmdShim(),
|
|
129
169
|
},
|
|
170
|
+
{
|
|
171
|
+
relativePath: 'bin/gsdd.ps1',
|
|
172
|
+
content: renderPlanningCliPowerShellShim(),
|
|
173
|
+
},
|
|
174
|
+
...HELPER_LIB_FILES.map((fileName) => ({
|
|
175
|
+
relativePath: `bin/lib/${fileName}`,
|
|
176
|
+
content: readHelperLibContent(fileName),
|
|
177
|
+
})),
|
|
130
178
|
];
|
|
131
179
|
}
|
|
132
180
|
|
|
@@ -135,62 +183,62 @@ function buildPortableSkillEntries(workflows) {
|
|
|
135
183
|
relativePath: `.agents/skills/${workflow.name}/SKILL.md`,
|
|
136
184
|
content: renderSkillContent(workflow),
|
|
137
185
|
}));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function renderOpenCodeCommandContent(workflow) {
|
|
141
|
-
const workflowContent = getWorkflowContent(workflow.workflow);
|
|
142
|
-
return `---
|
|
143
|
-
description: ${workflow.description}
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
${workflowContent}`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function renderAgentsBoundedBlock() {
|
|
150
|
-
const blockPath = join(DISTILLED_DIR, 'templates', 'agents.block.md');
|
|
151
|
-
if (existsSync(blockPath)) return readFileSync(blockPath, 'utf-8').trim();
|
|
152
|
-
return '## GSDD Governance (Generated)\n\n- Framework: GSDD\n- Planning: .planning/\n- Workflows: .agents/skills/gsdd-*/SKILL.md';
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function renderAgentsFileContent() {
|
|
156
|
-
const templatePath = join(DISTILLED_DIR, 'templates', 'agents.md');
|
|
157
|
-
if (existsSync(templatePath)) {
|
|
158
|
-
const template = readFileSync(templatePath, 'utf-8');
|
|
159
|
-
return template.replace('{{GSDD_BLOCK}}', renderAgentsBoundedBlock()).trimEnd() + '\n';
|
|
160
|
-
}
|
|
161
|
-
const block = renderAgentsBoundedBlock();
|
|
162
|
-
return `# AGENTS.md - GSDD Governance\n\n<!-- BEGIN GSDD -->\n${block}\n<!-- END GSDD -->\n`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function escapeRegExp(value) {
|
|
166
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function upsertBoundedBlock(existing, blockContent) {
|
|
170
|
-
const begin = '<!-- BEGIN GSDD -->';
|
|
171
|
-
const end = '<!-- END GSDD -->';
|
|
172
|
-
const bounded = `${begin}\n${blockContent.trimEnd()}\n${end}`;
|
|
173
|
-
|
|
174
|
-
const re = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}`, 'm');
|
|
175
|
-
if (re.test(existing)) return existing.replace(re, bounded);
|
|
176
|
-
|
|
177
|
-
const lines = existing.split(/\r?\n/);
|
|
178
|
-
const h1Idx = lines.findIndex((line) => /^#\s+/.test(line));
|
|
179
|
-
if (h1Idx !== -1) {
|
|
180
|
-
const insertAt = h1Idx + 1;
|
|
181
|
-
const out = [
|
|
182
|
-
...lines.slice(0, insertAt),
|
|
183
|
-
'',
|
|
184
|
-
bounded,
|
|
185
|
-
'',
|
|
186
|
-
...lines.slice(insertAt),
|
|
187
|
-
];
|
|
188
|
-
return out.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return `${bounded}\n\n${existing}`.replace(/\n{3,}/g, '\n\n');
|
|
192
|
-
}
|
|
193
|
-
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function renderOpenCodeCommandContent(workflow) {
|
|
189
|
+
const workflowContent = getWorkflowContent(workflow.workflow);
|
|
190
|
+
return `---
|
|
191
|
+
description: ${workflow.description}
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
${workflowContent}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function renderAgentsBoundedBlock() {
|
|
198
|
+
const blockPath = join(DISTILLED_DIR, 'templates', 'agents.block.md');
|
|
199
|
+
if (existsSync(blockPath)) return readFileSync(blockPath, 'utf-8').trim();
|
|
200
|
+
return '## GSDD Governance (Generated)\n\n- Framework: GSDD\n- Planning: .planning/\n- Workflows: .agents/skills/gsdd-*/SKILL.md';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderAgentsFileContent() {
|
|
204
|
+
const templatePath = join(DISTILLED_DIR, 'templates', 'agents.md');
|
|
205
|
+
if (existsSync(templatePath)) {
|
|
206
|
+
const template = readFileSync(templatePath, 'utf-8');
|
|
207
|
+
return template.replace('{{GSDD_BLOCK}}', renderAgentsBoundedBlock()).trimEnd() + '\n';
|
|
208
|
+
}
|
|
209
|
+
const block = renderAgentsBoundedBlock();
|
|
210
|
+
return `# AGENTS.md - GSDD Governance\n\n<!-- BEGIN GSDD -->\n${block}\n<!-- END GSDD -->\n`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function escapeRegExp(value) {
|
|
214
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function upsertBoundedBlock(existing, blockContent) {
|
|
218
|
+
const begin = '<!-- BEGIN GSDD -->';
|
|
219
|
+
const end = '<!-- END GSDD -->';
|
|
220
|
+
const bounded = `${begin}\n${blockContent.trimEnd()}\n${end}`;
|
|
221
|
+
|
|
222
|
+
const re = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}`, 'm');
|
|
223
|
+
if (re.test(existing)) return existing.replace(re, bounded);
|
|
224
|
+
|
|
225
|
+
const lines = existing.split(/\r?\n/);
|
|
226
|
+
const h1Idx = lines.findIndex((line) => /^#\s+/.test(line));
|
|
227
|
+
if (h1Idx !== -1) {
|
|
228
|
+
const insertAt = h1Idx + 1;
|
|
229
|
+
const out = [
|
|
230
|
+
...lines.slice(0, insertAt),
|
|
231
|
+
'',
|
|
232
|
+
bounded,
|
|
233
|
+
'',
|
|
234
|
+
...lines.slice(insertAt),
|
|
235
|
+
];
|
|
236
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return `${bounded}\n\n${existing}`.replace(/\n{3,}/g, '\n\n');
|
|
240
|
+
}
|
|
241
|
+
|
|
194
242
|
export {
|
|
195
243
|
buildPlanningCliHelperEntries,
|
|
196
244
|
buildPortableSkillEntries,
|