@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- 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/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 +4 -4
- 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 +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +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 +72 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- 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/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- 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/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -37,6 +37,7 @@ import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequir
|
|
|
37
37
|
import { rowToGate } from "./db-gate-rows.js";
|
|
38
38
|
import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
|
|
39
39
|
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
40
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
40
41
|
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, } from "./db-migration-steps.js";
|
|
41
42
|
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
42
43
|
import { createDbOpenState } from "./db-open-state.js";
|
|
@@ -1419,15 +1420,47 @@ export function setMilestoneQueueOrder(order) {
|
|
|
1419
1420
|
throw err;
|
|
1420
1421
|
}
|
|
1421
1422
|
}
|
|
1423
|
+
function getMilestoneStatusForUpdate(milestoneId) {
|
|
1424
|
+
if (!currentDb)
|
|
1425
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1426
|
+
const row = currentDb.prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": milestoneId });
|
|
1427
|
+
return typeof row?.["status"] === "string" ? row["status"] : null;
|
|
1428
|
+
}
|
|
1429
|
+
function writeMilestoneStatus(milestoneId, status, completedAt) {
|
|
1430
|
+
if (!currentDb)
|
|
1431
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1432
|
+
currentDb.prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
|
|
1433
|
+
}
|
|
1422
1434
|
/**
|
|
1423
1435
|
* Update a milestone's status in the database.
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1436
|
+
*
|
|
1437
|
+
* Generic status updates may close milestones, park/unpark open milestones, or
|
|
1438
|
+
* advance planned milestones. They may not reopen a closed milestone; callers
|
|
1439
|
+
* must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
|
|
1426
1440
|
*/
|
|
1427
1441
|
export function updateMilestoneStatus(milestoneId, status, completedAt) {
|
|
1428
1442
|
if (!currentDb)
|
|
1429
1443
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1430
|
-
|
|
1444
|
+
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1445
|
+
if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(status)) {
|
|
1446
|
+
throw new Error(`Cannot update closed milestone ${milestoneId} from ${currentStatus} to ${status}; use gsd_milestone_reopen for an explicit reopen.`);
|
|
1447
|
+
}
|
|
1448
|
+
writeMilestoneStatus(milestoneId, status, completedAt);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Explicit closed -> active transition for gsd_milestone_reopen only.
|
|
1452
|
+
*/
|
|
1453
|
+
export function reopenMilestoneStatus(milestoneId) {
|
|
1454
|
+
if (!currentDb)
|
|
1455
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1456
|
+
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1457
|
+
if (!currentStatus) {
|
|
1458
|
+
throw new Error(`Cannot reopen missing milestone ${milestoneId}`);
|
|
1459
|
+
}
|
|
1460
|
+
if (!isClosedStatus(currentStatus)) {
|
|
1461
|
+
throw new Error(`Cannot reopen milestone ${milestoneId} from status ${currentStatus}; milestone is not closed.`);
|
|
1462
|
+
}
|
|
1463
|
+
writeMilestoneStatus(milestoneId, "active", null);
|
|
1431
1464
|
}
|
|
1432
1465
|
export function getActiveMilestoneFromDb() {
|
|
1433
1466
|
if (!currentDb)
|
|
@@ -2240,7 +2273,7 @@ export function deleteArtifactByPath(path) {
|
|
|
2240
2273
|
}
|
|
2241
2274
|
/**
|
|
2242
2275
|
* Drop hierarchy rows in dependency order inside a transaction. Used by
|
|
2243
|
-
* `gsd recover` to rebuild engine state from markdown.
|
|
2276
|
+
* `gsd recover --confirm` to rebuild engine state from markdown.
|
|
2244
2277
|
*/
|
|
2245
2278
|
export function clearEngineHierarchy() {
|
|
2246
2279
|
if (!currentDb)
|
|
@@ -1908,7 +1908,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1908
1908
|
const result = await checkMarkdownHierarchyAgainstDb(basePath);
|
|
1909
1909
|
if (result.action === "recovery-required") {
|
|
1910
1910
|
ctx.ui.notify(result.message ??
|
|
1911
|
-
`Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover"}\` to import markdown explicitly.`, "warning");
|
|
1911
|
+
`Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover --confirm"}\` to import markdown explicitly.`, "warning");
|
|
1912
1912
|
}
|
|
1913
1913
|
}
|
|
1914
1914
|
catch (err) {
|
|
@@ -24,14 +24,17 @@ function collectServerEntries(servers) {
|
|
|
24
24
|
export function discoverMcpServers(projectDir) {
|
|
25
25
|
const mcpJsonPath = resolve(projectDir, ".mcp.json");
|
|
26
26
|
const settingsPath = resolve(projectDir, ".claude", "settings.json");
|
|
27
|
+
const localSettingsPath = resolve(projectDir, ".claude", "settings.local.json");
|
|
27
28
|
const mcpJson = readJsonFile(mcpJsonPath);
|
|
28
29
|
const settings = readJsonFile(settingsPath, true);
|
|
30
|
+
const localSettings = readJsonFile(localSettingsPath, true);
|
|
29
31
|
const seen = new Set();
|
|
30
32
|
const discovered = [];
|
|
31
33
|
for (const entry of [
|
|
32
34
|
...collectServerEntries(mcpJson?.mcpServers),
|
|
33
35
|
...collectServerEntries(mcpJson?.servers),
|
|
34
36
|
...collectServerEntries(settings?.mcpServers),
|
|
37
|
+
...collectServerEntries(localSettings?.mcpServers),
|
|
35
38
|
]) {
|
|
36
39
|
if (seen.has(entry.name))
|
|
37
40
|
continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { basename, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -157,6 +157,62 @@ function readExistingConfig(configPath) {
|
|
|
157
157
|
throw new Error(`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
function readExistingClaudeCodeSettings(settingsPath) {
|
|
161
|
+
if (!existsSync(settingsPath))
|
|
162
|
+
return {};
|
|
163
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(raw);
|
|
166
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
throw new Error(`Failed to parse ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export function ensureClaudeCodeMcpJsonServersEnabled(projectRoot, serverNames) {
|
|
173
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
174
|
+
assertSafeDirectory(resolvedProjectRoot);
|
|
175
|
+
const targetServerNames = [...new Set(serverNames.filter((name) => name.trim().length > 0))];
|
|
176
|
+
if (targetServerNames.length === 0)
|
|
177
|
+
return false;
|
|
178
|
+
const settingsDir = resolve(resolvedProjectRoot, ".claude");
|
|
179
|
+
const settingsPath = resolve(settingsDir, "settings.local.json");
|
|
180
|
+
const existing = readExistingClaudeCodeSettings(settingsPath);
|
|
181
|
+
const enabled = Array.isArray(existing.enabledMcpjsonServers)
|
|
182
|
+
? [...existing.enabledMcpjsonServers]
|
|
183
|
+
: [];
|
|
184
|
+
const enabledNames = new Set(enabled.filter((value) => typeof value === "string"));
|
|
185
|
+
let changed = !Array.isArray(existing.enabledMcpjsonServers);
|
|
186
|
+
for (const serverName of targetServerNames) {
|
|
187
|
+
if (!enabledNames.has(serverName)) {
|
|
188
|
+
enabled.push(serverName);
|
|
189
|
+
enabledNames.add(serverName);
|
|
190
|
+
changed = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
let nextDisabled = existing.disabledMcpjsonServers;
|
|
194
|
+
if (Array.isArray(existing.disabledMcpjsonServers)) {
|
|
195
|
+
const blockedNames = new Set(targetServerNames);
|
|
196
|
+
const filtered = existing.disabledMcpjsonServers.filter((value) => !blockedNames.has(String(value)));
|
|
197
|
+
if (filtered.length !== existing.disabledMcpjsonServers.length) {
|
|
198
|
+
nextDisabled = filtered;
|
|
199
|
+
changed = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!changed)
|
|
203
|
+
return false;
|
|
204
|
+
const nextSettings = {
|
|
205
|
+
...existing,
|
|
206
|
+
enabledMcpjsonServers: enabled,
|
|
207
|
+
...(Array.isArray(existing.disabledMcpjsonServers) ? { disabledMcpjsonServers: nextDisabled } : {}),
|
|
208
|
+
};
|
|
209
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
210
|
+
writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf-8");
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
export function ensureClaudeCodeMcpJsonServerEnabled(projectRoot, serverName) {
|
|
214
|
+
return ensureClaudeCodeMcpJsonServersEnabled(projectRoot, [serverName]);
|
|
215
|
+
}
|
|
160
216
|
export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
|
|
161
217
|
const resolvedProjectRoot = resolve(projectRoot);
|
|
162
218
|
assertSafeDirectory(resolvedProjectRoot);
|
|
@@ -170,10 +226,18 @@ export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
|
|
|
170
226
|
};
|
|
171
227
|
const desiredServerNames = Object.keys(desiredServers);
|
|
172
228
|
const alreadyPresent = existsSync(configPath);
|
|
173
|
-
const
|
|
229
|
+
const mcpConfigUnchanged = desiredServerNames.every((serverName) => (JSON.stringify(previousServers[serverName] ?? null)
|
|
174
230
|
=== JSON.stringify(desiredServers[serverName])))
|
|
175
231
|
&& existing.mcpServers !== undefined;
|
|
176
|
-
if (
|
|
232
|
+
if (!mcpConfigUnchanged) {
|
|
233
|
+
const nextConfig = {
|
|
234
|
+
...existing,
|
|
235
|
+
mcpServers: nextServers,
|
|
236
|
+
};
|
|
237
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
238
|
+
}
|
|
239
|
+
const localSettingsChanged = ensureClaudeCodeMcpJsonServersEnabled(resolvedProjectRoot, desiredServerNames);
|
|
240
|
+
if (mcpConfigUnchanged && !localSettingsChanged) {
|
|
177
241
|
return {
|
|
178
242
|
configPath,
|
|
179
243
|
serverName: workflowServerName,
|
|
@@ -181,11 +245,6 @@ export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
|
|
|
181
245
|
status: "unchanged",
|
|
182
246
|
};
|
|
183
247
|
}
|
|
184
|
-
const nextConfig = {
|
|
185
|
-
...existing,
|
|
186
|
-
mcpServers: nextServers,
|
|
187
|
-
};
|
|
188
|
-
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
189
248
|
return {
|
|
190
249
|
configPath,
|
|
191
250
|
serverName: workflowServerName,
|
|
@@ -78,9 +78,9 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
|
|
|
78
78
|
markdown,
|
|
79
79
|
beforeDb,
|
|
80
80
|
afterDb: beforeDb,
|
|
81
|
-
recoveryCommand: "/gsd recover",
|
|
81
|
+
recoveryCommand: "/gsd recover --confirm",
|
|
82
82
|
message: `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
|
|
83
83
|
`do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
|
|
84
|
-
"Runtime startup will not import markdown automatically; run `/gsd recover` if markdown should repopulate the database.",
|
|
84
|
+
"Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
|
|
85
85
|
};
|
|
86
86
|
}
|
|
@@ -37,7 +37,13 @@ You are the UAT runner. Execute every check defined in `{{uatPath}}` as deeply a
|
|
|
37
37
|
|
|
38
38
|
Choose the lightest tool that proves the check honestly:
|
|
39
39
|
|
|
40
|
-
- Run
|
|
40
|
+
- Run automated checks with `gsd_uat_exec`
|
|
41
|
+
- Use `uat-artifact-check` as `intent` for static file, grep, structure, or artifact checks.
|
|
42
|
+
- Use `uat-runtime-check` as `intent` for executing tests, scripts, or runtime assertions.
|
|
43
|
+
- Use `uat-browser-check` as `intent` for browser interaction or screenshot-backed UI checks.
|
|
44
|
+
- Use `uat-service-start` as `intent` only when starting or connecting to an app/service.
|
|
45
|
+
- Use `uat-log-inspection` as `intent` for checking logs or captured output files.
|
|
46
|
+
- The result-table evidence mode is separate; do not use `artifact`, `runtime`, or `human-follow-up` as `intent`.
|
|
41
47
|
- Run `grep` / `rg` checks against files
|
|
42
48
|
- Run `node` / other script invocations
|
|
43
49
|
- Read files and verify their contents
|
|
@@ -48,7 +54,7 @@ Choose the lightest tool that proves the check honestly:
|
|
|
48
54
|
For each check, record:
|
|
49
55
|
- The check description (from the UAT file)
|
|
50
56
|
- The evidence mode used: `artifact`, `runtime`, or `human-follow-up`
|
|
51
|
-
- The command or action taken
|
|
57
|
+
- The command or action taken, including the `gsd_uat_exec` evidence ID for automated checks
|
|
52
58
|
- The actual result observed
|
|
53
59
|
- `PASS`, `FAIL`, or `NEEDS-HUMAN`
|
|
54
60
|
|
|
@@ -57,7 +63,7 @@ After running all checks, compute the **overall verdict**:
|
|
|
57
63
|
- `FAIL` — one or more automatable checks failed
|
|
58
64
|
- `PARTIAL` — one or more automatable checks were skipped or returned inconclusive results (not the same as `NEEDS-HUMAN` — use PARTIAL only when the agent itself could not determine pass/fail for a check it was supposed to automate)
|
|
59
65
|
|
|
60
|
-
Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `slice_id: {{sliceId}}`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content
|
|
66
|
+
Call `gsd_summary_save` with `milestone_id: "{{milestoneId}}"`, `slice_id: "{{sliceId}}"`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content`. The tool computes the assessment path, persists to DB/disk, and saves the aggregate UAT gate. The content should follow this logical shape:
|
|
61
67
|
|
|
62
68
|
```markdown
|
|
63
69
|
---
|
|
@@ -86,6 +92,6 @@ date: <ISO 8601 timestamp>
|
|
|
86
92
|
|
|
87
93
|
---
|
|
88
94
|
|
|
89
|
-
**You MUST call `gsd_summary_save` with the UAT result content before finishing.**
|
|
95
|
+
**You MUST call `gsd_summary_save` with `artifact_type: "ASSESSMENT"` and the UAT result content before finishing. Do not write the assessment file directly.**
|
|
90
96
|
|
|
91
97
|
When done, say: "UAT {{sliceId}} complete."
|
|
@@ -32,7 +32,7 @@ GSD ships with bundled skills. Installed skills are listed in `<available_skills
|
|
|
32
32
|
- Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
|
|
33
33
|
- Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
|
|
34
34
|
- In enduring files, write current state only unless the file is explicitly historical.
|
|
35
|
-
- **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
|
|
35
|
+
- **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, terragrunt/aws/kubectl mutations, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
|
|
36
36
|
|
|
37
37
|
If a `GSD Skill Preferences` block appears below, treat it as durable guidance for skills to use, prefer, or avoid unless it conflicts with artifact rules, verification, or higher-priority instructions.
|
|
38
38
|
|
|
@@ -160,4 +160,6 @@ Fix root causes, not symptoms. If applying temporary mitigation, label it and pr
|
|
|
160
160
|
- When debugging, stay curious. Problems are puzzles. Say what's interesting about the failure before reaching for fixes.
|
|
161
161
|
- After completing a task, give a brief summary and 2-4 numbered next-step options; last option is always "Other". Omit the list for strict output formats.
|
|
162
162
|
|
|
163
|
+
If any next step is destructive/outward-facing, present it via `ask_user_questions` and wait for the user's answer before execution. Do not execute a next-step item from a prior plain-text numbered list without fresh confirmation.
|
|
164
|
+
|
|
163
165
|
Good narration states a decision or finding: "Three handlers follow a middleware pattern - using that instead of a custom wrapper." Bad narration just announces the next call ("Reading the file now.") or emits compressed planner notes ("Need create plan artifact maybe read existing plans.").
|
|
@@ -16,6 +16,9 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
16
16
|
{ pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
|
|
17
17
|
{ pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
|
|
18
18
|
{ pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
|
|
19
|
+
{ pattern: /\bterra(form|grunt)\s+(apply|destroy)/i, label: "IaC apply/destroy" },
|
|
20
|
+
{ pattern: /\baws\s+\w+\s+(delete|create|put|remove|terminate)\b/i, label: "AWS mutation" },
|
|
21
|
+
{ pattern: /\bkubectl\s+(delete|apply)\b/i, label: "kubectl mutation" },
|
|
19
22
|
];
|
|
20
23
|
/**
|
|
21
24
|
* Classify a bash command for destructive operations.
|
|
@@ -41,6 +41,16 @@ function tokenizeSkillContext(...parts) {
|
|
|
41
41
|
}
|
|
42
42
|
return tokens;
|
|
43
43
|
}
|
|
44
|
+
function tokenizeUnitType(unitType) {
|
|
45
|
+
const tokens = new Set();
|
|
46
|
+
const value = unitType?.trim().toLowerCase();
|
|
47
|
+
if (!value)
|
|
48
|
+
return tokens;
|
|
49
|
+
tokens.add(value);
|
|
50
|
+
tokens.add(value.replace(/[-_]+/g, " "));
|
|
51
|
+
tokens.add(value.replace(/[-_\s]+/g, ""));
|
|
52
|
+
return tokens;
|
|
53
|
+
}
|
|
44
54
|
function skillMatchesContext(skill, contextTokens) {
|
|
45
55
|
const haystacks = [
|
|
46
56
|
skill.name.toLowerCase(),
|
|
@@ -63,13 +73,20 @@ function ruleMatchesContext(when, contextTokens) {
|
|
|
63
73
|
const whenTokens = tokenizeSkillContext(when);
|
|
64
74
|
return [...whenTokens].some(token => contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)));
|
|
65
75
|
}
|
|
66
|
-
function
|
|
76
|
+
function ruleMatchesUnitType(when, unitType) {
|
|
77
|
+
if (!unitType)
|
|
78
|
+
return false;
|
|
79
|
+
const whenTokens = tokenizeSkillContext(when);
|
|
80
|
+
const unitTokens = tokenizeUnitType(unitType);
|
|
81
|
+
return [...unitTokens].some(token => whenTokens.has(token));
|
|
82
|
+
}
|
|
83
|
+
function resolveSkillRuleMatches(prefs, contextTokens, base, unitType) {
|
|
67
84
|
if (!prefs?.skill_rules?.length)
|
|
68
85
|
return { include: [], avoid: [] };
|
|
69
86
|
const include = [];
|
|
70
87
|
const avoid = [];
|
|
71
88
|
for (const rule of prefs.skill_rules) {
|
|
72
|
-
if (!ruleMatchesContext(rule.when, contextTokens))
|
|
89
|
+
if (!ruleMatchesContext(rule.when, contextTokens) && !ruleMatchesUnitType(rule.when, unitType))
|
|
73
90
|
continue;
|
|
74
91
|
include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
|
|
75
92
|
avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
|
|
@@ -141,7 +158,7 @@ export function buildSkillActivationBlock(params) {
|
|
|
141
158
|
for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
|
|
142
159
|
matched.add(name);
|
|
143
160
|
}
|
|
144
|
-
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
|
|
161
|
+
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base, params.unitType);
|
|
145
162
|
for (const name of ruleMatches.include)
|
|
146
163
|
matched.add(name);
|
|
147
164
|
for (const name of ruleMatches.avoid)
|
|
@@ -313,7 +313,8 @@ export function repairArtifactDbDrift(record, ctx) {
|
|
|
313
313
|
throw new Error(`Artifact/DB status drift in ${record.milestoneId}` +
|
|
314
314
|
`${record.sliceId ? `/${record.sliceId}` : ""}` +
|
|
315
315
|
`${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
|
|
316
|
-
"Runtime will not silently import completion artifacts into DB state
|
|
316
|
+
"Runtime will not silently import completion artifacts into DB state. " +
|
|
317
|
+
"Run `/gsd rebuild markdown` after review to quarantine stale projections and re-render from the DB; use `/gsd recover --confirm` only when markdown should repopulate a lost or corrupt DB.");
|
|
317
318
|
}
|
|
318
319
|
export function describeArtifactDbDriftBlocker(record) {
|
|
319
320
|
if (record.kind === "disk-slice-id-divergence") {
|
|
@@ -329,7 +330,8 @@ export function describeArtifactDbDriftBlocker(record) {
|
|
|
329
330
|
return (`Artifact/DB status drift in ${record.milestoneId}` +
|
|
330
331
|
`${record.sliceId ? `/${record.sliceId}` : ""}` +
|
|
331
332
|
`${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
|
|
332
|
-
"Runtime will not silently import completion artifacts into DB state
|
|
333
|
+
"Runtime will not silently import completion artifacts into DB state. " +
|
|
334
|
+
"Run `/gsd rebuild markdown` after review to quarantine stale projections and re-render from the DB; use `/gsd recover --confirm` only when markdown should repopulate a lost or corrupt DB.");
|
|
333
335
|
}
|
|
334
336
|
export const diskSliceIdDivergenceHandler = {
|
|
335
337
|
kind: "disk-slice-id-divergence",
|
|
@@ -36,7 +36,7 @@ export function detectUnregisteredMilestoneDrift(_state, ctx) {
|
|
|
36
36
|
*/
|
|
37
37
|
export function repairUnregisteredMilestone(record, _ctx) {
|
|
38
38
|
throw new Error(`Milestone ${record.milestoneId} exists only as markdown projection. ` +
|
|
39
|
-
"Runtime reconciliation will not import markdown into the authoritative DB; run `/gsd recover` if this markdown should repopulate the database.");
|
|
39
|
+
"Runtime reconciliation will not import markdown into the authoritative DB; run `/gsd recover --confirm` if this markdown should repopulate the database.");
|
|
40
40
|
}
|
|
41
41
|
export const unregisteredMilestoneHandler = {
|
|
42
42
|
kind: "unregistered-milestone",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// checkboxes) and the DB slice rows for that milestone, then re-renders the
|
|
5
5
|
// ROADMAP projection from the authoritative DB rows.
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { getMilestone, getMilestoneSlices, isDbAvailable, } from "../../gsd-db.js";
|
|
7
|
+
import { getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, } from "../../gsd-db.js";
|
|
8
8
|
import { renderRoadmapFromDb } from "../../markdown-renderer.js";
|
|
9
9
|
import { findMilestoneIds } from "../../milestone-ids.js";
|
|
10
10
|
import { parseRoadmap } from "../../parsers-legacy.js";
|
|
@@ -18,6 +18,15 @@ function arraysEqual(a, b) {
|
|
|
18
18
|
return false;
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
|
+
function getSlicesReadyForDivergenceCheck(milestoneId, dbSlices) {
|
|
22
|
+
const ready = new Set();
|
|
23
|
+
for (const slice of dbSlices) {
|
|
24
|
+
if (isClosedStatus(slice.status) || getSliceTasks(milestoneId, slice.id).length > 0) {
|
|
25
|
+
ready.add(slice.id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return ready;
|
|
29
|
+
}
|
|
21
30
|
function milestoneHasDivergence(basePath, milestoneId) {
|
|
22
31
|
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
23
32
|
if (!roadmapPath || !existsSync(roadmapPath))
|
|
@@ -31,6 +40,10 @@ function milestoneHasDivergence(basePath, milestoneId) {
|
|
|
31
40
|
}
|
|
32
41
|
const dbSlices = getMilestoneSlices(milestoneId);
|
|
33
42
|
const dbSliceMap = new Map(dbSlices.map((s) => [s.id, s]));
|
|
43
|
+
const readySliceIds = getSlicesReadyForDivergenceCheck(milestoneId, dbSlices);
|
|
44
|
+
if (dbSlices.length > 0 && readySliceIds.size === 0) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
34
47
|
const roadmapSliceIds = new Set();
|
|
35
48
|
for (let i = 0; i < roadmap.slices.length; i++) {
|
|
36
49
|
const roadmapSlice = roadmap.slices[i];
|
|
@@ -39,6 +52,8 @@ function milestoneHasDivergence(basePath, milestoneId) {
|
|
|
39
52
|
const dbSlice = dbSliceMap.get(roadmapSlice.id);
|
|
40
53
|
if (!dbSlice)
|
|
41
54
|
return true; // Roadmap has a slice the DB doesn't.
|
|
55
|
+
if (!readySliceIds.has(dbSlice.id))
|
|
56
|
+
continue;
|
|
42
57
|
if (dbSlice.sequence !== expectedSequence)
|
|
43
58
|
return true;
|
|
44
59
|
if (!arraysEqual(dbSlice.depends, roadmapSlice.depends))
|
|
@@ -47,6 +62,8 @@ function milestoneHasDivergence(basePath, milestoneId) {
|
|
|
47
62
|
return true;
|
|
48
63
|
}
|
|
49
64
|
for (const dbSlice of dbSlices) {
|
|
65
|
+
if (!readySliceIds.has(dbSlice.id))
|
|
66
|
+
continue;
|
|
50
67
|
if (!roadmapSliceIds.has(dbSlice.id))
|
|
51
68
|
return true;
|
|
52
69
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// File Purpose: ADR-017 drift-driven State Reconciliation Module entry point.
|
|
3
3
|
// reconcileBeforeDispatch runs before every Dispatch decision and worker spawn.
|
|
4
4
|
import { deriveState as defaultDeriveState, invalidateStateCache as defaultInvalidate, } from "../state.js";
|
|
5
|
+
import { clearParseCache as defaultClearParseCache } from "../files.js";
|
|
5
6
|
import { ReconciliationFailedError, } from "./errors.js";
|
|
6
7
|
import { DRIFT_REGISTRY } from "./registry.js";
|
|
7
8
|
export { ReconciliationFailedError } from "./errors.js";
|
|
@@ -10,6 +11,7 @@ const MAX_PASSES = 2;
|
|
|
10
11
|
const defaultDeps = {
|
|
11
12
|
invalidateStateCache: defaultInvalidate,
|
|
12
13
|
deriveState: defaultDeriveState,
|
|
14
|
+
clearParseCache: defaultClearParseCache,
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
17
|
* Drift-driven pre-dispatch reconciliation per ADR-017.
|
|
@@ -27,6 +29,7 @@ const defaultDeps = {
|
|
|
27
29
|
*/
|
|
28
30
|
export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
29
31
|
const registry = deps.registry ?? DRIFT_REGISTRY;
|
|
32
|
+
const clearParseCache = deps.clearParseCache ?? defaultClearParseCache;
|
|
30
33
|
const repaired = [];
|
|
31
34
|
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
32
35
|
deps.invalidateStateCache();
|
|
@@ -67,6 +70,9 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
|
|
|
67
70
|
failures.push({ drift: record, cause });
|
|
68
71
|
}
|
|
69
72
|
}
|
|
73
|
+
if (repairedThisPass) {
|
|
74
|
+
clearParseCache();
|
|
75
|
+
}
|
|
70
76
|
if (blockers.length > 0) {
|
|
71
77
|
let blockerState = stateSnapshot;
|
|
72
78
|
if (repairedThisPass) {
|
|
@@ -35,7 +35,7 @@ function formatNeedsRemediationBlocker(milestoneId) {
|
|
|
35
35
|
return [
|
|
36
36
|
`Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
|
|
37
37
|
`Fix options:`,
|
|
38
|
-
`1.
|
|
38
|
+
`1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
|
|
39
39
|
`2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
|
|
40
40
|
`3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
|
|
41
41
|
].join("\n");
|
|
@@ -204,10 +204,16 @@ export function invalidateStateCache() {
|
|
|
204
204
|
/**
|
|
205
205
|
* Returns the ID of the first incomplete milestone, or null if all are complete.
|
|
206
206
|
*/
|
|
207
|
+
function getRequestedMilestoneLock() {
|
|
208
|
+
const lock = process.env.GSD_MILESTONE_LOCK?.trim();
|
|
209
|
+
return lock || undefined;
|
|
210
|
+
}
|
|
207
211
|
export async function getActiveMilestoneId(basePath) {
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
212
|
+
// Milestone-scoped execution. Parallel workers and explicit solo commands
|
|
213
|
+
// such as `/gsd auto M002` both set GSD_MILESTONE_LOCK; state derivation must
|
|
214
|
+
// honor it so recovery/adoption sees the requested milestone, not the first
|
|
215
|
+
// open milestone in queue order.
|
|
216
|
+
const milestoneLock = getRequestedMilestoneLock();
|
|
211
217
|
if (milestoneLock) {
|
|
212
218
|
if (isDbAvailable()) {
|
|
213
219
|
const locked = getAllMilestones().find(m => m.id === milestoneLock);
|
|
@@ -573,7 +579,7 @@ function checkReplanTrigger(basePath, milestoneId, sliceId) {
|
|
|
573
579
|
export async function deriveStateFromDb(basePath, artifactReadRoot = basePath) {
|
|
574
580
|
const requirements = getRequirementCounts();
|
|
575
581
|
const allMilestones = getAllMilestones();
|
|
576
|
-
const milestoneLock =
|
|
582
|
+
const milestoneLock = getRequestedMilestoneLock();
|
|
577
583
|
const milestones = milestoneLock
|
|
578
584
|
? allMilestones.filter(m => m.id === milestoneLock)
|
|
579
585
|
: allMilestones;
|
|
@@ -779,13 +785,10 @@ export async function _deriveStateImpl(basePath, opts) {
|
|
|
779
785
|
const diskIds = findMilestoneIds(basePath);
|
|
780
786
|
const customOrder = loadQueueOrder(basePath);
|
|
781
787
|
const milestoneIds = sortByQueueOrder(diskIds, customOrder);
|
|
782
|
-
// ──
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
// don't exist). This gives each worker complete isolation without
|
|
787
|
-
// modifying any other state derivation logic.
|
|
788
|
-
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
788
|
+
// ── Milestone-scoped execution ─────────────────────────────────────────
|
|
789
|
+
// Parallel workers and explicit solo recovery both scope auto-mode to one
|
|
790
|
+
// milestone through GSD_MILESTONE_LOCK.
|
|
791
|
+
const milestoneLock = getRequestedMilestoneLock();
|
|
789
792
|
if (milestoneLock && milestoneIds.includes(milestoneLock)) {
|
|
790
793
|
milestoneIds.length = 0;
|
|
791
794
|
milestoneIds.push(milestoneLock);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Resolve phase-aware tool surfaces for GSD model presentations.
|
|
3
|
+
export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
|
|
4
|
+
"gsd_uat_exec",
|
|
5
|
+
"gsd_uat_result_save",
|
|
6
|
+
"gsd_resume",
|
|
7
|
+
"gsd_milestone_status",
|
|
8
|
+
"gsd_journal_query",
|
|
9
|
+
];
|
|
10
|
+
export const RUN_UAT_FORBIDDEN_TOOL_NAMES = [
|
|
11
|
+
"edit",
|
|
12
|
+
"write",
|
|
13
|
+
"gsd_exec",
|
|
14
|
+
"gsd_summary_save",
|
|
15
|
+
"gsd_save_gate_result",
|
|
16
|
+
"search-the-web",
|
|
17
|
+
"WebSearch",
|
|
18
|
+
"Bash",
|
|
19
|
+
"Write",
|
|
20
|
+
"Edit",
|
|
21
|
+
"mcp__gsd-workflow__*",
|
|
22
|
+
];
|
|
23
|
+
export const RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES = [
|
|
24
|
+
"Read",
|
|
25
|
+
"Glob",
|
|
26
|
+
"Grep",
|
|
27
|
+
];
|
|
28
|
+
const WORKFLOW_ALIAS_TO_CANONICAL = {
|
|
29
|
+
gsd_save_decision: "gsd_decision_save",
|
|
30
|
+
gsd_update_requirement: "gsd_requirement_update",
|
|
31
|
+
gsd_save_requirement: "gsd_requirement_save",
|
|
32
|
+
gsd_save_summary: "gsd_summary_save",
|
|
33
|
+
gsd_generate_milestone_id: "gsd_milestone_generate_id",
|
|
34
|
+
gsd_milestone_plan: "gsd_plan_milestone",
|
|
35
|
+
gsd_slice_plan: "gsd_plan_slice",
|
|
36
|
+
gsd_task_plan: "gsd_plan_task",
|
|
37
|
+
gsd_slice_replan: "gsd_replan_slice",
|
|
38
|
+
gsd_complete_slice: "gsd_slice_complete",
|
|
39
|
+
gsd_milestone_complete: "gsd_complete_milestone",
|
|
40
|
+
gsd_milestone_validate: "gsd_validate_milestone",
|
|
41
|
+
gsd_roadmap_reassess: "gsd_reassess_roadmap",
|
|
42
|
+
gsd_complete_task: "gsd_task_complete",
|
|
43
|
+
gsd_reopen_task: "gsd_task_reopen",
|
|
44
|
+
gsd_reopen_slice: "gsd_slice_reopen",
|
|
45
|
+
gsd_reopen_milestone: "gsd_milestone_reopen",
|
|
46
|
+
};
|
|
47
|
+
export function canonicalWorkflowToolName(toolName) {
|
|
48
|
+
const mcp = parseMcpToolName(toolName);
|
|
49
|
+
const baseName = mcp?.tool ?? toolName;
|
|
50
|
+
return WORKFLOW_ALIAS_TO_CANONICAL[baseName] ?? baseName;
|
|
51
|
+
}
|
|
52
|
+
export function parseMcpToolName(toolName) {
|
|
53
|
+
if (!toolName.startsWith("mcp__"))
|
|
54
|
+
return null;
|
|
55
|
+
const toolSeparator = toolName.indexOf("__", "mcp__".length);
|
|
56
|
+
if (toolSeparator < 0)
|
|
57
|
+
return null;
|
|
58
|
+
return {
|
|
59
|
+
server: toolName.slice("mcp__".length, toolSeparator),
|
|
60
|
+
tool: toolName.slice(toolSeparator + 2),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function toWorkflowMcpToolName(serverName, toolName) {
|
|
64
|
+
return `mcp__${serverName}__${canonicalWorkflowToolName(toolName)}`;
|
|
65
|
+
}
|
|
66
|
+
function dedupe(values) {
|
|
67
|
+
return [...new Set(values)];
|
|
68
|
+
}
|
|
69
|
+
function addBlockedTool(blocked, name, reason) {
|
|
70
|
+
if (!blocked.some((entry) => entry.name === name)) {
|
|
71
|
+
blocked.push({ name, reason });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function buildRunUatCanonicalToolNames(options = {}) {
|
|
75
|
+
return dedupe([
|
|
76
|
+
...RUN_UAT_WORKFLOW_TOOL_NAMES,
|
|
77
|
+
...(options.includeBrowserTools ?? []),
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
export function resolveToolPresentationPlan(options) {
|
|
81
|
+
const requested = options.requestedToolNames ?? (options.phase === "run-uat"
|
|
82
|
+
? buildRunUatCanonicalToolNames({ includeBrowserTools: options.includeBrowserTools })
|
|
83
|
+
: []);
|
|
84
|
+
const available = new Set(options.availableToolNames ?? requested);
|
|
85
|
+
const aliases = [];
|
|
86
|
+
const blockedToolNames = [];
|
|
87
|
+
const allowed = [];
|
|
88
|
+
for (const name of requested) {
|
|
89
|
+
const canonical = canonicalWorkflowToolName(name);
|
|
90
|
+
if (canonical !== name)
|
|
91
|
+
aliases.push({ requested: name, canonical });
|
|
92
|
+
if (!available.has(name) && !available.has(canonical)) {
|
|
93
|
+
addBlockedTool(blockedToolNames, canonical, "not registered or provider-incompatible");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
allowed.push(canonical);
|
|
97
|
+
}
|
|
98
|
+
const allowedToolNames = dedupe(allowed);
|
|
99
|
+
const workflowServerName = options.workflowMcpServerName || "gsd-workflow";
|
|
100
|
+
const presentedToolNames = options.surface === "claude-code-sdk" || options.surface === "mcp"
|
|
101
|
+
? allowedToolNames.map((name) => name.startsWith("gsd_") || name === "ask_user_questions"
|
|
102
|
+
? toWorkflowMcpToolName(workflowServerName, name)
|
|
103
|
+
: name)
|
|
104
|
+
: allowedToolNames;
|
|
105
|
+
if (options.phase === "run-uat") {
|
|
106
|
+
for (const forbidden of RUN_UAT_FORBIDDEN_TOOL_NAMES) {
|
|
107
|
+
addBlockedTool(blockedToolNames, forbidden, "forbidden during run-uat");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
phase: options.phase,
|
|
112
|
+
surface: options.surface,
|
|
113
|
+
model: options.model,
|
|
114
|
+
allowedToolNames,
|
|
115
|
+
presentedToolNames,
|
|
116
|
+
blockedToolNames,
|
|
117
|
+
aliases,
|
|
118
|
+
diagnostics: [],
|
|
119
|
+
};
|
|
120
|
+
}
|