@opengsd/gsd-core 1.9.0 → 1.10.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +131 -34
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-executor.md +7 -5
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-plan-checker.md +9 -0
- package/agents/gsd-planner.md +5 -8
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-verifier.md +14 -70
- package/bin/install.js +503 -341
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +607 -63
- package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
- package/gsd-core/bin/lib/api-coverage.cjs +120 -0
- package/gsd-core/bin/lib/audit.cjs +89 -1
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-registry.cjs +96 -110
- package/gsd-core/bin/lib/capability-validator.cjs +12 -2
- package/gsd-core/bin/lib/check-command-router.cjs +43 -1
- package/gsd-core/bin/lib/command-aliases.cjs +72 -0
- package/gsd-core/bin/lib/commands.cjs +26 -25
- package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +12 -1
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +91 -12
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/frontmatter.cjs +84 -12
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1021 -57
- package/gsd-core/bin/lib/install-engine.cjs +64 -10
- package/gsd-core/bin/lib/install-profiles.cjs +27 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
- package/gsd-core/bin/lib/io.cjs +28 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone.cjs +106 -51
- package/gsd-core/bin/lib/phase-id.cjs +63 -0
- package/gsd-core/bin/lib/phase-locator.cjs +138 -45
- package/gsd-core/bin/lib/phase.cjs +260 -25
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
- package/gsd-core/bin/lib/project-root.cjs +48 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
- package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
- package/gsd-core/bin/lib/roadmap.cjs +37 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
- package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
- package/gsd-core/bin/lib/smart-entry.cjs +12 -0
- package/gsd-core/bin/lib/state-transition.cjs +73 -8
- package/gsd-core/bin/lib/state.cjs +151 -62
- package/gsd-core/bin/lib/surface.cjs +12 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
- package/gsd-core/bin/lib/uat.cjs +320 -21
- package/gsd-core/bin/lib/unusable-input.cjs +9 -0
- package/gsd-core/bin/lib/verification.cjs +29 -12
- package/gsd-core/bin/lib/verify.cjs +29 -5
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
- package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
- package/gsd-core/bin/lib/workstream.cjs +6 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/specless-probe-fallback.md +7 -6
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +32 -69
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
- package/gsd-core/workflows/code-review.md +42 -145
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +23 -81
- package/gsd-core/workflows/debug.md +9 -12
- package/gsd-core/workflows/diagnose-issues.md +22 -0
- package/gsd-core/workflows/discovery-phase.md +4 -4
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +65 -137
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/ingest-docs.md +2 -1
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +21 -38
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +13 -226
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +49 -193
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +11 -153
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +20 -390
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +15 -8
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +17 -14
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +49 -11
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +8 -21
- package/gsd-core/workflows/ui-phase.md +8 -7
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +18 -7
- package/gsd-core/workflows/verify-phase.md +4 -7
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +8 -58
- package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
- package/hooks/dist/gsd-check-update-worker.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
- package/hooks/dist/gsd-read-injection-scanner.js +7 -0
- package/hooks/dist/gsd-statusline.js +72 -6
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/isolation-sentinel.js +268 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +428 -0
- package/hooks/gsd-check-update-worker.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +532 -26
- package/hooks/gsd-read-injection-scanner.js +7 -0
- package/hooks/gsd-statusline.js +72 -6
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/isolation-sentinel.js +268 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +14 -5
- package/pi/gsd.cjs +57 -12
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +9 -2
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-context-index.cjs +448 -0
- package/scripts/gen-inventory-manifest.cjs +101 -1
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +39 -15
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-test-file-count.allowlist.json +27 -1
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/prompt-injection-scan.sh +27 -6
- package/scripts/registry-schema.cjs +323 -94
- package/scripts/run-tests.cjs +3 -2
- package/scripts/validate-registry.cjs +10 -6
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +2 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-emitted-baseline.cjs +0 -145
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
* the additive, importable server surface a host (or the bin shim) drives.
|
|
28
28
|
*/
|
|
29
29
|
'use strict';
|
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
+
};
|
|
30
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
34
|
exports.SERVER_NAME = exports.PROTOCOL_VERSION = void 0;
|
|
32
35
|
exports.handleMessage = handleMessage;
|
|
@@ -36,9 +39,62 @@ const stateIo = require("./state-io.cjs");
|
|
|
36
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
40
|
const shellCommandProjection = require("./shell-command-projection.cjs");
|
|
38
41
|
const { dispatchGsdCommand } = shellCommandProjection;
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
44
|
+
const mcp_catalog_cjs_1 = require("./mcp-catalog.cjs");
|
|
39
45
|
exports.PROTOCOL_VERSION = '2024-11-05';
|
|
40
46
|
exports.SERVER_NAME = 'gsd-core';
|
|
41
|
-
|
|
47
|
+
// #3072: resolve SERVER_VERSION from package.json (source tree) or the
|
|
48
|
+
// installed gsd-core/VERSION marker, WITHOUT a top-level `require(...)` —
|
|
49
|
+
// mirrors the established, already-precedented resolver in
|
|
50
|
+
// `runtime-artifact-conversion.cts`'s `gsdVersion()` (#1383): a top-level
|
|
51
|
+
// require would throw on any runtime whose root has no package.json/VERSION,
|
|
52
|
+
// and `scripts/sync-manifest-versions.cjs` is not a fit here — its
|
|
53
|
+
// `VERSIONED_MANIFESTS` list stamps a `version` FIELD into hand-authored JSON
|
|
54
|
+
// manifests (plugin.json, marketplace.json, vscode/package.json); a `.cts`
|
|
55
|
+
// source constant is not a JSON document that script can round-trip through
|
|
56
|
+
// `readJson`/`setByPath`, and teaching it to text-edit TypeScript source
|
|
57
|
+
// would be a much larger footprint than this lazy, cached, defensive read.
|
|
58
|
+
// Resolved once per process (never per-request) and cached; a failed lookup
|
|
59
|
+
// degrades to '0.0.0' (never `undefined`, never a crash) rather than making
|
|
60
|
+
// `initialize`'s `serverInfo.version` field absent or malformed.
|
|
61
|
+
const SEMVER_PREFIX = /^\d+\.\d+\.\d+/;
|
|
62
|
+
let cachedServerVersion;
|
|
63
|
+
function resolveServerVersion() {
|
|
64
|
+
if (cachedServerVersion !== undefined)
|
|
65
|
+
return cachedServerVersion;
|
|
66
|
+
let version = '0.0.0';
|
|
67
|
+
try {
|
|
68
|
+
const v = node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, '..', '..', 'VERSION'), 'utf8').trim();
|
|
69
|
+
if (SEMVER_PREFIX.test(v))
|
|
70
|
+
version = v;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* not an installed tree (no gsd-core/VERSION) */
|
|
74
|
+
}
|
|
75
|
+
if (version === '0.0.0') {
|
|
76
|
+
try {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- lazy, defensive: a top-level require would throw on a runtime root with no package.json.
|
|
78
|
+
const pkg = require(node_path_1.default.join(__dirname, '..', '..', '..', 'package.json'));
|
|
79
|
+
if (pkg && typeof pkg.version === 'string' && SEMVER_PREFIX.test(pkg.version))
|
|
80
|
+
version = pkg.version;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* runtime root has no package.json */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
cachedServerVersion = version;
|
|
87
|
+
return version;
|
|
88
|
+
}
|
|
89
|
+
// Catalog is built once per process, lazily, and cached (design "Shape" /
|
|
90
|
+
// Gall's Law — no watching, no invalidation; Known limits: the package is
|
|
91
|
+
// immutable in every install mode we ship).
|
|
92
|
+
let cachedCatalog;
|
|
93
|
+
function getCatalog() {
|
|
94
|
+
if (cachedCatalog === undefined)
|
|
95
|
+
cachedCatalog = (0, mcp_catalog_cjs_1.buildCatalog)();
|
|
96
|
+
return cachedCatalog;
|
|
97
|
+
}
|
|
42
98
|
// JSON-RPC 2.0 error codes.
|
|
43
99
|
const PARSE_ERROR = -32700;
|
|
44
100
|
const INVALID_REQUEST = -32600;
|
|
@@ -93,6 +149,32 @@ function okResponse(id, result) {
|
|
|
93
149
|
function asString(v) {
|
|
94
150
|
return typeof v === 'string' ? v : null;
|
|
95
151
|
}
|
|
152
|
+
/** Extract the {@link REASON} carried by a `CatalogError`, if any (see `mcp-catalog.cts`). */
|
|
153
|
+
function catalogErrorReason(err) {
|
|
154
|
+
const reason = err && typeof err === 'object' ? err.reason : undefined;
|
|
155
|
+
return typeof reason === 'string' ? reason : undefined;
|
|
156
|
+
}
|
|
157
|
+
function catalogErrorMessage(err) {
|
|
158
|
+
return err instanceof Error ? err.message : String(err);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Map a `mcp-catalog.cts` `CatalogError.reason` to a JSON-RPC error code.
|
|
162
|
+
* `READ_FAILED` is a server-side IO/compose failure (`INTERNAL_ERROR`);
|
|
163
|
+
* every other reason (unknown uri/prompt/cursor/root, malformed/traversal
|
|
164
|
+
* uri, wrong-typed param) is a client-supplied-value problem (`INVALID_PARAMS`)
|
|
165
|
+
* — deliberately never `METHOD_NOT_FOUND`, so a refusal is never
|
|
166
|
+
* indistinguishable from the method simply not existing (test-matrix rows
|
|
167
|
+
* 33/35).
|
|
168
|
+
*/
|
|
169
|
+
function catalogErrorCode(err) {
|
|
170
|
+
return catalogErrorReason(err) === mcp_catalog_cjs_1.REASON.READ_FAILED ? INTERNAL_ERROR : INVALID_PARAMS;
|
|
171
|
+
}
|
|
172
|
+
function wireResource(entry) {
|
|
173
|
+
return { uri: entry.uri, name: entry.name, title: entry.title, description: entry.description, mimeType: entry.mimeType };
|
|
174
|
+
}
|
|
175
|
+
function wirePrompt(entry) {
|
|
176
|
+
return { name: entry.name, title: entry.title, description: entry.description };
|
|
177
|
+
}
|
|
96
178
|
function callTool(name, args, ctx) {
|
|
97
179
|
const a = (args && typeof args === 'object' ? args : {});
|
|
98
180
|
const cwd = asString(ctx.cwd) || process.cwd();
|
|
@@ -148,8 +230,12 @@ function handleMessage(request, ctx = {}) {
|
|
|
148
230
|
case 'initialize':
|
|
149
231
|
result = {
|
|
150
232
|
protocolVersion: exports.PROTOCOL_VERSION,
|
|
151
|
-
|
|
152
|
-
|
|
233
|
+
// #3072: resources/prompts declared alongside tools. Deliberately NO
|
|
234
|
+
// subscribe/listChanged — nothing ever sends those notifications
|
|
235
|
+
// (design row 2 / Hyrum's Law: advertising an unimplemented
|
|
236
|
+
// notification is a lie a host would act on).
|
|
237
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
238
|
+
serverInfo: { name: exports.SERVER_NAME, version: resolveServerVersion() },
|
|
153
239
|
};
|
|
154
240
|
break;
|
|
155
241
|
case 'tools/list':
|
|
@@ -163,6 +249,52 @@ function handleMessage(request, ctx = {}) {
|
|
|
163
249
|
result = callTool(toolName, params.arguments, ctx);
|
|
164
250
|
break;
|
|
165
251
|
}
|
|
252
|
+
case 'resources/list': {
|
|
253
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
254
|
+
try {
|
|
255
|
+
const cursor = typeof params.cursor === 'string' ? params.cursor : undefined;
|
|
256
|
+
const pageSize = typeof params.pageSize === 'number' ? params.pageSize : undefined;
|
|
257
|
+
const page = (0, mcp_catalog_cjs_1.listResources)(getCatalog(), { cursor, pageSize });
|
|
258
|
+
result = page.nextCursor === undefined
|
|
259
|
+
? { resources: page.resources.map(wireResource) }
|
|
260
|
+
: { resources: page.resources.map(wireResource), nextCursor: page.nextCursor };
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 'resources/read': {
|
|
268
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
269
|
+
try {
|
|
270
|
+
const read = (0, mcp_catalog_cjs_1.readResource)(getCatalog(), params.uri);
|
|
271
|
+
result = { contents: [{ uri: read.uri, mimeType: read.mimeType, text: read.text }] };
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case 'prompts/list':
|
|
279
|
+
result = {
|
|
280
|
+
prompts: [...getCatalog().prompts.values()]
|
|
281
|
+
.map(wirePrompt)
|
|
282
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
283
|
+
};
|
|
284
|
+
break;
|
|
285
|
+
case 'prompts/get': {
|
|
286
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
287
|
+
const name = asString(params.name);
|
|
288
|
+
if (!name)
|
|
289
|
+
return errorResponse(id, INVALID_PARAMS, 'prompts/get requires string "name".');
|
|
290
|
+
try {
|
|
291
|
+
result = (0, mcp_catalog_cjs_1.getPrompt)(getCatalog(), name, params.arguments);
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
166
298
|
default:
|
|
167
299
|
if (isNotification)
|
|
168
300
|
return null;
|
|
@@ -135,10 +135,15 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
135
135
|
// Surface 1 — the checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID**
|
|
136
136
|
// Use replace() + compare to avoid the test()+replace() global regex
|
|
137
137
|
// lastIndex bug where test() advances state and replace() misses matches.
|
|
138
|
+
// (#2788 defect 2: the flip is CONDITIONAL — when a traceability row EXISTS
|
|
139
|
+
// for this ID but its Status write is rejected, the checkbox must NOT flip,
|
|
140
|
+
// so the two surfaces cannot silently diverge. The row-write outcome below
|
|
141
|
+
// gates whether the flip is kept.)
|
|
138
142
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi');
|
|
143
|
+
const beforeCheckbox = reqContent;
|
|
139
144
|
const afterCheckbox = reqContent.replace(checkboxPattern, '$1x$2');
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
145
|
+
const checkboxFlipped = afterCheckbox !== beforeCheckbox;
|
|
146
|
+
if (checkboxFlipped)
|
|
142
147
|
reqContent = afterCheckbox;
|
|
143
148
|
// Surface 2 — the traceability row: | <REQ-ID> | Phase N | Pending | → ... Complete |
|
|
144
149
|
// via the markdown-table seam (ADR-2143 §7) — supersedes the prior ordinal
|
|
@@ -156,7 +161,11 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
156
161
|
// updateTableCell call both probes the current value and writes.
|
|
157
162
|
let tableHit = false;
|
|
158
163
|
const tableUpdate = updateTraceabilityCell(reqContent, rowMatch, 'Status', (current) => {
|
|
159
|
-
|
|
164
|
+
// #2788: accept `Gaps Found` as a forward input too — `revert-phase` (the
|
|
165
|
+
// documented gaps_found response) leaves a row stranded at Gaps Found with
|
|
166
|
+
// no inverse; a genuinely-satisfied requirement must be able to reach
|
|
167
|
+
// Complete again via mark-complete, or the milestone is blocked forever.
|
|
168
|
+
if (/^(pending|gaps found)$/i.test(current.trim())) {
|
|
160
169
|
tableHit = true;
|
|
161
170
|
return ' Complete ';
|
|
162
171
|
}
|
|
@@ -165,6 +174,17 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
165
174
|
if (tableUpdate.ok) {
|
|
166
175
|
reqContent = tableUpdate.value;
|
|
167
176
|
}
|
|
177
|
+
// #2788 defect 2: if a row EXISTS for this ID but its Status write was
|
|
178
|
+
// rejected (e.g. the row reads `Blocked`, which mark-complete does not
|
|
179
|
+
// accept), roll the checkbox back so the checkbox and the row cannot
|
|
180
|
+
// silently diverge. The checkbox and the row are two representations of the
|
|
181
|
+
// same fact; flipping one while the other rejects the write is the lie.
|
|
182
|
+
let checkboxHit = checkboxFlipped;
|
|
183
|
+
const rowExistsProbe = tableUpdate; // ok === a row matched (probes existence)
|
|
184
|
+
if (checkboxFlipped && rowExistsProbe.ok && !tableHit) {
|
|
185
|
+
reqContent = beforeCheckbox;
|
|
186
|
+
checkboxHit = false;
|
|
187
|
+
}
|
|
168
188
|
// ADR-2143 §6 per-ID write-set entries: this ID's checkbox surface is
|
|
169
189
|
// always tracked; the traceability surface is tracked only when the file
|
|
170
190
|
// has a traceability table at all (same `hasTable` gate the existing
|
|
@@ -190,7 +210,14 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
190
210
|
const hasRow = statusProbe.ok;
|
|
191
211
|
const doneCheckbox = new RegExp(`-\\s*\\[x\\]\\s*\\*\\*${reqEscaped}\\*\\*`, 'i').test(reqContent);
|
|
192
212
|
const doneTable = Boolean(hasRow && /^complete$/i.test(currentStatusCell.trim()));
|
|
193
|
-
|
|
213
|
+
// #2788 defect 2: when a traceability table exists AND this ID has a row in
|
|
214
|
+
// it, `updated`/`marked_complete` must reflect the ROW moving, not a
|
|
215
|
+
// checkbox-only flip. Otherwise (`table_unmatched` — no row for this ID, or
|
|
216
|
+
// no table at all) the checkbox flip is a legitimate partial reconcile / the
|
|
217
|
+
// sole completion surface, so the #2140 OR semantics are preserved.
|
|
218
|
+
const rowExists = hasTable && hasRow;
|
|
219
|
+
const idUpdated = rowExists ? tableHit : (checkboxHit || tableHit);
|
|
220
|
+
if (idUpdated) {
|
|
194
221
|
updated.push(reqId);
|
|
195
222
|
}
|
|
196
223
|
else if (doneTable || (doneCheckbox && !hasTable)) {
|
|
@@ -437,13 +464,25 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
437
464
|
// Guard: prevent marking complete when ROADMAP still lists phases that have
|
|
438
465
|
// no directory on disk (disk_status: no_directory). This catches the case
|
|
439
466
|
// where the active milestone was erroneously marked complete before phases
|
|
440
|
-
// were even started.
|
|
441
|
-
//
|
|
442
|
-
//
|
|
467
|
+
// were even started. The scan scopes the ROADMAP via the `version` argument
|
|
468
|
+
// (getMilestonePhaseFilter / extractCurrentMilestone above) and runs whenever
|
|
469
|
+
// --force is absent — a fresh project with no `### Phase N:` headings in the
|
|
470
|
+
// scoped slice yields an empty `noDirectoryPhases` and the guard is a no-op,
|
|
471
|
+
// so no STATE match is required to avoid false positives.
|
|
443
472
|
// Pass --force to override this guard.
|
|
473
|
+
//
|
|
474
|
+
// #2946: the scan used to be nested inside `if (stateVersion && stateVersion
|
|
475
|
+
// === version)`, which silently disarmed the guard whenever STATE.md's
|
|
476
|
+
// `milestone:` field was desynced or absent — functionally an implicit
|
|
477
|
+
// --force on a one-way-door operation (ROADMAP/REQUIREMENTS archived, phase
|
|
478
|
+
// directories MOVED). The STATE field is not the source of truth for which
|
|
479
|
+
// phases belong to this milestone; the ROADMAP scoping is. The scan now runs
|
|
480
|
+
// unconditionally, and a present-but-mismatched STATE field emits a WARNING
|
|
481
|
+
// so the suspicious condition is visible rather than silent.
|
|
444
482
|
if (!options.force) {
|
|
445
483
|
try {
|
|
446
|
-
//
|
|
484
|
+
// Read STATE.md's milestone field only to detect a suspicious mismatch;
|
|
485
|
+
// it no longer gates the scan. (#2946)
|
|
447
486
|
let stateVersion = null;
|
|
448
487
|
try {
|
|
449
488
|
const stateRaw = node_fs_1.default.existsSync(statePath) ? node_fs_1.default.readFileSync(statePath, 'utf-8') : null;
|
|
@@ -454,50 +493,66 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
454
493
|
}
|
|
455
494
|
}
|
|
456
495
|
catch {
|
|
457
|
-
/* skip */
|
|
496
|
+
/* skip — stateVersion stays null, scan still runs */
|
|
458
497
|
}
|
|
459
|
-
if (stateVersion && stateVersion
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
// with a matching token exists on disk. Use the same phaseTokenMatches
|
|
490
|
-
// helper that roadmap.analyze uses to avoid false positives on decimal
|
|
491
|
-
// (2.1) and letter-suffix (12A) phase IDs.
|
|
492
|
-
const hasDirectory = phaseDirEntries.some((d) => phaseTokenMatches(d, normalized));
|
|
493
|
-
if (!hasDirectory) {
|
|
494
|
-
noDirectoryPhases.push(phaseNum);
|
|
495
|
-
}
|
|
498
|
+
if (stateVersion !== null && stateVersion !== version) {
|
|
499
|
+
// #2946: emit a WARNING so the suspicious STATE mismatch is visible
|
|
500
|
+
// rather than silently disarming the guard. Plain-text diagnostic on
|
|
501
|
+
// stderr, matching the existing [gsd-tools] WARNING convention
|
|
502
|
+
// (state.cts). A missing STATE.md `milestone:` field is not warned
|
|
503
|
+
// here — the scan still runs, and "no milestone declared" is a normal
|
|
504
|
+
// state for a fresh project, not a suspicious drift.
|
|
505
|
+
//
|
|
506
|
+
// `stateVersion` comes from a user-controlled file (STATE.md) and is
|
|
507
|
+
// not validated like the CLI `version` arg (ARCHIVE_VERSION_LABEL_RE).
|
|
508
|
+
// Sanitize before interpolating into stderr so ANSI escapes / control
|
|
509
|
+
// chars / secret-looking strings cannot be echoed verbatim into a CI
|
|
510
|
+
// log or terminal (CONTRIBUTING.md security: secret-looking values in
|
|
511
|
+
// stderr). `version` is already constrained to [A-Za-z0-9._-].
|
|
512
|
+
const safeStateVersion = stateVersion.replace(/[\x00-\x1f\x7f]/g, '?').slice(0, 80);
|
|
513
|
+
process.stderr.write(`[gsd-tools] WARNING: STATE.md milestone: "${safeStateVersion}" ≠ requested "${version}" — ` +
|
|
514
|
+
`running the unstarted-phase guard against the ROADMAP scoped for "${version}" anyway.\n`);
|
|
515
|
+
}
|
|
516
|
+
const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
517
|
+
const scopedContent = extractCurrentMilestone(roadmapContent, cwd);
|
|
518
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
519
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
520
|
+
const noDirectoryPhases = [];
|
|
521
|
+
let pm;
|
|
522
|
+
const phaseDirEntries = (() => {
|
|
523
|
+
try {
|
|
524
|
+
return node_fs_1.default
|
|
525
|
+
.readdirSync(phasesDir, { withFileTypes: true })
|
|
526
|
+
.filter((e) => e.isDirectory())
|
|
527
|
+
.map((e) => e.name);
|
|
496
528
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
`(e.g. Phase ${noDirectoryPhases[0]}). Re-run with --force to override.`);
|
|
529
|
+
catch {
|
|
530
|
+
return [];
|
|
500
531
|
}
|
|
532
|
+
})();
|
|
533
|
+
while ((pm = phasePattern.exec(scopedContent)) !== null) {
|
|
534
|
+
const phaseNum = pm[1];
|
|
535
|
+
// Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not
|
|
536
|
+
// real phases — they legitimately have no directory and must not block
|
|
537
|
+
// milestone completion. Mirrors the engine-wide sentinel convention
|
|
538
|
+
// (phase-id getMilestoneFromPhaseId, roadmap-command-router SENTINELS,
|
|
539
|
+
// the #1445 /^999/ progress filters). (#1580)
|
|
540
|
+
const major = parseInt(phaseNum, 10);
|
|
541
|
+
if (major === 0 || major === 999)
|
|
542
|
+
continue;
|
|
543
|
+
const normalized = normalizePhaseName(phaseNum);
|
|
544
|
+
// A phase has disk_status: 'no_directory' when no phase directory
|
|
545
|
+
// with a matching token exists on disk. Use the same phaseTokenMatches
|
|
546
|
+
// helper that roadmap.analyze uses to avoid false positives on decimal
|
|
547
|
+
// (2.1) and letter-suffix (12A) phase IDs.
|
|
548
|
+
const hasDirectory = phaseDirEntries.some((d) => phaseTokenMatches(d, normalized));
|
|
549
|
+
if (!hasDirectory) {
|
|
550
|
+
noDirectoryPhases.push(phaseNum);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (noDirectoryPhases.length > 0) {
|
|
554
|
+
error(`Cannot mark milestone complete: ROADMAP lists ${noDirectoryPhases.length} unstarted phase(s) ` +
|
|
555
|
+
`(e.g. Phase ${noDirectoryPhases[0]}). Re-run with --force to override.`);
|
|
501
556
|
}
|
|
502
557
|
}
|
|
503
558
|
catch (e) {
|
|
@@ -505,7 +560,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
505
560
|
const message = e instanceof Error ? e.message : String(e);
|
|
506
561
|
if (message && message.startsWith('Cannot mark milestone complete:'))
|
|
507
562
|
throw e;
|
|
508
|
-
// Phase scan failed
|
|
563
|
+
// Phase scan failed (e.g. ROADMAP unreadable) — allow completion to proceed.
|
|
509
564
|
}
|
|
510
565
|
}
|
|
511
566
|
// Gather stats from phases (scoped to current milestone only)
|
|
@@ -668,7 +723,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
668
723
|
kind: 'milestoneComplete',
|
|
669
724
|
version,
|
|
670
725
|
nextMilestoneCommand: (0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd)),
|
|
671
|
-
}, { clock: clock_cjs_1.realClock,
|
|
726
|
+
}, { clock: clock_cjs_1.realClock, sourcePath: statePath });
|
|
672
727
|
writeStateMd(statePath, result.content, cwd);
|
|
673
728
|
}
|
|
674
729
|
// Archive phase directories if requested
|
|
@@ -520,6 +520,65 @@ function phaseTokenMatches(dirName, normalized) {
|
|
|
520
520
|
}
|
|
521
521
|
return false;
|
|
522
522
|
}
|
|
523
|
+
// ─── Canonical phase KEY surface (#2562) ─────────────────────────────────────
|
|
524
|
+
//
|
|
525
|
+
// A phase "key" is the padding-, case- and project-code-insensitive identity of
|
|
526
|
+
// a phase, for use as a Map/Set key when two independently-derived phase
|
|
527
|
+
// references (a ROADMAP table cell and a phase directory name, say) must be
|
|
528
|
+
// compared. Promoted here from a local pair in state.cts (#2445) so every
|
|
529
|
+
// consumer derives BOTH sides of a comparison from the SAME function — deriving
|
|
530
|
+
// one side with a bespoke regex is the #2562 defect class (a `01` table cell
|
|
531
|
+
// never matching a `1-slug` directory, silently zeroing a rollup).
|
|
532
|
+
/**
|
|
533
|
+
* Canonical key for an already-extracted phase TOKEN (`"5"`, `"05"`, `"005"`,
|
|
534
|
+
* `"12A"`, `"30.1"`, `"PROJ-05"`). Padding- and case-insensitive: every
|
|
535
|
+
* spelling of a number collapses to one key.
|
|
536
|
+
*
|
|
537
|
+
* Leading zeros are stripped per hyphen-separated segment BEFORE
|
|
538
|
+
* `normalizePhaseName` pads to the 2-digit convention. Padding alone is not a
|
|
539
|
+
* normalisation — `padStart(2)` is a no-op once the input is already ≥2
|
|
540
|
+
* characters, so `5` yielded `05` while `005` stayed `005` and the two never
|
|
541
|
+
* compared equal. The strip is deliberately confined to this key surface:
|
|
542
|
+
* `normalizePhaseName` itself is a RENDERING function whose verbatim treatment
|
|
543
|
+
* of wide IDs (`001.10`) is relied on by plan-ID capture and wave assignment.
|
|
544
|
+
* Arithmetic is avoided (`parseInt` would lose precision on a long digit run).
|
|
545
|
+
*/
|
|
546
|
+
function phaseKeyFromToken(token) {
|
|
547
|
+
const stripped = String(token)
|
|
548
|
+
.split('-')
|
|
549
|
+
.map(segment => segment.replace(/^0+(?=\d)/, ''))
|
|
550
|
+
.join('-');
|
|
551
|
+
return normalizePhaseName(stripped).toUpperCase();
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Canonical key for a phase DIRECTORY name (`"05-schedule-8"` → `"05"`,
|
|
555
|
+
* `"PROJ-5-x"` → `"05"`, `"30.1-follow-up"` → `"30.1"`).
|
|
556
|
+
*/
|
|
557
|
+
function phaseKeyFromDir(dirName) {
|
|
558
|
+
return phaseKeyFromToken(extractPhaseToken(dirName));
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Canonical key for a phase referenced in PROSE — a ROADMAP `## Progress` table
|
|
562
|
+
* cell (`"30. Schedule 8 rollout"`, `"**05.1 Follow-up**"`) or a STATE.md
|
|
563
|
+
* `Phase:` value. Markdown emphasis is stripped first so a bolded cell is not
|
|
564
|
+
* mistaken for a non-phase. Returns null when the value does not BEGIN with a
|
|
565
|
+
* phase token (`parsePhaseFromProse` anchoring, #2111).
|
|
566
|
+
*/
|
|
567
|
+
function phaseKeyFromProse(value) {
|
|
568
|
+
if (value == null)
|
|
569
|
+
return null;
|
|
570
|
+
const { phase } = parsePhaseFromProse(String(value).replace(/[*_`~]/g, ''));
|
|
571
|
+
return phase === null ? null : phaseKeyFromToken(phase);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The PARENT phase key of a sub-phase key (`"30.1"` → `"30"`), or null for a
|
|
575
|
+
* top-level phase. A sub-phase directory inserted mid-milestone frequently has
|
|
576
|
+
* no ROADMAP row of its own and inherits its parent's milestone (#2562).
|
|
577
|
+
*/
|
|
578
|
+
function parentPhaseKey(key) {
|
|
579
|
+
const dot = key.indexOf('.');
|
|
580
|
+
return dot === -1 ? null : key.slice(0, dot);
|
|
581
|
+
}
|
|
523
582
|
// ─── #2121 canonical surface (ADR-2121) ──────────────────────────────────────
|
|
524
583
|
/**
|
|
525
584
|
* Parse a phase identifier from a STATE.md `Phase:` prose field VALUE — the text
|
|
@@ -662,6 +721,10 @@ module.exports = {
|
|
|
662
721
|
comparePhaseNum,
|
|
663
722
|
extractPhaseToken,
|
|
664
723
|
phaseTokenMatches,
|
|
724
|
+
phaseKeyFromToken,
|
|
725
|
+
phaseKeyFromDir,
|
|
726
|
+
phaseKeyFromProse,
|
|
727
|
+
parentPhaseKey,
|
|
665
728
|
parsePhaseFromProse,
|
|
666
729
|
stripConfiguredProjectCodePrefix,
|
|
667
730
|
isForeignPrefixedPhaseQuery,
|