@opengsd/gsd-pi 1.1.1-dev.74e8dd1 → 1.1.1-dev.9bb7453
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/dist/cli.js +3 -2
- package/dist/help-text.js +10 -6
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +495 -0
- package/dist/resources/extensions/browser-tools/engine/selection.js +16 -0
- package/dist/resources/extensions/browser-tools/extension-manifest.json +2 -2
- package/dist/resources/extensions/browser-tools/index.js +57 -9
- package/dist/resources/extensions/browser-tools/package.json +5 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +77 -13
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +59 -22
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +9 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -4
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -5
- package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
- package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
- package/dist/resources/extensions/gsd/escalation.js +4 -4
- package/dist/resources/extensions/gsd/forensics.js +74 -2
- package/dist/resources/extensions/gsd/gsd-db.js +5 -2
- package/dist/resources/extensions/gsd/guided-flow.js +29 -68
- package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
- package/dist/resources/extensions/gsd/memory-store.js +4 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +40 -22
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/dist/resources/extensions/gsd/rule-registry.js +428 -52
- package/dist/resources/extensions/gsd/state.js +2 -2
- package/dist/resources/extensions/gsd/templates/plan.md +3 -1
- package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +51 -14
- package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
- package/dist/resources/extensions/gsd/verification-gate.js +72 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
- package/dist/rtk.d.ts +7 -1
- package/dist/rtk.js +27 -11
- package/dist/update-check.d.ts +15 -1
- package/dist/update-check.js +87 -12
- package/dist/update-cmd.d.ts +1 -0
- package/dist/update-cmd.js +53 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +4 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/agent-session.d.ts +9 -0
- package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/agent-session.js +32 -0
- package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
- package/packages/gsd-agent-core/dist/index.d.ts +1 -0
- package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/index.js +1 -0
- package/packages/gsd-agent-core/dist/index.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
- package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
- package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
- package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
- package/packages/gsd-agent-core/package.json +6 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +20 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +23 -9
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +2 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +38 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +3 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/api-registry.d.ts +2 -0
- package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
- package/packages/pi-ai/dist/api-registry.js +23 -0
- package/packages/pi-ai/dist/api-registry.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +68 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +72 -4
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/stream.js +6 -6
- package/packages/pi-ai/dist/stream.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
- package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
- package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
- package/src/resources/extensions/browser-tools/index.ts +60 -9
- package/src/resources/extensions/browser-tools/package.json +5 -1
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
- package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +93 -15
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -4
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -5
- package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
- package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
- package/src/resources/extensions/gsd/escalation.ts +4 -4
- package/src/resources/extensions/gsd/forensics.ts +99 -5
- package/src/resources/extensions/gsd/gsd-db.ts +5 -2
- package/src/resources/extensions/gsd/guided-flow.ts +90 -82
- package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
- package/src/resources/extensions/gsd/memory-store.ts +4 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +40 -22
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/src/resources/extensions/gsd/rule-registry.ts +558 -58
- package/src/resources/extensions/gsd/rule-types.ts +2 -0
- package/src/resources/extensions/gsd/state.ts +2 -2
- package/src/resources/extensions/gsd/templates/plan.md +3 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
- package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +130 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +63 -15
- package/src/resources/extensions/gsd/types.ts +69 -5
- package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
- package/src/resources/extensions/gsd/verification-gate.ts +87 -1
- package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
- /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → jBtwT9v1u2lUA3UEOy_ZH}/_ssgManifest.js +0 -0
|
@@ -314,9 +314,18 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
314
314
|
// overwrite it; gate rows are UPSERT-keyed per task and will also be
|
|
315
315
|
// overwritten. This restores the invariant that deriveState() sees a
|
|
316
316
|
// consistent "task not done" view so the loop re-dispatches the task.
|
|
317
|
+
let escalationMetadata;
|
|
317
318
|
if (validatedEscalationArtifact) {
|
|
318
319
|
try {
|
|
319
|
-
writeEscalationArtifact(artifactBasePath, validatedEscalationArtifact);
|
|
320
|
+
const escalationPath = writeEscalationArtifact(artifactBasePath, validatedEscalationArtifact);
|
|
321
|
+
escalationMetadata = {
|
|
322
|
+
artifactPath: escalationPath,
|
|
323
|
+
question: validatedEscalationArtifact.question,
|
|
324
|
+
options: validatedEscalationArtifact.options,
|
|
325
|
+
recommendation: validatedEscalationArtifact.recommendation,
|
|
326
|
+
recommendationRationale: validatedEscalationArtifact.recommendationRationale,
|
|
327
|
+
continueWithDefault: validatedEscalationArtifact.continueWithDefault,
|
|
328
|
+
};
|
|
320
329
|
}
|
|
321
330
|
catch (escalationErr) {
|
|
322
331
|
const msg = `complete-task escalation write failed for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${escalationErr.message}`;
|
|
@@ -378,6 +387,7 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
378
387
|
sliceId: params.sliceId,
|
|
379
388
|
milestoneId: params.milestoneId,
|
|
380
389
|
summaryPath,
|
|
390
|
+
...(escalationMetadata ? { escalation: escalationMetadata } : {}),
|
|
381
391
|
...(projectionStale ? { stale: true } : {}),
|
|
382
392
|
};
|
|
383
393
|
}
|
|
@@ -44,19 +44,12 @@ function getRequiredVerificationClasses(milestoneId) {
|
|
|
44
44
|
required.push("UAT");
|
|
45
45
|
return required;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
chunks.push(artifact.full_content);
|
|
54
|
-
const assessmentPath = resolveSliceFile(basePath, milestoneId, slice.id, "ASSESSMENT");
|
|
55
|
-
const assessmentContent = assessmentPath ? await loadFile(assessmentPath) : null;
|
|
56
|
-
if (assessmentContent)
|
|
57
|
-
chunks.push(assessmentContent);
|
|
58
|
-
}
|
|
59
|
-
return chunks.join("\n\n");
|
|
47
|
+
function hasRuntimeExecutableUatEvidenceText(text) {
|
|
48
|
+
if (!/\buatType:\s*runtime-executable\b/i.test(text))
|
|
49
|
+
return false;
|
|
50
|
+
if (!/\bverdict:\s*PASS\b/i.test(text))
|
|
51
|
+
return false;
|
|
52
|
+
return /^\|\s*[^|\n]+\s*\|\s*runtime\s*\|\s*PASS\s*\|[^|\n]*\bgsd_uat_exec\b/mi.test(text);
|
|
60
53
|
}
|
|
61
54
|
async function browserEvidenceGateRequiresAttention(params, basePath) {
|
|
62
55
|
if (params.verdict !== "pass")
|
|
@@ -77,7 +70,36 @@ async function browserEvidenceGateRequiresAttention(params, basePath) {
|
|
|
77
70
|
]);
|
|
78
71
|
if (!hasBrowserRequiredText(requirementText))
|
|
79
72
|
return false;
|
|
80
|
-
|
|
73
|
+
// Collect per-slice evidence so the runtime bypass is checked independently
|
|
74
|
+
// for each slice. Concatenating all slices before checking would allow runtime
|
|
75
|
+
// evidence from one slice to cover another slice's browser requirements.
|
|
76
|
+
const sliceEvidencePairs = [];
|
|
77
|
+
for (const slice of slices) {
|
|
78
|
+
const chunks = [];
|
|
79
|
+
const artifactPath = `milestones/${params.milestoneId}/slices/${slice.id}/${slice.id}-ASSESSMENT.md`;
|
|
80
|
+
const artifact = getArtifact(artifactPath);
|
|
81
|
+
if (artifact?.full_content)
|
|
82
|
+
chunks.push(artifact.full_content);
|
|
83
|
+
const assessmentPath = resolveSliceFile(basePath, params.milestoneId, slice.id, "ASSESSMENT");
|
|
84
|
+
const assessmentContent = assessmentPath ? await loadFile(assessmentPath) : null;
|
|
85
|
+
if (assessmentContent)
|
|
86
|
+
chunks.push(assessmentContent);
|
|
87
|
+
sliceEvidencePairs.push({
|
|
88
|
+
sliceRequirementText: compactTextParts([slice.demo, slice.goal, slice.success_criteria]),
|
|
89
|
+
evidenceText: chunks.join("\n\n"),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const persistedEvidence = sliceEvidencePairs.map((s) => s.evidenceText).join("\n\n");
|
|
93
|
+
// Runtime bypass: each slice whose own requirement text has browser-observable
|
|
94
|
+
// criteria must have its own runtime-executable UAT evidence. When no individual
|
|
95
|
+
// slice has slice-level browser requirements (e.g., they come from milestone-level
|
|
96
|
+
// fields only), fall back to checking whether any slice has runtime evidence.
|
|
97
|
+
const browserRequiringSlices = sliceEvidencePairs.filter((s) => hasBrowserRequiredText(s.sliceRequirementText));
|
|
98
|
+
const runtimeBypasses = browserRequiringSlices.length > 0
|
|
99
|
+
? browserRequiringSlices.every((s) => hasRuntimeExecutableUatEvidenceText(s.evidenceText))
|
|
100
|
+
: sliceEvidencePairs.some((s) => hasRuntimeExecutableUatEvidenceText(s.evidenceText));
|
|
101
|
+
if (runtimeBypasses)
|
|
102
|
+
return false;
|
|
81
103
|
const validationEvidence = compactTextParts([
|
|
82
104
|
params.successCriteriaChecklist,
|
|
83
105
|
params.verificationClasses,
|
|
@@ -138,12 +160,20 @@ export async function handleValidateMilestone(params, basePath, opts) {
|
|
|
138
160
|
const requiredClasses = getRequiredVerificationClasses(params.milestoneId);
|
|
139
161
|
if (requiredClasses.length > 0) {
|
|
140
162
|
const verificationClasses = params.verificationClasses ?? "";
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
163
|
+
const missingClasses = requiredClasses.filter((className) => !new RegExp(`\\b${className}\\b`, "i").test(verificationClasses));
|
|
164
|
+
if (missingClasses.length === 1) {
|
|
165
|
+
const missingClass = missingClasses[0];
|
|
143
166
|
return {
|
|
144
167
|
error: `verificationClasses must include canonical row "${missingClass}" because this milestone planned ${missingClass.toLowerCase()} verification`,
|
|
145
168
|
};
|
|
146
169
|
}
|
|
170
|
+
if (missingClasses.length > 1) {
|
|
171
|
+
const quotedClasses = missingClasses.map((className) => `"${className}"`).join(", ");
|
|
172
|
+
const plannedClasses = missingClasses.map((className) => className.toLowerCase()).join(", ");
|
|
173
|
+
return {
|
|
174
|
+
error: `verificationClasses must include canonical rows ${quotedClasses} because this milestone planned ${plannedClasses} verification`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
147
177
|
}
|
|
148
178
|
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
|
|
149
179
|
const shouldApplyBrowserEvidenceGate = !opts?.skipBrowserEvidenceGate &&
|
|
@@ -334,6 +334,28 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
|
|
|
334
334
|
isError: true,
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
|
+
if (result.escalation) {
|
|
338
|
+
const recommended = result.escalation.options.find((option) => option.id === result.escalation?.recommendation);
|
|
339
|
+
const optionIds = result.escalation.options.map((option) => option.id).join("|");
|
|
340
|
+
return {
|
|
341
|
+
content: [{
|
|
342
|
+
type: "text",
|
|
343
|
+
text: [
|
|
344
|
+
`Task completed with escalation decision required: ${result.escalation.question}`,
|
|
345
|
+
`Recommendation: ${result.escalation.recommendation}${recommended ? ` (${recommended.label})` : ""} — ${result.escalation.recommendationRationale}`,
|
|
346
|
+
`Resolve with: /gsd escalate resolve ${result.taskId} <${optionIds}|accept|reject-blocker> [rationale...]`,
|
|
347
|
+
].join("\n"),
|
|
348
|
+
}],
|
|
349
|
+
details: {
|
|
350
|
+
operation: "complete_task",
|
|
351
|
+
taskId: result.taskId,
|
|
352
|
+
sliceId: result.sliceId,
|
|
353
|
+
milestoneId: result.milestoneId,
|
|
354
|
+
summaryPath: result.summaryPath,
|
|
355
|
+
escalation: result.escalation,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
337
359
|
return {
|
|
338
360
|
content: [{ type: "text", text: `Completed task ${result.taskId} (${result.sliceId}/${result.milestoneId})` }],
|
|
339
361
|
details: {
|
|
@@ -932,6 +954,9 @@ function validateUatChecks(basePath, params) {
|
|
|
932
954
|
function validateUatMode(params) {
|
|
933
955
|
const modes = new Set(params.checks.map((check) => check.mode));
|
|
934
956
|
const hasHuman = params.checks.some((check) => check.result === "NEEDS-HUMAN");
|
|
957
|
+
if (params.uatType === "artifact-driven" && hasHuman && params.verdict === "PASS") {
|
|
958
|
+
return "artifact-driven UAT cannot PASS with human-only checks";
|
|
959
|
+
}
|
|
935
960
|
if (hasHuman &&
|
|
936
961
|
params.verdict === "PASS" &&
|
|
937
962
|
!["human-experience", "mixed", "live-runtime"].includes(params.uatType) &&
|
|
@@ -947,11 +972,11 @@ function validateUatMode(params) {
|
|
|
947
972
|
if (params.uatType === "live-runtime" && !modes.has("runtime") && !modes.has("browser")) {
|
|
948
973
|
return "live-runtime UAT requires runtime or browser evidence";
|
|
949
974
|
}
|
|
950
|
-
if (params.uatType === "artifact-driven" && hasHuman && params.verdict === "PASS") {
|
|
951
|
-
return "artifact-driven UAT cannot PASS with human-only checks";
|
|
952
|
-
}
|
|
953
975
|
return null;
|
|
954
976
|
}
|
|
977
|
+
function quoteToolNames(toolNames) {
|
|
978
|
+
return toolNames.map((toolName) => `"${toolName}"`).join(", ");
|
|
979
|
+
}
|
|
955
980
|
function validateCanonicalPresentation(params) {
|
|
956
981
|
const aliasHints = {
|
|
957
982
|
gsd_save_summary: "gsd_summary_save",
|
|
@@ -959,34 +984,46 @@ function validateCanonicalPresentation(params) {
|
|
|
959
984
|
gsd_complete_slice: "gsd_slice_complete",
|
|
960
985
|
gsd_milestone_complete: "gsd_complete_milestone",
|
|
961
986
|
};
|
|
987
|
+
const errors = [];
|
|
962
988
|
for (const toolName of params.presentation.presentedTools) {
|
|
963
989
|
const baseName = parseMcpToolName(toolName)?.tool ?? toolName;
|
|
964
990
|
const canonical = aliasHints[baseName];
|
|
965
991
|
if (canonical)
|
|
966
|
-
|
|
992
|
+
errors.push(`presentation tool "${toolName}" uses an alias; use canonical "${canonical}"`);
|
|
967
993
|
}
|
|
968
994
|
const presentedCanonical = new Set(params.presentation.presentedTools.map((toolName) => canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)));
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
995
|
+
const missingRequiredTools = RUN_UAT_WORKFLOW_TOOL_NAMES.filter((requiredTool) => !presentedCanonical.has(requiredTool));
|
|
996
|
+
if (missingRequiredTools.length === 1) {
|
|
997
|
+
errors.push(`presentation is missing required UAT tool "${missingRequiredTools[0]}"`);
|
|
998
|
+
}
|
|
999
|
+
else if (missingRequiredTools.length > 1) {
|
|
1000
|
+
errors.push(`presentation is missing required UAT tools ${quoteToolNames(missingRequiredTools)}`);
|
|
973
1001
|
}
|
|
974
1002
|
const forbiddenCanonical = new Set(RUN_UAT_FORBIDDEN_TOOL_NAMES
|
|
975
1003
|
.filter((toolName) => !toolName.includes("*"))
|
|
976
1004
|
.map((toolName) => canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName)));
|
|
1005
|
+
const forbiddenPresentedTools = [];
|
|
977
1006
|
for (const toolName of params.presentation.presentedTools) {
|
|
978
1007
|
const canonical = canonicalWorkflowToolName(parseMcpToolName(toolName)?.tool ?? toolName);
|
|
979
1008
|
if (toolName === "mcp__gsd-workflow__*" || forbiddenCanonical.has(canonical)) {
|
|
980
|
-
|
|
1009
|
+
forbiddenPresentedTools.push(toolName);
|
|
981
1010
|
}
|
|
982
1011
|
}
|
|
1012
|
+
if (forbiddenPresentedTools.length === 1) {
|
|
1013
|
+
errors.push(`presentation includes forbidden run-uat tool "${forbiddenPresentedTools[0]}"`);
|
|
1014
|
+
}
|
|
1015
|
+
else if (forbiddenPresentedTools.length > 1) {
|
|
1016
|
+
errors.push(`presentation includes forbidden run-uat tools ${quoteToolNames(forbiddenPresentedTools)}`);
|
|
1017
|
+
}
|
|
983
1018
|
const blockedCanonical = new Set(params.presentation.blockedTools.map((entry) => canonicalWorkflowToolName(parseMcpToolName(entry.name)?.tool ?? entry.name)));
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
1019
|
+
const missingBlockedTools = ["gsd_exec", "gsd_summary_save", "gsd_save_gate_result"].filter((blockedTool) => !blockedCanonical.has(blockedTool));
|
|
1020
|
+
if (missingBlockedTools.length === 1) {
|
|
1021
|
+
errors.push(`presentation must record "${missingBlockedTools[0]}" as blocked during run-uat`);
|
|
988
1022
|
}
|
|
989
|
-
|
|
1023
|
+
else if (missingBlockedTools.length > 1) {
|
|
1024
|
+
errors.push(`presentation must record ${quoteToolNames(missingBlockedTools)} as blocked during run-uat`);
|
|
1025
|
+
}
|
|
1026
|
+
return errors.length > 0 ? errors.join("; ") : null;
|
|
990
1027
|
}
|
|
991
1028
|
function nextUatAttempt(basePath, milestoneId, sliceId) {
|
|
992
1029
|
const contract = resolveGsdPathContract(basePath);
|
|
@@ -5,7 +5,62 @@
|
|
|
5
5
|
* (e.g. `passed` → `pass`) are applied consistently across the codebase.
|
|
6
6
|
*/
|
|
7
7
|
import { extractUatType } from "./files.js";
|
|
8
|
+
import { splitFrontmatter, parseFrontmatterMap } from "../shared/frontmatter.js";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
function normalizeVerdict(value) {
|
|
11
|
+
if (typeof value !== "string")
|
|
12
|
+
return undefined;
|
|
13
|
+
let verdict = value.trim().toLowerCase();
|
|
14
|
+
if (!verdict)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (verdict === "passed")
|
|
17
|
+
verdict = "pass";
|
|
18
|
+
return verdict;
|
|
19
|
+
}
|
|
20
|
+
function getCaseInsensitive(obj, key) {
|
|
21
|
+
const lowerKey = key.toLowerCase();
|
|
22
|
+
for (const [candidate, value] of Object.entries(obj)) {
|
|
23
|
+
if (candidate.toLowerCase() === lowerKey)
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
8
28
|
// ── Verdict extraction ──────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Extract and normalize the frontmatter `verdict` value.
|
|
31
|
+
*
|
|
32
|
+
* Supports both top-level `verdict` and the hook outcome shape
|
|
33
|
+
* `outcome.verdict`. Returns `undefined` when frontmatter is absent or has no
|
|
34
|
+
* verdict field.
|
|
35
|
+
*/
|
|
36
|
+
export function extractFrontmatterVerdict(content) {
|
|
37
|
+
const [frontmatterLines] = splitFrontmatter(content);
|
|
38
|
+
if (!frontmatterLines)
|
|
39
|
+
return undefined;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = parseYaml(frontmatterLines.join("\n"));
|
|
42
|
+
if (parsed && typeof parsed === "object") {
|
|
43
|
+
const root = parsed;
|
|
44
|
+
const topLevel = normalizeVerdict(getCaseInsensitive(root, "verdict"));
|
|
45
|
+
if (topLevel)
|
|
46
|
+
return topLevel;
|
|
47
|
+
const outcome = getCaseInsensitive(root, "outcome");
|
|
48
|
+
if (outcome && typeof outcome === "object") {
|
|
49
|
+
const nested = normalizeVerdict(getCaseInsensitive(outcome, "verdict"));
|
|
50
|
+
if (nested)
|
|
51
|
+
return nested;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Fall through to the permissive parser used by legacy frontmatter paths.
|
|
57
|
+
}
|
|
58
|
+
const frontmatter = parseFrontmatterMap(frontmatterLines);
|
|
59
|
+
const topLevel = normalizeVerdict(getCaseInsensitive(frontmatter, "verdict"));
|
|
60
|
+
if (topLevel)
|
|
61
|
+
return topLevel;
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
9
64
|
/**
|
|
10
65
|
* Extract and normalize the `verdict` value from YAML frontmatter.
|
|
11
66
|
*
|
|
@@ -17,25 +72,14 @@ import { extractUatType } from "./files.js";
|
|
|
17
72
|
*/
|
|
18
73
|
export function extractVerdict(content) {
|
|
19
74
|
// Primary: YAML frontmatter verdict (canonical format)
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
if (verdictMatch) {
|
|
24
|
-
let v = verdictMatch[1].toLowerCase();
|
|
25
|
-
if (v === "passed")
|
|
26
|
-
v = "pass";
|
|
27
|
-
return v;
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
75
|
+
const [frontmatterLines] = splitFrontmatter(content);
|
|
76
|
+
if (frontmatterLines)
|
|
77
|
+
return extractFrontmatterVerdict(content);
|
|
31
78
|
// Fallback: detect verdict in markdown body (LLM manual writes, #2960).
|
|
32
79
|
// Matches patterns like: **Verdict:** PASS, **Verdict:** ✅ PASS, **Verdict** needs-remediation
|
|
33
80
|
const bodyMatch = content.match(/\*\*Verdict:?\*\*\s*(?:✅\s*)?(\w[\w-]*)/i);
|
|
34
81
|
if (bodyMatch) {
|
|
35
|
-
|
|
36
|
-
if (v === "passed")
|
|
37
|
-
v = "pass";
|
|
38
|
-
return v;
|
|
82
|
+
return normalizeVerdict(bodyMatch[1]);
|
|
39
83
|
}
|
|
40
84
|
return undefined;
|
|
41
85
|
}
|
|
@@ -246,6 +246,76 @@ function hasUnsafeShellSyntax(cmd) {
|
|
|
246
246
|
}
|
|
247
247
|
return false;
|
|
248
248
|
}
|
|
249
|
+
function splitLeadingShellWords(cmd) {
|
|
250
|
+
const words = [];
|
|
251
|
+
let current = "";
|
|
252
|
+
let inSingle = false;
|
|
253
|
+
let inDouble = false;
|
|
254
|
+
let escaped = false;
|
|
255
|
+
for (let i = 0; i < cmd.length; i += 1) {
|
|
256
|
+
const ch = cmd[i];
|
|
257
|
+
if (escaped) {
|
|
258
|
+
current += ch;
|
|
259
|
+
escaped = false;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (ch === "\\" && !inSingle) {
|
|
263
|
+
escaped = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ch === "'" && !inDouble) {
|
|
267
|
+
inSingle = !inSingle;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (ch === "\"" && !inSingle) {
|
|
271
|
+
inDouble = !inDouble;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!inSingle && !inDouble) {
|
|
275
|
+
if (/\s/.test(ch)) {
|
|
276
|
+
if (current) {
|
|
277
|
+
words.push(current);
|
|
278
|
+
current = "";
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if ([";", "|", "&", "<", ">"].includes(ch)) {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
current += ch;
|
|
287
|
+
}
|
|
288
|
+
if (current) {
|
|
289
|
+
words.push(current);
|
|
290
|
+
}
|
|
291
|
+
return words;
|
|
292
|
+
}
|
|
293
|
+
function isCountFlag(token) {
|
|
294
|
+
return (token === "--count" ||
|
|
295
|
+
token.startsWith("--count=") ||
|
|
296
|
+
token === "--count-matches" ||
|
|
297
|
+
token.startsWith("--count-matches=") ||
|
|
298
|
+
/^-[A-Za-z]*c[A-Za-z]*$/.test(token));
|
|
299
|
+
}
|
|
300
|
+
function countSearchWarning(command, exitCode) {
|
|
301
|
+
if (exitCode !== 1)
|
|
302
|
+
return null;
|
|
303
|
+
const trimmed = command.trim();
|
|
304
|
+
if (trimmed.startsWith("!"))
|
|
305
|
+
return null;
|
|
306
|
+
const [tool, ...args] = splitLeadingShellWords(trimmed);
|
|
307
|
+
if (tool !== "grep" && tool !== "rg")
|
|
308
|
+
return null;
|
|
309
|
+
if (!args.some(isCountFlag))
|
|
310
|
+
return null;
|
|
311
|
+
return `verification-gate: warning: '${tool} -c' returns exit 1 when count=0; for absence checks use '! ${tool} -q ...' instead.`;
|
|
312
|
+
}
|
|
313
|
+
function appendStderrWarning(stderr, warning) {
|
|
314
|
+
if (!warning)
|
|
315
|
+
return stderr;
|
|
316
|
+
const trimmed = stderr.trimEnd();
|
|
317
|
+
return trimmed ? `${trimmed}\n${warning}` : warning;
|
|
318
|
+
}
|
|
249
319
|
/**
|
|
250
320
|
* Known executable first-tokens that are safe to run.
|
|
251
321
|
* Lowercase commands, common build/test tools, and npm/yarn/pnpm invocations.
|
|
@@ -411,11 +481,12 @@ export function runVerificationGate(options) {
|
|
|
411
481
|
exitCode = result.status ?? 1;
|
|
412
482
|
stderr = truncate(result.stderr, MAX_OUTPUT_BYTES);
|
|
413
483
|
}
|
|
484
|
+
const warning = countSearchWarning(command, exitCode);
|
|
414
485
|
checks.push({
|
|
415
486
|
command,
|
|
416
487
|
exitCode,
|
|
417
488
|
stdout: truncate(result.stdout, MAX_OUTPUT_BYTES),
|
|
418
|
-
stderr,
|
|
489
|
+
stderr: truncate(appendStderrWarning(stderr, warning), MAX_OUTPUT_BYTES),
|
|
419
490
|
durationMs,
|
|
420
491
|
});
|
|
421
492
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { basename, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
export const GSD_BROWSER_MCP_SERVER_NAME = "gsd-browser";
|
|
8
|
+
function parseJsonEnv(env, name) {
|
|
9
|
+
const raw = env[name];
|
|
10
|
+
if (!raw)
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`Invalid JSON in ${name}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function sanitizeSessionSegment(value) {
|
|
20
|
+
return value
|
|
21
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
22
|
+
.replace(/^-+|-+$/g, "")
|
|
23
|
+
.slice(0, 40);
|
|
24
|
+
}
|
|
25
|
+
function compareSemverLocal(a, b) {
|
|
26
|
+
const left = a.split(".").map(Number);
|
|
27
|
+
const right = b.split(".").map(Number);
|
|
28
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
29
|
+
const leftValue = left[index] || 0;
|
|
30
|
+
const rightValue = right[index] || 0;
|
|
31
|
+
if (leftValue > rightValue)
|
|
32
|
+
return 1;
|
|
33
|
+
if (leftValue < rightValue)
|
|
34
|
+
return -1;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
function parseGsdBrowserVersion(output) {
|
|
39
|
+
return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
|
|
40
|
+
}
|
|
41
|
+
function resolveBundledGsdBrowserPackageVersion() {
|
|
42
|
+
try {
|
|
43
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
44
|
+
const packageJsonPath = requireFromHere.resolve("@opengsd/gsd-browser/package.json");
|
|
45
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
46
|
+
return typeof pkg.version === "string" ? parseGsdBrowserVersion(pkg.version) : null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolvePathGsdBrowserVersion(env) {
|
|
53
|
+
const explicit = env.GSD_BROWSER_PATH_VERSION?.trim();
|
|
54
|
+
if (explicit)
|
|
55
|
+
return parseGsdBrowserVersion(explicit);
|
|
56
|
+
try {
|
|
57
|
+
return parseGsdBrowserVersion(execFileSync("gsd-browser", ["--version"], {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
env,
|
|
60
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
+
timeout: 2000,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function shouldPreferPathGsdBrowser(env) {
|
|
69
|
+
const pathVersion = resolvePathGsdBrowserVersion(env);
|
|
70
|
+
if (!pathVersion)
|
|
71
|
+
return false;
|
|
72
|
+
const bundledVersion = resolveBundledGsdBrowserPackageVersion();
|
|
73
|
+
return !bundledVersion || compareSemverLocal(pathVersion, bundledVersion) > 0;
|
|
74
|
+
}
|
|
75
|
+
export function resolveBundledGsdBrowserCliPath(env = process.env) {
|
|
76
|
+
const explicit = env.GSD_BROWSER_CLI_PATH?.trim() || env.GSD_BROWSER_BIN_PATH?.trim();
|
|
77
|
+
if (explicit)
|
|
78
|
+
return explicit;
|
|
79
|
+
try {
|
|
80
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
81
|
+
const packageJsonPath = requireFromHere.resolve("@opengsd/gsd-browser/package.json");
|
|
82
|
+
const candidate = resolve(packageJsonPath, "..", "bin", "gsd-browser");
|
|
83
|
+
if (existsSync(candidate))
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Fall through to path candidates for source/dist layouts.
|
|
88
|
+
}
|
|
89
|
+
const candidates = [
|
|
90
|
+
resolve(fileURLToPath(new URL("../../../../node_modules/@opengsd/gsd-browser/bin/gsd-browser", import.meta.url))),
|
|
91
|
+
resolve(fileURLToPath(new URL("../../../../node_modules/.bin/gsd-browser", import.meta.url))),
|
|
92
|
+
];
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
if (existsSync(candidate))
|
|
95
|
+
return candidate;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
export function buildGsdBrowserSessionName(projectRoot, suffix) {
|
|
100
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
101
|
+
const base = sanitizeSessionSegment(basename(resolvedProjectRoot)) || "project";
|
|
102
|
+
const hash = createHash("sha1").update(resolvedProjectRoot).digest("hex").slice(0, 8);
|
|
103
|
+
const cleanSuffix = suffix ? sanitizeSessionSegment(suffix) : "";
|
|
104
|
+
return cleanSuffix ? `gsd-${base}-${hash}-${cleanSuffix}` : `gsd-${base}-${hash}`;
|
|
105
|
+
}
|
|
106
|
+
export function resolveGsdBrowserMcpLaunchConfig(projectRoot, env = process.env, options = {}) {
|
|
107
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
108
|
+
const serverName = env.GSD_BROWSER_MCP_NAME?.trim() || GSD_BROWSER_MCP_SERVER_NAME;
|
|
109
|
+
const explicitArgs = parseJsonEnv(env, "GSD_BROWSER_MCP_ARGS");
|
|
110
|
+
const explicitEnv = parseJsonEnv(env, "GSD_BROWSER_MCP_ENV");
|
|
111
|
+
const explicitCommand = env.GSD_BROWSER_MCP_COMMAND?.trim();
|
|
112
|
+
const explicitCliPath = env.GSD_BROWSER_CLI_PATH?.trim() || env.GSD_BROWSER_BIN_PATH?.trim();
|
|
113
|
+
const preferPathCli = !explicitCommand && !explicitCliPath && shouldPreferPathGsdBrowser(env);
|
|
114
|
+
const bundledCliPath = !explicitCommand && !explicitCliPath && !preferPathCli
|
|
115
|
+
? resolveBundledGsdBrowserCliPath(env)
|
|
116
|
+
: null;
|
|
117
|
+
const sessionName = options.sessionName?.trim() || buildGsdBrowserSessionName(resolvedProjectRoot, options.sessionSuffix);
|
|
118
|
+
const command = explicitCommand
|
|
119
|
+
|| explicitCliPath
|
|
120
|
+
|| (preferPathCli ? "gsd-browser" : undefined)
|
|
121
|
+
|| (bundledCliPath ? process.execPath : undefined)
|
|
122
|
+
|| "gsd-browser";
|
|
123
|
+
const args = Array.isArray(explicitArgs) && explicitArgs.length > 0
|
|
124
|
+
? explicitArgs.map(String)
|
|
125
|
+
: [
|
|
126
|
+
...(bundledCliPath ? [bundledCliPath] : []),
|
|
127
|
+
"mcp",
|
|
128
|
+
"--session",
|
|
129
|
+
sessionName,
|
|
130
|
+
"--identity-scope",
|
|
131
|
+
"project",
|
|
132
|
+
"--identity-project",
|
|
133
|
+
resolvedProjectRoot,
|
|
134
|
+
];
|
|
135
|
+
const cwd = env.GSD_BROWSER_MCP_CWD?.trim() || resolvedProjectRoot;
|
|
136
|
+
return {
|
|
137
|
+
serverName,
|
|
138
|
+
command,
|
|
139
|
+
args,
|
|
140
|
+
cwd,
|
|
141
|
+
...(explicitEnv ? { env: explicitEnv } : {}),
|
|
142
|
+
projectRoot: resolvedProjectRoot,
|
|
143
|
+
sessionName,
|
|
144
|
+
};
|
|
145
|
+
}
|
package/dist/rtk.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface ValidateRtkBinaryOptions {
|
|
|
40
40
|
spawnSyncImpl?: typeof spawnSync;
|
|
41
41
|
env?: NodeJS.ProcessEnv;
|
|
42
42
|
}
|
|
43
|
-
export
|
|
43
|
+
export type ValidateRtkBinaryResult = {
|
|
44
|
+
valid: true;
|
|
45
|
+
} | {
|
|
46
|
+
valid: false;
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
export declare function validateRtkBinary(binaryPath: string, options?: ValidateRtkBinaryOptions): ValidateRtkBinaryResult;
|
|
44
50
|
export declare function ensureRtkAvailable(options?: EnsureRtkOptions): Promise<EnsureRtkResult>;
|
|
45
51
|
export declare function bootstrapRtk(options?: EnsureRtkOptions): Promise<EnsureRtkResult>;
|
package/dist/rtk.js
CHANGED
|
@@ -162,19 +162,28 @@ export function rewriteCommandWithRtk(command, options = {}) {
|
|
|
162
162
|
const rewritten = (result.stdout ?? "").trimEnd();
|
|
163
163
|
return rewritten || command;
|
|
164
164
|
}
|
|
165
|
+
function trimSpawnOutput(output) {
|
|
166
|
+
return output?.toString().trim() ?? "";
|
|
167
|
+
}
|
|
165
168
|
export function validateRtkBinary(binaryPath, options = {}) {
|
|
166
169
|
const run = options.spawnSyncImpl ?? spawnSync;
|
|
167
170
|
const result = run(binaryPath, ["rewrite", "git status"], {
|
|
168
171
|
encoding: "utf-8",
|
|
169
172
|
env: buildRtkEnv(options.env ?? process.env),
|
|
170
|
-
stdio: ["ignore", "pipe", "
|
|
173
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
171
174
|
timeout: RTK_REWRITE_TIMEOUT_MS,
|
|
172
175
|
});
|
|
173
176
|
if (result.error)
|
|
174
|
-
return false;
|
|
175
|
-
if (result.status !== 0)
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
return { valid: false, error: result.error.message };
|
|
178
|
+
if (result.status !== 0) {
|
|
179
|
+
const stderr = trimSpawnOutput(result.stderr);
|
|
180
|
+
return { valid: false, error: stderr || `exit code ${result.status ?? "unknown"}` };
|
|
181
|
+
}
|
|
182
|
+
const stdout = trimSpawnOutput(result.stdout);
|
|
183
|
+
if (stdout !== "rtk git status") {
|
|
184
|
+
return { valid: false, error: stdout ? `unexpected output: ${stdout}` : "unexpected empty output" };
|
|
185
|
+
}
|
|
186
|
+
return { valid: true };
|
|
178
187
|
}
|
|
179
188
|
export async function ensureRtkAvailable(options = {}) {
|
|
180
189
|
const env = options.env ?? process.env;
|
|
@@ -190,12 +199,18 @@ export async function ensureRtkAvailable(options = {}) {
|
|
|
190
199
|
}
|
|
191
200
|
const targetDir = options.targetDir ?? getManagedRtkDir(env);
|
|
192
201
|
const managedPath = getManagedRtkPath(process.platform, targetDir);
|
|
193
|
-
if (existsSync(managedPath)
|
|
194
|
-
|
|
202
|
+
if (existsSync(managedPath)) {
|
|
203
|
+
const managedValidation = validateRtkBinary(managedPath, { env });
|
|
204
|
+
if (managedValidation.valid) {
|
|
205
|
+
return { enabled: true, supported: true, available: true, source: "managed", binaryPath: managedPath };
|
|
206
|
+
}
|
|
195
207
|
}
|
|
196
208
|
const systemPath = resolveSystemRtkPath(options.pathValue ?? getPathValue(env));
|
|
197
|
-
if (systemPath
|
|
198
|
-
|
|
209
|
+
if (systemPath) {
|
|
210
|
+
const systemValidation = validateRtkBinary(systemPath, { env });
|
|
211
|
+
if (systemValidation.valid) {
|
|
212
|
+
return { enabled: true, supported: true, available: true, source: "system", binaryPath: systemPath };
|
|
213
|
+
}
|
|
199
214
|
}
|
|
200
215
|
const version = options.releaseVersion ?? RTK_VERSION;
|
|
201
216
|
const assetName = resolveRtkAssetName(process.platform, osArch(), version);
|
|
@@ -241,9 +256,10 @@ export async function ensureRtkAvailable(options = {}) {
|
|
|
241
256
|
if (process.platform !== "win32") {
|
|
242
257
|
chmodSync(managedPath, 0o755);
|
|
243
258
|
}
|
|
244
|
-
|
|
259
|
+
const downloadedValidation = validateRtkBinary(managedPath, { env });
|
|
260
|
+
if (!downloadedValidation.valid) {
|
|
245
261
|
rmSync(managedPath, { force: true });
|
|
246
|
-
throw new Error(
|
|
262
|
+
throw new Error(`downloaded RTK binary failed validation: ${downloadedValidation.error}`);
|
|
247
263
|
}
|
|
248
264
|
options.log?.(`installed RTK ${version} to ${managedPath}`);
|
|
249
265
|
return { enabled: true, supported: true, available: true, source: "downloaded", binaryPath: managedPath };
|