@opengsd/gsd-core 1.6.1 → 1.7.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +711 -0
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-code-fixer.md +2 -0
- package/agents/gsd-code-reviewer.md +2 -0
- package/agents/gsd-codebase-mapper.md +2 -0
- package/agents/gsd-debugger.md +2 -0
- package/agents/gsd-doc-writer.md +2 -0
- package/agents/gsd-eval-auditor.md +2 -0
- package/agents/gsd-executor.md +9 -6
- package/agents/gsd-integration-checker.md +2 -0
- package/agents/gsd-nyquist-auditor.md +2 -0
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-plan-checker.md +2 -0
- package/agents/gsd-planner.md +2 -0
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-roadmapper.md +2 -0
- package/agents/gsd-security-auditor.md +2 -0
- package/agents/gsd-ui-auditor.md +2 -0
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +2 -0
- package/agents/gsd-verifier.md +5 -2
- package/bin/gsd-mcp-server.js +31 -0
- package/bin/install.js +411 -1146
- package/commands/gsd/review.md +6 -0
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +134 -8
- package/gsd-core/bin/lib/adapter-declarative.cjs +35 -0
- package/gsd-core/bin/lib/adapter-imperative.cjs +52 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +231 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +7 -7
- package/gsd-core/bin/lib/capability-loader.cjs +45 -9
- package/gsd-core/bin/lib/capability-lock.cjs +2 -2
- package/gsd-core/bin/lib/capability-registry.cjs +891 -82
- package/gsd-core/bin/lib/capability-source.cjs +26 -11
- package/gsd-core/bin/lib/capability-validator.cjs +222 -2
- package/gsd-core/bin/lib/cli-skew-check.cjs +44 -0
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/commands.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +27 -0
- package/gsd-core/bin/lib/embedding-adapter.cjs +27 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +70 -0
- package/gsd-core/bin/lib/frontmatter.cjs +53 -6
- package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
- package/gsd-core/bin/lib/hook-bus.cjs +81 -0
- package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
- package/gsd-core/bin/lib/host-integration.cjs +469 -0
- package/gsd-core/bin/lib/init.cjs +35 -7
- package/gsd-core/bin/lib/install-engine.cjs +755 -0
- package/gsd-core/bin/lib/install-profiles.cjs +35 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/mcp-server.cjs +194 -0
- package/gsd-core/bin/lib/milestone.cjs +68 -40
- package/gsd-core/bin/lib/model-adapter.cjs +50 -0
- package/gsd-core/bin/lib/phase-id.cjs +18 -0
- package/gsd-core/bin/lib/phase.cjs +57 -90
- package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
- package/gsd-core/bin/lib/planning-workspace.cjs +1 -1
- package/gsd-core/bin/lib/probe-core.cjs +132 -2
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +129 -13
- package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +3 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -22
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +65 -9
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +54 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +5 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +160 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +16 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +254 -0
- package/gsd-core/bin/lib/state-command-router.cjs +4 -0
- package/gsd-core/bin/lib/state-io.cjs +55 -0
- package/gsd-core/bin/lib/state-transition.cjs +1603 -0
- package/gsd-core/bin/lib/state.cjs +327 -683
- package/gsd-core/bin/lib/surface.cjs +4 -1
- package/gsd-core/bin/lib/validate.cjs +2 -1
- package/gsd-core/bin/lib/verify.cjs +6 -4
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
- package/gsd-core/bin/lib/workstream.cjs +4 -4
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -0
- package/gsd-core/references/agent-skills-bootstrap.md +60 -0
- package/gsd-core/references/honest-verifier.md +105 -0
- package/gsd-core/references/model-profiles.md +27 -0
- package/gsd-core/references/reviewer-instances.md +99 -0
- package/gsd-core/workflows/autonomous.md +30 -32
- package/gsd-core/workflows/complete-milestone.md +6 -10
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/forensics.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/manager.md +15 -15
- package/gsd-core/workflows/milestone-summary.md +3 -3
- package/gsd-core/workflows/new-milestone.md +6 -0
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
- package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
- package/gsd-core/workflows/plan-phase.md +4 -159
- package/gsd-core/workflows/review.md +33 -2
- package/gsd-core/workflows/thread.md +4 -4
- package/gsd-core/workflows/verify-phase.md +11 -4
- package/gsd-core/workflows/verify-work.md +1 -2
- package/hooks/dist/gsd-graphify-update.sh +7 -1
- package/hooks/gsd-graphify-update.sh +7 -1
- package/package.json +6 -4
- package/scripts/ci-test-scope.cjs +38 -9
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-regression-test-names.allowlist.json +3 -0
- package/scripts/lint-test-file-count.allowlist.json +19 -5
- package/scripts/mutation-matrix.cjs +45 -3
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/run-tests.cjs +51 -1
- package/scripts/sync-manifest-versions.cjs +66 -14
- package/skills/gsd-review/SKILL.md +6 -0
- package/scripts/lint-windows-test-portability.cjs +0 -178
|
@@ -14,6 +14,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const node_os_1 = __importDefault(require("node:os"));
|
|
16
16
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const conversionModule = require("./runtime-artifact-conversion.cjs");
|
|
19
|
+
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, } = conversionModule;
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
18
21
|
// Profile definitions
|
|
19
22
|
// ---------------------------------------------------------------------------
|
|
@@ -521,19 +524,33 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
521
524
|
* For tiered profiles, only agents whose full stem is in `resolvedProfile.agents`
|
|
522
525
|
* are staged (mirrors `stageAgentsForProfile` behaviour).
|
|
523
526
|
*
|
|
527
|
+
* ADR-1235 §1: when `agentCtx` is provided, the per-file order matches the inline
|
|
528
|
+
* agent loop in bin/install.js exactly:
|
|
529
|
+
* 1. applyAgentPathRewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
530
|
+
* 2. processAttribution (Co-Authored-By policy)
|
|
531
|
+
* 3. converter (runtime-specific frontmatter/body transform)
|
|
532
|
+
* 4. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
|
|
533
|
+
* When `agentCtx` is absent, only the converter is applied (backward-compat for
|
|
534
|
+
* the feat-1173 synthetic-descriptor tests and the copilot/antigravity paths
|
|
535
|
+
* that handle cross-cutting inside their converters).
|
|
536
|
+
*
|
|
524
537
|
* @param srcAgentsDir source agents directory (e.g. agents/)
|
|
525
538
|
* @param resolvedProfile profile filter from resolveProfile()
|
|
526
539
|
* @param converter (content: string, isGlobal?: boolean) → string per-file
|
|
527
540
|
* converter; scope-aware converters (copilot/antigravity)
|
|
528
541
|
* read isGlobal, single-arg converters ignore it (#1173)
|
|
529
542
|
* @param isGlobal install scope passed through to the converter
|
|
543
|
+
* @param agentCtx optional cross-cutting context (ADR-1235 §1); when absent,
|
|
544
|
+
* only the converter is applied (backward compat)
|
|
530
545
|
*/
|
|
531
|
-
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false) {
|
|
546
|
+
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx) {
|
|
532
547
|
if (!node_fs_1.default.existsSync(srcAgentsDir))
|
|
533
548
|
return srcAgentsDir;
|
|
534
549
|
const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-agents-'));
|
|
535
550
|
try {
|
|
536
551
|
const entries = node_fs_1.default.readdirSync(srcAgentsDir, { withFileTypes: true });
|
|
552
|
+
// Resolve cmdNames once per staging call (not per file) for performance.
|
|
553
|
+
const cmdNames = agentCtx ? _readGsdCommandNames() : [];
|
|
537
554
|
for (const entry of entries) {
|
|
538
555
|
if (!entry.isFile())
|
|
539
556
|
continue;
|
|
@@ -546,9 +563,23 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
546
563
|
continue;
|
|
547
564
|
}
|
|
548
565
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
566
|
+
let content = node_fs_1.default.readFileSync(node_path_1.default.join(srcAgentsDir, entry.name), 'utf8');
|
|
567
|
+
if (agentCtx) {
|
|
568
|
+
// ADR-1235 §1: pre-converter cross-cutting (matches inline loop order exactly)
|
|
569
|
+
// Step 1: path rewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
570
|
+
content = _applyAgentPathRewrites(content, agentCtx.runtime, agentCtx.pathPrefix);
|
|
571
|
+
// Step 2: attribution
|
|
572
|
+
content = _processAttribution(content, agentCtx.attribution);
|
|
573
|
+
// Step 3: converter (runtime-specific frontmatter/body transform)
|
|
574
|
+
content = converter(content, isGlobal);
|
|
575
|
+
// Step 4: normalize colon→hyphen refs (no-op for trivial group)
|
|
576
|
+
content = _normalizeAgentBodyForRuntime(content, agentCtx.runtime, cmdNames);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
// Backward-compat: only apply the converter (no cross-cutting)
|
|
580
|
+
content = converter(content, isGlobal);
|
|
581
|
+
}
|
|
582
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, entry.name), content, 'utf8');
|
|
552
583
|
}
|
|
553
584
|
}
|
|
554
585
|
catch (err) {
|
|
@@ -88,7 +88,7 @@ function atomicWriteInstallState(configDir, content) {
|
|
|
88
88
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
89
89
|
try {
|
|
90
90
|
node_fs_1.default.writeFileSync(tmpPath, content, 'utf8');
|
|
91
|
-
|
|
91
|
+
(0, shell_command_projection_cjs_1.retryRenameSync)(tmpPath, filePath);
|
|
92
92
|
}
|
|
93
93
|
catch (error) {
|
|
94
94
|
try {
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companion MCP server (ADR-1239 Phase C-2, #1681 slice 3a).
|
|
3
|
+
*
|
|
4
|
+
* A minimal stdio JSON-RPC 2.0 server exposing two of the six interface points
|
|
5
|
+
* so any MCP-consuming host (Claude/Codex/OpenCode/VS Code/Gemini/Cursor/Cline/
|
|
6
|
+
* Hermes) can drive GSD with NO bespoke plugin:
|
|
7
|
+
*
|
|
8
|
+
* - point 1 (command): tool `gsd_invoke_command` → the command-routing hub
|
|
9
|
+
* (`createHub`/`dispatch`, src/command-routing-hub.cts).
|
|
10
|
+
* - point 5 (state IO): tools `gsd_read_state` / `gsd_write_state` → the
|
|
11
|
+
* Phase 3 `stateIO` seam (src/state-io.cts, filesystem default).
|
|
12
|
+
*
|
|
13
|
+
* No new runtime dependency — the JSON-RPC stdio loop is hand-rolled (the repo
|
|
14
|
+
* ships only claude-agent-sdk + ws; adding an MCP SDK is a separate packaging
|
|
15
|
+
* decision). The protocol logic (`handleMessage`) is PURE and fully testable;
|
|
16
|
+
* `runServer` is a thin line-delimited-JSON loop over injectable streams.
|
|
17
|
+
*
|
|
18
|
+
* Bin entry / packaging / manifest-version-sync is slice 3b — this module is
|
|
19
|
+
* the additive, importable server surface a host (or the bin shim) drives.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.SERVER_NAME = exports.PROTOCOL_VERSION = void 0;
|
|
24
|
+
exports.handleMessage = handleMessage;
|
|
25
|
+
exports.runServer = runServer;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const commandRoutingHub = require("./command-routing-hub.cjs");
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const stateIo = require("./state-io.cjs");
|
|
30
|
+
exports.PROTOCOL_VERSION = '2024-11-05';
|
|
31
|
+
exports.SERVER_NAME = 'gsd-core';
|
|
32
|
+
const SERVER_VERSION = '1.7.0';
|
|
33
|
+
// JSON-RPC 2.0 error codes.
|
|
34
|
+
const PARSE_ERROR = -32700;
|
|
35
|
+
const INVALID_REQUEST = -32600;
|
|
36
|
+
const METHOD_NOT_FOUND = -32601;
|
|
37
|
+
const INVALID_PARAMS = -32602;
|
|
38
|
+
const INTERNAL_ERROR = -32603;
|
|
39
|
+
const TOOLS = [
|
|
40
|
+
{
|
|
41
|
+
name: 'gsd_invoke_command',
|
|
42
|
+
description: 'Invoke a GSD command via the command-routing hub (interface point 1).',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
family: { type: 'string', description: 'Command family (e.g. "query", "state", "phase").' },
|
|
47
|
+
subcommand: { type: 'string', description: 'Subcommand name.' },
|
|
48
|
+
args: { type: 'array', items: {}, description: 'Positional args.' },
|
|
49
|
+
},
|
|
50
|
+
required: ['family', 'subcommand'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'gsd_read_state',
|
|
55
|
+
description: 'Read a .planning state file (interface point 5).',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: { path: { type: 'string', description: 'Absolute path under .planning/.' } },
|
|
59
|
+
required: ['path'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'gsd_write_state',
|
|
64
|
+
description: 'Write a .planning state file (interface point 5).',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
path: { type: 'string', description: 'Absolute path under .planning/.' },
|
|
69
|
+
content: { type: 'string', description: 'File content.' },
|
|
70
|
+
},
|
|
71
|
+
required: ['path', 'content'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
function errorResponse(id, code, message, data) {
|
|
76
|
+
const err = { code, message };
|
|
77
|
+
if (data !== undefined)
|
|
78
|
+
err.data = data;
|
|
79
|
+
return { jsonrpc: '2.0', id, error: err };
|
|
80
|
+
}
|
|
81
|
+
function okResponse(id, result) {
|
|
82
|
+
return { jsonrpc: '2.0', id, result };
|
|
83
|
+
}
|
|
84
|
+
function asString(v) {
|
|
85
|
+
return typeof v === 'string' ? v : null;
|
|
86
|
+
}
|
|
87
|
+
function callTool(name, args, ctx) {
|
|
88
|
+
const a = (args && typeof args === 'object' ? args : {});
|
|
89
|
+
const cwd = asString(ctx.cwd) || process.cwd();
|
|
90
|
+
try {
|
|
91
|
+
if (name === 'gsd_invoke_command') {
|
|
92
|
+
const family = asString(a.family);
|
|
93
|
+
const subcommand = asString(a.subcommand);
|
|
94
|
+
if (!family || !subcommand) {
|
|
95
|
+
return { isError: true, content: [{ type: 'text', text: 'gsd_invoke_command requires string "family" and "subcommand".' }] };
|
|
96
|
+
}
|
|
97
|
+
const hub = commandRoutingHub.createHub();
|
|
98
|
+
const res = hub.dispatch({ family, subcommand, args: Array.isArray(a.args) ? a.args : [], cwd, raw: undefined });
|
|
99
|
+
return { content: [{ type: 'text', text: JSON.stringify(res) }] };
|
|
100
|
+
}
|
|
101
|
+
if (name === 'gsd_read_state') {
|
|
102
|
+
const p = asString(a.path);
|
|
103
|
+
if (!p)
|
|
104
|
+
return { isError: true, content: [{ type: 'text', text: 'gsd_read_state requires string "path".' }] };
|
|
105
|
+
const io = stateIo.createStateIO({ io: 'filesystem' });
|
|
106
|
+
return { content: [{ type: 'text', text: io.read(p) }] };
|
|
107
|
+
}
|
|
108
|
+
if (name === 'gsd_write_state') {
|
|
109
|
+
const p = asString(a.path);
|
|
110
|
+
const content = asString(a.content);
|
|
111
|
+
if (!p || content === null)
|
|
112
|
+
return { isError: true, content: [{ type: 'text', text: 'gsd_write_state requires string "path" and "content".' }] };
|
|
113
|
+
const io = stateIo.createStateIO({ io: 'filesystem' });
|
|
114
|
+
io.write(p, content);
|
|
115
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, path: p }) }] };
|
|
116
|
+
}
|
|
117
|
+
return { isError: true, content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return { isError: true, content: [{ type: 'text', text: `Tool error: ${e instanceof Error ? e.message : String(e)}` }] };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pure JSON-RPC handler. Takes a parsed request object + context, returns a
|
|
125
|
+
* JSON-RPC response object (or null for JSON-RPC notifications — no id).
|
|
126
|
+
*/
|
|
127
|
+
function handleMessage(request, ctx = {}) {
|
|
128
|
+
if (!request || typeof request !== 'object') {
|
|
129
|
+
return errorResponse(null, INVALID_REQUEST, 'Invalid Request: not an object.');
|
|
130
|
+
}
|
|
131
|
+
const id = request.id;
|
|
132
|
+
// Notification (no id) → no response per JSON-RPC.
|
|
133
|
+
const isNotification = id === undefined || id === null;
|
|
134
|
+
const method = typeof request.method === 'string' ? request.method : '';
|
|
135
|
+
let result;
|
|
136
|
+
switch (method) {
|
|
137
|
+
case 'initialize':
|
|
138
|
+
result = {
|
|
139
|
+
protocolVersion: exports.PROTOCOL_VERSION,
|
|
140
|
+
capabilities: { tools: {} },
|
|
141
|
+
serverInfo: { name: exports.SERVER_NAME, version: SERVER_VERSION },
|
|
142
|
+
};
|
|
143
|
+
break;
|
|
144
|
+
case 'tools/list':
|
|
145
|
+
result = { tools: TOOLS };
|
|
146
|
+
break;
|
|
147
|
+
case 'tools/call': {
|
|
148
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
149
|
+
const toolName = asString(params.name);
|
|
150
|
+
if (!toolName)
|
|
151
|
+
return errorResponse(id, INVALID_PARAMS, 'tools/call requires string "name".');
|
|
152
|
+
result = callTool(toolName, params.arguments, ctx);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
default:
|
|
156
|
+
if (isNotification)
|
|
157
|
+
return null;
|
|
158
|
+
return errorResponse(id, METHOD_NOT_FOUND, `Method not found: ${method || '(empty)'}.`);
|
|
159
|
+
}
|
|
160
|
+
if (isNotification)
|
|
161
|
+
return null;
|
|
162
|
+
return okResponse(id, result);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Thin stdio loop over injectable streams. Reads line-delimited JSON-RPC from
|
|
166
|
+
* `input`, writes responses (one JSON object + newline) to `output`. Stops when
|
|
167
|
+
* input ends. Errors in handleMessage are caught and emitted as JSON-RPC error
|
|
168
|
+
* responses (the loop never crashes).
|
|
169
|
+
*/
|
|
170
|
+
async function runServer({ input, output, ctx = {}, }) {
|
|
171
|
+
for await (const chunk of input) {
|
|
172
|
+
const lines = chunk.toString('utf-8').split(/\r?\n/);
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
if (!line.trim())
|
|
175
|
+
continue;
|
|
176
|
+
let parsed;
|
|
177
|
+
try {
|
|
178
|
+
parsed = JSON.parse(line);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
output.write(JSON.stringify(errorResponse(null, PARSE_ERROR, 'Parse error.')) + '\n');
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const response = handleMessage(parsed, ctx);
|
|
186
|
+
if (response)
|
|
187
|
+
output.write(JSON.stringify(response) + '\n');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
output.write(JSON.stringify(errorResponse(null, INTERNAL_ERROR, e instanceof Error ? e.message : 'Internal error.')) + '\n');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -19,6 +19,8 @@ const frontmatterMod = require("./frontmatter.cjs");
|
|
|
19
19
|
const stateMod = require("./state.cjs");
|
|
20
20
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
21
21
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
22
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
23
|
+
const state_transition_cjs_1 = require("./state-transition.cjs");
|
|
22
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
25
|
const ioMod = require("./io.cjs");
|
|
24
26
|
const { output, error } = ioMod;
|
|
@@ -27,13 +29,13 @@ const phaseIdMod = require("./phase-id.cjs");
|
|
|
27
29
|
const { escapeRegex, normalizePhaseName, phaseTokenMatches } = phaseIdMod;
|
|
28
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
31
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
30
|
-
const { getMilestonePhaseFilter, extractCurrentMilestone } = roadmapParserMod;
|
|
32
|
+
const { getMilestonePhaseFilter, extractCurrentMilestone, getMilestoneInfo } = roadmapParserMod;
|
|
31
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
34
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
33
35
|
const { extractOneLinerFromBody } = coreUtilsMod;
|
|
34
36
|
const { planningPaths } = planningWorkspace;
|
|
35
37
|
const { extractFrontmatter } = frontmatterMod;
|
|
36
|
-
const { writeStateMd
|
|
38
|
+
const { writeStateMd } = stateMod;
|
|
37
39
|
function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
38
40
|
if (!reqIdsRaw || reqIdsRaw.length === 0) {
|
|
39
41
|
error('requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02');
|
|
@@ -110,8 +112,13 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
110
112
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
111
113
|
const reqPath = planningPaths(cwd).requirements;
|
|
112
114
|
const statePath = planningPaths(cwd).state;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
// #1911: derive the archive base from the workstream-aware planning root so
|
|
116
|
+
// `milestone complete --ws` archives into the workstream, not root. planningPaths(cwd).planning
|
|
117
|
+
// resolves to the workstream base when GSD_WORKSTREAM is set and to root .planning otherwise
|
|
118
|
+
// (flat mode is a no-op).
|
|
119
|
+
const planningBase = planningPaths(cwd).planning;
|
|
120
|
+
const milestonesPath = node_path_1.default.join(planningBase, 'MILESTONES.md');
|
|
121
|
+
const archiveDir = node_path_1.default.join(planningBase, 'milestones');
|
|
115
122
|
const phasesDir = planningPaths(cwd).phases;
|
|
116
123
|
const today = new Date().toISOString().split('T')[0];
|
|
117
124
|
const milestoneName = options.name || version;
|
|
@@ -149,7 +156,8 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
149
156
|
if (stateVersion && stateVersion === version) {
|
|
150
157
|
const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
151
158
|
const scopedContent = extractCurrentMilestone(roadmapContent, cwd);
|
|
152
|
-
|
|
159
|
+
// #1729: `(?:\s*\([^)\n]*\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
160
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)(?:\s*\([^)\n]*\))?\s*:\s*([^\n]+)/gi;
|
|
153
161
|
const noDirectoryPhases = [];
|
|
154
162
|
let pm;
|
|
155
163
|
const phaseDirEntries = (() => {
|
|
@@ -259,9 +267,9 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
259
267
|
(0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent);
|
|
260
268
|
}
|
|
261
269
|
// Archive audit file if exists
|
|
262
|
-
const auditFile = node_path_1.default.join(
|
|
270
|
+
const auditFile = node_path_1.default.join(planningBase, `${version}-MILESTONE-AUDIT.md`);
|
|
263
271
|
if (node_fs_1.default.existsSync(auditFile)) {
|
|
264
|
-
|
|
272
|
+
(0, shell_command_projection_cjs_1.retryRenameSync)(auditFile, node_path_1.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
265
273
|
}
|
|
266
274
|
// Create/append MILESTONES.md entry
|
|
267
275
|
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join('\n');
|
|
@@ -289,38 +297,26 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
289
297
|
else {
|
|
290
298
|
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, `# Milestones\n\n${milestoneEntry}`);
|
|
291
299
|
}
|
|
292
|
-
// Update STATE.md — keep frontmatter/body semantically aligned after closure
|
|
300
|
+
// Update STATE.md — keep frontmatter/body semantically aligned after closure.
|
|
301
|
+
// ADR-1769 Phase 5: dispatches to the STATE.md Transition Module. The closure
|
|
302
|
+
// write (Status, Last Activity, Last Activity Description, Current Position
|
|
303
|
+
// reset, Operator Next Steps reset) is the pure `milestoneCompleteCore` in
|
|
304
|
+
// src/state-transition.cts, backed by the field-classification table. The
|
|
305
|
+
// runtime-specific next-milestone slash command is resolved here and injected
|
|
306
|
+
// via the intent so the core stays pure. writeStateMd still owns the lock and
|
|
307
|
+
// the steady-state syncStateFrontmatter post-sync.
|
|
293
308
|
if (node_fs_1.default.existsSync(statePath)) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i; // allow-adhoc-markdown: pre-seam section write-modify in milestone.cts; pending collectSection migration #1372
|
|
301
|
-
const closedPositionBody = `\nPhase: Milestone ${version} complete\n` +
|
|
302
|
-
`Plan: —\n` +
|
|
303
|
-
`Status: Awaiting next milestone\n` +
|
|
304
|
-
`Last activity: ${today} — Milestone ${version} completed and archived\n\n`;
|
|
305
|
-
if (positionPattern.test(stateContent)) {
|
|
306
|
-
stateContent = stateContent.replace(positionPattern, (_m, header) => `${header}${closedPositionBody}`);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
stateContent = `${stateContent.trimEnd()}\n\n## Current Position\n${closedPositionBody}`;
|
|
310
|
-
}
|
|
311
|
-
// Normalize operator-next-step tails that can become stale after close.
|
|
312
|
-
const operatorPattern = /(##\s*Operator Next Steps\s*\n)([\s\S]*?)(?=\n##|$)/i; // allow-adhoc-markdown: pre-seam section write-modify in milestone.cts; pending collectSection migration #1372
|
|
313
|
-
if (operatorPattern.test(stateContent)) {
|
|
314
|
-
stateContent = stateContent.replace(operatorPattern, `$1\n- Start the next milestone with ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))}\n\n`);
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
stateContent = `${stateContent.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))}\n`;
|
|
318
|
-
}
|
|
319
|
-
writeStateMd(statePath, stateContent, cwd);
|
|
309
|
+
const result = (0, state_transition_cjs_1.transitionCore)(node_fs_1.default.readFileSync(statePath, 'utf-8'), {
|
|
310
|
+
kind: 'milestoneComplete',
|
|
311
|
+
version,
|
|
312
|
+
nextMilestoneCommand: (0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd)),
|
|
313
|
+
}, { clock: clock_cjs_1.realClock, progressProvider: () => null });
|
|
314
|
+
writeStateMd(statePath, result.content, cwd);
|
|
320
315
|
}
|
|
321
316
|
// Archive phase directories if requested
|
|
322
317
|
let phasesArchived = false;
|
|
323
|
-
|
|
318
|
+
// #1871: archive phase dirs by default on milestone complete (opt out via --no-archive-phases).
|
|
319
|
+
if (options.archivePhases !== false) {
|
|
324
320
|
try {
|
|
325
321
|
const phaseArchiveDir = node_path_1.default.join(archiveDir, `${version}-phases`);
|
|
326
322
|
(0, shell_command_projection_cjs_1.platformEnsureDir)(phaseArchiveDir);
|
|
@@ -330,7 +326,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
330
326
|
for (const dir of phaseDirNames) {
|
|
331
327
|
if (!isDirInMilestone(dir))
|
|
332
328
|
continue;
|
|
333
|
-
|
|
329
|
+
(0, shell_command_projection_cjs_1.retryRenameSync)(node_path_1.default.join(phasesDir, dir), node_path_1.default.join(phaseArchiveDir, dir));
|
|
334
330
|
archivedCount++;
|
|
335
331
|
}
|
|
336
332
|
phasesArchived = archivedCount > 0;
|
|
@@ -409,10 +405,8 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
409
405
|
}
|
|
410
406
|
}
|
|
411
407
|
try {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
cleared++;
|
|
415
|
-
}
|
|
408
|
+
// #1871: archive phase directories instead of destroying them (shared helper).
|
|
409
|
+
cleared = archivePhaseDirectories(cwd, phasesDir, dirs).archived;
|
|
416
410
|
}
|
|
417
411
|
catch (e) {
|
|
418
412
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -421,6 +415,40 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
421
415
|
}
|
|
422
416
|
output({ cleared }, raw, `${cleared} phase director${cleared === 1 ? 'y' : 'ies'} cleared`);
|
|
423
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* #1871: move each non-999 phase directory under `phasesDir` into
|
|
420
|
+
* `milestones/<version>-phases/` (collision-safe; version from getMilestoneInfo,
|
|
421
|
+
* timestamp fallback). Shared by `phases clear` (archive-then-remove) and the
|
|
422
|
+
* internal milestone.complete phase archival so phase history survives a
|
|
423
|
+
* milestone switch instead of being hard-deleted.
|
|
424
|
+
*/
|
|
425
|
+
function archivePhaseDirectories(cwd, phasesDir, dirs) {
|
|
426
|
+
let archiveVersion = null;
|
|
427
|
+
try {
|
|
428
|
+
archiveVersion = getMilestoneInfo(cwd).version ?? null;
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
/* ROADMAP/STATE unreadable — fall back to a dated label */
|
|
432
|
+
}
|
|
433
|
+
if (!archiveVersion) {
|
|
434
|
+
archiveVersion = `archived-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 8)}`;
|
|
435
|
+
}
|
|
436
|
+
const archivePhasesDir = node_path_1.default.join(planningPaths(cwd).planning, 'milestones', `${archiveVersion}-phases`);
|
|
437
|
+
(0, shell_command_projection_cjs_1.platformEnsureDir)(archivePhasesDir);
|
|
438
|
+
let archived = 0;
|
|
439
|
+
for (const entry of dirs) {
|
|
440
|
+
const src = node_path_1.default.join(phasesDir, entry.name);
|
|
441
|
+
// Collision-safe: if a same-named archive entry exists (re-run), suffix it.
|
|
442
|
+
let dest = node_path_1.default.join(archivePhasesDir, entry.name);
|
|
443
|
+
let n = 1;
|
|
444
|
+
while (node_fs_1.default.existsSync(dest)) {
|
|
445
|
+
dest = node_path_1.default.join(archivePhasesDir, `${entry.name}.${n++}`);
|
|
446
|
+
}
|
|
447
|
+
(0, shell_command_projection_cjs_1.retryRenameSync)(src, dest);
|
|
448
|
+
archived++;
|
|
449
|
+
}
|
|
450
|
+
return { archiveDir: archivePhasesDir, archived };
|
|
451
|
+
}
|
|
424
452
|
module.exports = {
|
|
425
453
|
cmdRequirementsMarkComplete,
|
|
426
454
|
cmdMilestoneComplete,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model adapter seam (ADR-1239 Phase C-1, AC3 / #1680).
|
|
3
|
+
*
|
|
4
|
+
* Two model-layer adapters selected by the negotiated `modelMode` axis
|
|
5
|
+
* (host-integration.cts):
|
|
6
|
+
*
|
|
7
|
+
* - `passive` — GSD can only inject prompts / a per-agent `model` field (the
|
|
8
|
+
* CLI runtimes: claude/gemini/codex/opencode/cursor/…). Formalizes today's
|
|
9
|
+
* tier routing from src/model-resolver.cts: `resolveModel` delegates
|
|
10
|
+
* straight to `resolveModelForTier`, so passive reproduces current behavior
|
|
11
|
+
* byte-for-behavior.
|
|
12
|
+
* - `active` — the host exposes a provider `sendRequest` (VS Code `vscode.lm`,
|
|
13
|
+
* pi providers). GSD calls the model through the host. Ships here as a SEAM:
|
|
14
|
+
* a host-supplied `sendRequest` slot, fail-closed until a real consumer
|
|
15
|
+
* binds it (Phase 5 / #1682).
|
|
16
|
+
*
|
|
17
|
+
* Minimal (per ADR-1239 open wire-shape question): one factory, two shapes
|
|
18
|
+
* discriminated by `mode`. Concrete provider protocol (request/response shape)
|
|
19
|
+
* is fixed when a real active host lands in Phase 5.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.createModelAdapter = createModelAdapter;
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
+
const modelResolver = require("./model-resolver.cjs");
|
|
26
|
+
function createModelAdapter({ modelMode }, options = {}) {
|
|
27
|
+
if (modelMode !== 'passive' && modelMode !== 'active') {
|
|
28
|
+
throw new TypeError(`createModelAdapter: modelMode must be 'passive' | 'active' (got ${JSON.stringify(modelMode)})`);
|
|
29
|
+
}
|
|
30
|
+
if (modelMode === 'passive') {
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
mode: 'passive',
|
|
33
|
+
resolveModel({ cwd, agentType, attempt }) {
|
|
34
|
+
return modelResolver.resolveModelForTier(cwd, agentType, attempt);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// active: bind the host's sendRequest, fail-closed if absent.
|
|
39
|
+
const sendRequest = options.sendRequest;
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
mode: 'active',
|
|
42
|
+
sendRequest(req) {
|
|
43
|
+
if (typeof sendRequest !== 'function') {
|
|
44
|
+
throw new Error('ActiveModelAdapter.sendRequest: no host provider bound — the active model seam ' +
|
|
45
|
+
'requires a sendRequest primitive from the host (Phase 5 wires a concrete provider).');
|
|
46
|
+
}
|
|
47
|
+
return sendRequest(req);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -20,6 +20,23 @@ const PROJECT_CODE_PREFIX_STRIP_RE = /^[A-Z][A-Z0-9_]*-(?=\d)/;
|
|
|
20
20
|
const PROJECT_CODE_PREFIX_STRIP_RE_I = /^[A-Z][A-Z0-9_]*-(?=\d)/i;
|
|
21
21
|
const PROJECT_CODE_PREFIX_CAPTURE_RE_I = /^([A-Z][A-Z0-9_]*)-(\d.*)/i;
|
|
22
22
|
const OPTIONAL_PROJECT_CODE_PREFIX_SOURCE = '(?:[A-Z][A-Z0-9_]*-)?';
|
|
23
|
+
// #1729: phase headers may carry a parenthetical tag between the number and the
|
|
24
|
+
// colon, e.g. `### Phase 26 (Cluster B): Title`. This optional, non-capturing
|
|
25
|
+
// fragment is injected at every phase-header regex call site (immediately after
|
|
26
|
+
// the phase-number token, before the colon/space delimiter) so the resolver
|
|
27
|
+
// tolerates the tag — mirroring how `[...]` is already tolerated before `Phase`.
|
|
28
|
+
// `[^)\n]*` keeps the match single-line (headers are one line) to avoid
|
|
29
|
+
// over-consuming across a malformed multi-line document. Injected at the call
|
|
30
|
+
// site (not baked into phaseMarkdownRegexSource) so it applies uniformly to
|
|
31
|
+
// both the numeric and project-code-exact escaped sources, and so the decimal
|
|
32
|
+
// sub-phase patterns can place it after the `.N` segment.
|
|
33
|
+
//
|
|
34
|
+
// Enumeration/parse call sites that read phase headers from a regex *literal*
|
|
35
|
+
// (rather than a `new RegExp` built from an interpolated phase number) cannot
|
|
36
|
+
// reference this constant; they inline its literal-regex mirror instead —
|
|
37
|
+
// `(?:\s*\([^)\n]*\))?` — kept character-for-character equivalent to this
|
|
38
|
+
// source. Both forms must change together; see the #1729 regression test.
|
|
39
|
+
const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]*\\))?';
|
|
23
40
|
function stripProjectCodePrefix(value, caseInsensitive = true) {
|
|
24
41
|
const input = String(value);
|
|
25
42
|
const re = caseInsensitive ? PROJECT_CODE_PREFIX_STRIP_RE_I : PROJECT_CODE_PREFIX_STRIP_RE;
|
|
@@ -215,6 +232,7 @@ function phaseTokenMatches(dirName, normalized) {
|
|
|
215
232
|
module.exports = {
|
|
216
233
|
escapeRegex,
|
|
217
234
|
OPTIONAL_PROJECT_CODE_PREFIX_SOURCE,
|
|
235
|
+
OPTIONAL_PHASE_TAG_SOURCE,
|
|
218
236
|
stripProjectCodePrefix,
|
|
219
237
|
normalizePhaseName,
|
|
220
238
|
getMilestoneFromPhaseId,
|