@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.955e4da0
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/bg-shell/utilities.js +2 -2
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +8 -2
- package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
- package/dist/resources/extensions/gsd/auto/phases.js +6 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +8 -6
- package/dist/resources/extensions/gsd/auto-start.js +8 -13
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -270
- package/dist/resources/extensions/gsd/auto.js +4 -7
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +26 -4
- package/dist/resources/extensions/gsd/captures.js +5 -13
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +372 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +8 -10
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +9 -2
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
- package/dist/resources/extensions/gsd/guided-flow.js +34 -3
- package/dist/resources/extensions/gsd/migrate/safety.js +17 -9
- package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
- package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +7 -5
- package/dist/resources/extensions/gsd/paths.js +10 -24
- package/dist/resources/extensions/gsd/preferences.js +14 -0
- package/dist/resources/extensions/gsd/recovery-classification.js +12 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +287 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +17 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -10
- package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- 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/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +13 -13
- package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
- 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/dist/worktree-status-banner.js +7 -3
- package/package.json +1 -1
- 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/package.json +5 -5
- 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 +30 -21
- 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/models.generated.d.ts +266 -35
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +235 -46
- 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/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/utilities.ts +2 -2
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +6 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
- package/src/resources/extensions/gsd/auto/phases.ts +10 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +12 -5
- package/src/resources/extensions/gsd/auto-start.ts +8 -14
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +20 -280
- package/src/resources/extensions/gsd/auto.ts +12 -9
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +25 -3
- package/src/resources/extensions/gsd/captures.ts +5 -14
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +453 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +8 -11
- package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +10 -3
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
- package/src/resources/extensions/gsd/guided-flow.ts +34 -3
- package/src/resources/extensions/gsd/migrate/safety.ts +15 -7
- package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
- package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +6 -5
- package/src/resources/extensions/gsd/paths.ts +9 -22
- package/src/resources/extensions/gsd/preferences.ts +18 -0
- package/src/resources/extensions/gsd/recovery-classification.ts +14 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +91 -1
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
- package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +308 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +17 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +12 -10
- package/src/resources/skills/gsd-browser/SKILL.md +1 -1
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → C24pqUd-aru-l0Dp0gLZP}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → C24pqUd-aru-l0Dp0gLZP}/_ssgManifest.js +0 -0
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Fix: removeWorktree should query `git worktree list` to find the actual
|
|
10
10
|
* registered path when the computed path doesn't match.
|
|
11
|
+
*
|
|
12
|
+
* New worktrees are created at the canonical `.gsd-worktrees/` sibling and
|
|
13
|
+
* never cross the symlink, so this scenario only applies to LEGACY worktrees
|
|
14
|
+
* (created under `.gsd/worktrees/` by older versions). The test creates the
|
|
15
|
+
* worktree at the legacy location directly to keep that coverage.
|
|
11
16
|
*/
|
|
12
17
|
import { mkdtempSync, mkdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
13
18
|
import { join } from "node:path";
|
|
@@ -15,7 +20,6 @@ import { tmpdir } from "node:os";
|
|
|
15
20
|
import { execSync } from "node:child_process";
|
|
16
21
|
|
|
17
22
|
import {
|
|
18
|
-
createWorktree,
|
|
19
23
|
removeWorktree,
|
|
20
24
|
listWorktrees,
|
|
21
25
|
worktreePath,
|
|
@@ -64,13 +68,15 @@ test('worktree-symlink-removal removes the git-registered symlink target safely'
|
|
|
64
68
|
run("git add .", base);
|
|
65
69
|
run('git commit -m "init"', base);
|
|
66
70
|
|
|
67
|
-
// Create a worktree
|
|
68
|
-
// the worktree at the external path
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
// Create a LEGACY worktree through the symlinked .gsd path — git resolves
|
|
72
|
+
// the symlink and registers the worktree at the external path. (Current
|
|
73
|
+
// createWorktree() uses the canonical .gsd-worktrees/ sibling instead.)
|
|
74
|
+
const legacyPath = join(base, ".gsd", "worktrees", "M002");
|
|
75
|
+
run(`git worktree add -b milestone/M002 "${legacyPath}"`, base);
|
|
76
|
+
assert.ok(existsSync(legacyPath), "worktree created");
|
|
71
77
|
|
|
72
78
|
// Verify worktree was created at the resolved (external) path
|
|
73
|
-
const realWtPath = realpathSync(
|
|
79
|
+
const realWtPath = realpathSync(legacyPath);
|
|
74
80
|
assert.ok(
|
|
75
81
|
realWtPath.startsWith(externalState),
|
|
76
82
|
`worktree real path (${realWtPath}) is under external state dir`,
|
|
@@ -116,8 +116,8 @@ describe("worktree-teardown-safety", () => {
|
|
|
116
116
|
|
|
117
117
|
const wtPathResult = worktreePath(tempDir, "anything");
|
|
118
118
|
assertTrue(
|
|
119
|
-
wtPathResult.startsWith(join(tempDir, ".gsd
|
|
120
|
-
"worktreePath returns path under .gsd
|
|
119
|
+
wtPathResult.startsWith(join(tempDir, ".gsd-worktrees")),
|
|
120
|
+
"worktreePath returns path under the canonical .gsd-worktrees/ container",
|
|
121
121
|
);
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -803,3 +803,45 @@ test('write-gate: resetWriteGateState persists through dangling .gsd symlink', (
|
|
|
803
803
|
} catch { /* swallow */ }
|
|
804
804
|
}
|
|
805
805
|
});
|
|
806
|
+
|
|
807
|
+
// ─── Scenario 31: hydrate in-memory gate state from persisted snapshot (MCP subprocess) ──
|
|
808
|
+
|
|
809
|
+
test('write-gate: getPendingGate hydrates from disk when workflow MCP verified gate in child process', () => {
|
|
810
|
+
const base = join(tmpdir(), `gsd-write-gate-mcp-hydrate-${randomUUID()}`);
|
|
811
|
+
const stateFilePath = join(base, '.gsd', 'runtime', 'write-gate-state.json');
|
|
812
|
+
const originalEnv = process.env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
813
|
+
const gateId = 'depth_verification_M005_confirm';
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
process.env.GSD_PERSIST_WRITE_GATE_STATE = '1';
|
|
817
|
+
mkdirSync(join(base, '.gsd', 'runtime'), { recursive: true });
|
|
818
|
+
clearDiscussionFlowState(base);
|
|
819
|
+
setPendingGate(gateId, base);
|
|
820
|
+
|
|
821
|
+
writeFileSync(stateFilePath, JSON.stringify({
|
|
822
|
+
verifiedDepthMilestones: ['M005'],
|
|
823
|
+
verifiedApprovalGates: [gateId],
|
|
824
|
+
activeQueuePhase: false,
|
|
825
|
+
pendingGateId: null,
|
|
826
|
+
}, null, 2), 'utf-8');
|
|
827
|
+
|
|
828
|
+
assert.strictEqual(getPendingGate(base), null, 'stale in-memory pending must refresh from disk');
|
|
829
|
+
assert.strictEqual(isMilestoneDepthVerified('M005', base), true, 'verified milestone must hydrate from disk');
|
|
830
|
+
assert.deepEqual(loadWriteGateSnapshot(base), {
|
|
831
|
+
verifiedDepthMilestones: ['M005'],
|
|
832
|
+
verifiedApprovalGates: [gateId],
|
|
833
|
+
activeQueuePhase: false,
|
|
834
|
+
pendingGateId: null,
|
|
835
|
+
});
|
|
836
|
+
} finally {
|
|
837
|
+
if (originalEnv === undefined) {
|
|
838
|
+
delete process.env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
839
|
+
} else {
|
|
840
|
+
process.env.GSD_PERSIST_WRITE_GATE_STATE = originalEnv;
|
|
841
|
+
}
|
|
842
|
+
clearDiscussionFlowState(base);
|
|
843
|
+
try {
|
|
844
|
+
rmSync(base, { recursive: true, force: true });
|
|
845
|
+
} catch { /* swallow */ }
|
|
846
|
+
}
|
|
847
|
+
});
|
|
@@ -15,20 +15,11 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
|
|
17
17
|
import type { CompleteSliceParams } from "../types.js";
|
|
18
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
19
18
|
import {
|
|
20
|
-
|
|
21
|
-
insertMilestone,
|
|
22
|
-
insertSlice,
|
|
23
|
-
getSlice,
|
|
24
|
-
getSliceTasks,
|
|
25
|
-
getMilestone,
|
|
26
|
-
updateSliceStatus,
|
|
19
|
+
completeSliceCascade,
|
|
27
20
|
setSliceSummaryMd,
|
|
28
21
|
saveGateResult,
|
|
29
22
|
getPendingGatesForTurn,
|
|
30
|
-
getMilestoneSlices,
|
|
31
|
-
updateMilestoneStatus,
|
|
32
23
|
} from "../gsd-db.js";
|
|
33
24
|
import { getGatesForTurn } from "../gate-registry.js";
|
|
34
25
|
import { gsdProjectionRoot, clearPathCache, resolveMilestoneFile } from "../paths.js";
|
|
@@ -371,60 +362,34 @@ export async function handleCompleteSlice(
|
|
|
371
362
|
};
|
|
372
363
|
}
|
|
373
364
|
|
|
374
|
-
// ──
|
|
365
|
+
// ── Atomic completion cascade (guards + writes in one transaction) ───────
|
|
375
366
|
const completedAt = new Date().toISOString();
|
|
376
367
|
let guardError: string | null = null;
|
|
377
368
|
let existingSummaryMd = "";
|
|
378
369
|
let duplicateComplete = false;
|
|
379
370
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Only block if they exist and are closed.
|
|
384
|
-
const milestone = getMilestone(params.milestoneId);
|
|
385
|
-
if (milestone && isClosedStatus(milestone.status)) {
|
|
386
|
-
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
391
|
-
existingSummaryMd = slice?.full_summary_md?.trim() ?? "";
|
|
392
|
-
if (slice && isClosedStatus(slice.status)) {
|
|
393
|
-
duplicateComplete = true;
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Verify all tasks are complete
|
|
398
|
-
const tasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
399
|
-
if (tasks.length === 0) {
|
|
400
|
-
guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
405
|
-
if (incompleteTasks.length > 0) {
|
|
406
|
-
const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
407
|
-
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// All guards passed — perform writes. Preserve existing planning metadata:
|
|
412
|
-
// completion should not overwrite title/risk/depends/demo/sequence.
|
|
413
|
-
insertMilestone({ id: params.milestoneId, title: params.milestoneId });
|
|
414
|
-
if (!slice) {
|
|
415
|
-
insertSlice({ id: params.sliceId, milestoneId: params.milestoneId, title: params.sliceTitle || params.sliceId });
|
|
416
|
-
}
|
|
417
|
-
updateSliceStatus(params.milestoneId, params.sliceId, "complete", completedAt);
|
|
418
|
-
|
|
419
|
-
const updatedSlices = getMilestoneSlices(params.milestoneId);
|
|
420
|
-
if (
|
|
421
|
-
milestone?.status === "planned" &&
|
|
422
|
-
updatedSlices.length > 0 &&
|
|
423
|
-
updatedSlices.every((s) => isClosedStatus(s.status))
|
|
424
|
-
) {
|
|
425
|
-
updateMilestoneStatus(params.milestoneId, "active");
|
|
426
|
-
}
|
|
371
|
+
const outcome = completeSliceCascade(params.milestoneId, params.sliceId, {
|
|
372
|
+
sliceTitle: params.sliceTitle,
|
|
373
|
+
completedAt,
|
|
427
374
|
});
|
|
375
|
+
if (outcome.ok) {
|
|
376
|
+
existingSummaryMd = outcome.existingSummaryMd;
|
|
377
|
+
duplicateComplete = outcome.duplicate;
|
|
378
|
+
} else {
|
|
379
|
+
switch (outcome.reason) {
|
|
380
|
+
case "milestone-closed":
|
|
381
|
+
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${outcome.status})`;
|
|
382
|
+
break;
|
|
383
|
+
case "no-tasks":
|
|
384
|
+
guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
|
|
385
|
+
break;
|
|
386
|
+
case "incomplete-tasks": {
|
|
387
|
+
const incompleteIds = outcome.incomplete.map((t) => `${t.id} (status: ${t.status})`).join(", ");
|
|
388
|
+
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
428
393
|
|
|
429
394
|
if (duplicateComplete) {
|
|
430
395
|
const staleSummaryPath = sliceSummaryPath(
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { realpathSync } from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
|
|
14
|
+
import { findWorktreeSegment } from "../worktree-root.js";
|
|
14
15
|
import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
|
|
15
16
|
|
|
16
17
|
export interface ExecToolParams {
|
|
@@ -201,11 +202,10 @@ function normalizeScanPath(value: string): string {
|
|
|
201
202
|
|
|
202
203
|
function parseWorktreeBase(baseDir: string): { originalRoot: string; worktreeRoot: string } | null {
|
|
203
204
|
const normalizedBase = normalizeScanPath(baseDir);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
if (markerIndex <= 0) return null;
|
|
205
|
+
const segment = findWorktreeSegment(normalizedBase);
|
|
206
|
+
if (!segment || segment.gsdIdx <= 0) return null;
|
|
207
207
|
return {
|
|
208
|
-
originalRoot: normalizedBase.slice(0,
|
|
208
|
+
originalRoot: normalizedBase.slice(0, segment.gsdIdx),
|
|
209
209
|
worktreeRoot: normalizedBase,
|
|
210
210
|
};
|
|
211
211
|
}
|
|
@@ -297,7 +297,7 @@ function scriptReferencesOriginalRootFromWorktree(script: string, baseDir: strin
|
|
|
297
297
|
const normalizedScript = script.replace(/\\/g, "/");
|
|
298
298
|
return comparablePathVariants(parsed.originalRoot).some((originalRoot) => {
|
|
299
299
|
const originalRootPattern = new RegExp(
|
|
300
|
-
`${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd
|
|
300
|
+
`${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd(?:-worktrees|/worktrees)(?:/|$)))`,
|
|
301
301
|
);
|
|
302
302
|
return originalRootPattern.test(normalizedScript);
|
|
303
303
|
});
|
|
@@ -10,16 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
-
getMilestone,
|
|
14
13
|
getMilestoneSlices,
|
|
15
14
|
getSliceTasks,
|
|
16
|
-
|
|
17
|
-
updateSliceStatus,
|
|
18
|
-
updateTaskStatus,
|
|
19
|
-
transaction,
|
|
15
|
+
reopenMilestoneCascade,
|
|
20
16
|
} from "../gsd-db.js";
|
|
21
17
|
import { invalidateStateCache } from "../state.js";
|
|
22
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
23
18
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
24
19
|
import { writeManifest } from "../workflow-manifest.js";
|
|
25
20
|
import { appendEvent } from "../workflow-events.js";
|
|
@@ -53,40 +48,18 @@ export async function handleReopenMilestone(
|
|
|
53
48
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
// ──
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
guardError = `milestone not found: ${params.milestoneId}`;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (!isClosedStatus(milestone.status)) {
|
|
68
|
-
guardError = `milestone ${params.milestoneId} is not closed (status: ${milestone.status}) — nothing to reopen`;
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
reopenMilestoneStatus(params.milestoneId);
|
|
73
|
-
|
|
74
|
-
const slices = getMilestoneSlices(params.milestoneId);
|
|
75
|
-
slicesResetCount = slices.length;
|
|
76
|
-
|
|
77
|
-
for (const slice of slices) {
|
|
78
|
-
updateSliceStatus(params.milestoneId, slice.id, "in_progress");
|
|
79
|
-
const tasks = getSliceTasks(params.milestoneId, slice.id);
|
|
80
|
-
tasksResetCount += tasks.length;
|
|
81
|
-
for (const task of tasks) {
|
|
82
|
-
updateTaskStatus(params.milestoneId, slice.id, task.id, "pending");
|
|
83
|
-
}
|
|
51
|
+
// ── Atomic reopen cascade (guards + writes in one transaction) ───────────
|
|
52
|
+
const outcome = reopenMilestoneCascade(params.milestoneId);
|
|
53
|
+
if (!outcome.ok) {
|
|
54
|
+
switch (outcome.reason) {
|
|
55
|
+
case "milestone-not-found":
|
|
56
|
+
return { error: `milestone not found: ${params.milestoneId}` };
|
|
57
|
+
case "milestone-not-closed":
|
|
58
|
+
return { error: `milestone ${params.milestoneId} is not closed (status: ${outcome.status}) — nothing to reopen` };
|
|
84
59
|
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
if (guardError) {
|
|
88
|
-
return { error: guardError };
|
|
89
60
|
}
|
|
61
|
+
const slicesResetCount = outcome.slicesReset;
|
|
62
|
+
const tasksResetCount = outcome.tasksReset;
|
|
90
63
|
|
|
91
64
|
// ── Invalidate caches ────────────────────────────────────────────────────
|
|
92
65
|
invalidateStateCache();
|
|
@@ -12,15 +12,10 @@
|
|
|
12
12
|
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
-
getMilestone,
|
|
16
|
-
getSlice,
|
|
17
15
|
getSliceTasks,
|
|
18
|
-
|
|
19
|
-
updateTaskStatus,
|
|
20
|
-
transaction,
|
|
16
|
+
reopenSliceCascade,
|
|
21
17
|
} from "../gsd-db.js";
|
|
22
18
|
import { invalidateStateCache } from "../state.js";
|
|
23
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
24
19
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
25
20
|
import { writeManifest } from "../workflow-manifest.js";
|
|
26
21
|
import { appendEvent } from "../workflow-events.js";
|
|
@@ -57,44 +52,21 @@ export async function handleReopenSlice(
|
|
|
57
52
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
// ──
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
76
|
-
if (!slice) {
|
|
77
|
-
guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (!isClosedStatus(slice.status)) {
|
|
81
|
-
guardError = `slice ${params.sliceId} is not complete (status: ${slice.status}) — nothing to reopen`;
|
|
82
|
-
return;
|
|
55
|
+
// ── Atomic reopen cascade (guards + writes in one transaction) ───────────
|
|
56
|
+
const outcome = reopenSliceCascade(params.milestoneId, params.sliceId);
|
|
57
|
+
if (!outcome.ok) {
|
|
58
|
+
switch (outcome.reason) {
|
|
59
|
+
case "milestone-not-found":
|
|
60
|
+
return { error: `milestone not found: ${params.milestoneId}` };
|
|
61
|
+
case "milestone-closed":
|
|
62
|
+
return { error: `cannot reopen slice in a closed milestone: ${params.milestoneId} (status: ${outcome.status})` };
|
|
63
|
+
case "slice-not-found":
|
|
64
|
+
return { error: `slice not found: ${params.milestoneId}/${params.sliceId}` };
|
|
65
|
+
case "slice-not-complete":
|
|
66
|
+
return { error: `slice ${params.sliceId} is not complete (status: ${outcome.status}) — nothing to reopen` };
|
|
83
67
|
}
|
|
84
|
-
|
|
85
|
-
// Fetch tasks inside txn so the list is consistent with the slice status check
|
|
86
|
-
const tasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
87
|
-
tasksResetCount = tasks.length;
|
|
88
|
-
|
|
89
|
-
updateSliceStatus(params.milestoneId, params.sliceId, "in_progress");
|
|
90
|
-
for (const task of tasks) {
|
|
91
|
-
updateTaskStatus(params.milestoneId, params.sliceId, task.id, "pending");
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (guardError) {
|
|
96
|
-
return { error: guardError };
|
|
97
68
|
}
|
|
69
|
+
const tasksResetCount = outcome.tasksReset;
|
|
98
70
|
|
|
99
71
|
// ── Invalidate caches ────────────────────────────────────────────────────
|
|
100
72
|
invalidateStateCache();
|
|
@@ -11,14 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
getSlice,
|
|
15
|
-
getSliceTasks,
|
|
16
14
|
isDbAvailable,
|
|
17
|
-
|
|
18
|
-
updateSliceStatus,
|
|
19
|
-
updateTaskStatus,
|
|
15
|
+
skipSliceCascade,
|
|
20
16
|
} from "../gsd-db.js";
|
|
21
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
22
17
|
|
|
23
18
|
/**
|
|
24
19
|
* Input parameters for {@link handleSkipSlice}.
|
|
@@ -90,44 +85,23 @@ export function handleSkipSlice(params: SkipSliceParams): SkipSliceResult {
|
|
|
90
85
|
throw new Error("handleSkipSlice: GSD database is not available");
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
// ──
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
wasAlreadySkipped = slice.status === "skipped";
|
|
113
|
-
if (!wasAlreadySkipped) {
|
|
114
|
-
updateSliceStatus(params.milestoneId, params.sliceId, "skipped");
|
|
88
|
+
// ── Atomic skip cascade (guards + writes in one transaction) ────────────
|
|
89
|
+
const outcome = skipSliceCascade(params.milestoneId, params.sliceId);
|
|
90
|
+
if (!outcome.ok) {
|
|
91
|
+
switch (outcome.reason) {
|
|
92
|
+
case "slice-not-found":
|
|
93
|
+
return {
|
|
94
|
+
...base,
|
|
95
|
+
error: `Slice ${params.sliceId} not found in milestone ${params.milestoneId}`,
|
|
96
|
+
errorCode: "slice_not_found" as SkipSliceErrorCode,
|
|
97
|
+
};
|
|
98
|
+
case "slice-already-complete":
|
|
99
|
+
return {
|
|
100
|
+
...base,
|
|
101
|
+
error: `Slice ${params.sliceId} is already complete — cannot skip.`,
|
|
102
|
+
errorCode: "already_complete" as SkipSliceErrorCode,
|
|
103
|
+
};
|
|
115
104
|
}
|
|
116
|
-
|
|
117
|
-
// Cascade: mark every non-closed task as skipped so milestone completion
|
|
118
|
-
// doesn't trip the deep-task guard (#4375). Closed tasks (complete/done/
|
|
119
|
-
// skipped) are left untouched — we never downgrade.
|
|
120
|
-
const tasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
121
|
-
for (const task of tasks) {
|
|
122
|
-
if (!isClosedStatus(task.status)) {
|
|
123
|
-
updateTaskStatus(params.milestoneId, params.sliceId, task.id, "skipped");
|
|
124
|
-
tasksSkipped++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
if (guardError) {
|
|
130
|
-
return { ...base, error: guardError, errorCode: guardCode ?? undefined };
|
|
131
105
|
}
|
|
132
|
-
return { ...base, tasksSkipped, wasAlreadySkipped };
|
|
106
|
+
return { ...base, tasksSkipped: outcome.tasksSkipped, wasAlreadySkipped: outcome.wasAlreadySkipped };
|
|
133
107
|
}
|
|
@@ -13,7 +13,7 @@ import { deriveState } from "./state.js";
|
|
|
13
13
|
import { invalidateAllCaches } from "./cache.js";
|
|
14
14
|
import { gsdRoot, resolveTasksDir, resolveSlicePath, resolveTaskFile, buildTaskFileName, buildSliceFileName } from "./paths.js";
|
|
15
15
|
import { sendDesktopNotification } from "./notifications.js";
|
|
16
|
-
import { getTask, getSlice, getSliceTasks, updateTaskStatus,
|
|
16
|
+
import { getTask, getSlice, getSliceTasks, updateTaskStatus, resetSliceCascade } from "./gsd-db.js";
|
|
17
17
|
import { renderPlanCheckboxes, renderRoadmapCheckboxes } from "./markdown-renderer.js";
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -330,12 +330,16 @@ export async function handleResetSlice(
|
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
// Reset all
|
|
334
|
-
|
|
333
|
+
// Reset all task statuses to "pending" and the slice to "active" in one
|
|
334
|
+
// atomic commit (DB is source of truth). Previously a per-task updateTaskStatus
|
|
335
|
+
// loop + a separate updateSliceStatus, which could leave a partial reset if
|
|
336
|
+
// interrupted mid-loop.
|
|
337
|
+
resetSliceCascade(mid, sid);
|
|
338
|
+
|
|
339
|
+
// Delete task summary files — projection cleanup, separate from the DB reset.
|
|
340
|
+
const tasksReset = tasks.length;
|
|
335
341
|
let summariesDeleted = 0;
|
|
336
342
|
for (const t of tasks) {
|
|
337
|
-
updateTaskStatus(mid, sid, t.id, "pending");
|
|
338
|
-
tasksReset++;
|
|
339
343
|
const summaryPath = resolveTaskFile(basePath, mid, sid, t.id, "SUMMARY");
|
|
340
344
|
if (summaryPath && existsSync(summaryPath)) {
|
|
341
345
|
unlinkSync(summaryPath);
|
|
@@ -343,9 +347,6 @@ export async function handleResetSlice(
|
|
|
343
347
|
}
|
|
344
348
|
}
|
|
345
349
|
|
|
346
|
-
// Reset slice status
|
|
347
|
-
updateSliceStatus(mid, sid, "active");
|
|
348
|
-
|
|
349
350
|
// Delete slice summary and UAT files
|
|
350
351
|
let sliceFilesDeleted = 0;
|
|
351
352
|
const slicePath = resolveSlicePath(basePath, mid, sid);
|