gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.2841a1e44
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/README.md +49 -30
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +3 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
- package/dist/resources/extensions/cmux/index.js +5 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
- package/dist/resources/extensions/gsd/auto/phases.js +9 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +169 -124
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
- package/dist/resources/extensions/gsd/auto-verification.js +28 -22
- package/dist/resources/extensions/gsd/auto.js +128 -52
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +3 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
- package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
- package/dist/resources/extensions/gsd/context-store.js +112 -0
- package/dist/resources/extensions/gsd/db-writer.js +150 -84
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
- package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
- package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
- package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
- package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
- package/dist/resources/extensions/gsd/md-importer.js +1 -1
- package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
- package/dist/resources/extensions/gsd/migrate/command.js +5 -0
- package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
- package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
- package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
- package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +25 -2
- package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +21 -2
- package/dist/resources/extensions/subagent/index.js +448 -78
- package/dist/resources/extensions/subagent/launch.js +77 -0
- package/dist/resources/extensions/subagent/run-store.js +148 -0
- package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
- package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/dist/resources/extensions/visual-brief/index.js +5 -0
- package/dist/resources/extensions/visual-brief/page-contract.js +122 -0
- package/dist/resources/extensions/visual-brief/prompts.js +111 -0
- 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 +15 -15
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
- package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-6a95bc41e0f7ec89.js} +1 -1
- package/dist/web/standalone/.next/static/css/0262768ec1b89d34.css +1 -0
- package/package.json +5 -4
- package/packages/contracts/dist/rpc.test.js +7 -0
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +21 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +24 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/src/rpc.test.ts +8 -0
- package/packages/contracts/src/workflow.ts +24 -0
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +14 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +80 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
- package/packages/mcp-server/src/workflow-tools.ts +168 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -2
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -1
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/index.ts +7 -2
- package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
- package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +5 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/tui.ts +6 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +3 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
- package/src/resources/extensions/cmux/index.ts +6 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
- package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
- package/src/resources/extensions/gsd/auto/phases.ts +14 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +194 -137
- package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
- package/src/resources/extensions/gsd/auto-verification.ts +36 -34
- package/src/resources/extensions/gsd/auto.ts +136 -51
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +3 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +25 -1
- package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
- package/src/resources/extensions/gsd/context-store.ts +120 -1
- package/src/resources/extensions/gsd/db-writer.ts +167 -84
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
- package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
- package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
- package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/md-importer.ts +1 -1
- package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
- package/src/resources/extensions/gsd/migrate/command.ts +5 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
- package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
- package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
- package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
- package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +16 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +35 -2
- package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -2
- package/src/resources/extensions/subagent/index.ts +567 -103
- package/src/resources/extensions/subagent/launch.ts +131 -0
- package/src/resources/extensions/subagent/run-store.ts +218 -0
- package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
- package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
- package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
- package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/src/resources/extensions/visual-brief/index.ts +8 -0
- package/src/resources/extensions/visual-brief/page-contract.ts +134 -0
- package/src/resources/extensions/visual-brief/prompts.ts +147 -0
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +172 -0
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_ssgManifest.js +0 -0
|
@@ -92,6 +92,7 @@ import { compileUnitToolContract } from "./tool-contract.js";
|
|
|
92
92
|
import { createWorktreeSafetyModule } from "./worktree-safety.js";
|
|
93
93
|
import { resolveManifest } from "./unit-context-manifest.js";
|
|
94
94
|
import { classifyFailure } from "./recovery-classification.js";
|
|
95
|
+
import { supportsStructuredQuestions } from "./workflow-mcp.js";
|
|
95
96
|
import { WorktreeLifecycle, } from "./worktree-lifecycle.js";
|
|
96
97
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
97
98
|
import { reorderForCaching } from "./prompt-ordering.js";
|
|
@@ -711,13 +712,11 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
711
712
|
}
|
|
712
713
|
initHealthWidget(ctx);
|
|
713
714
|
}
|
|
714
|
-
// ADR-016 phase 3 (#5693): the stop-path basePath restore routes
|
|
715
|
-
// `Lifecycle.restoreToProjectRoot()`, the sole owner of
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
// The chdir stays here because `restoreToProjectRoot` is a pure
|
|
720
|
-
// session-state mutation.
|
|
715
|
+
// ADR-016 phase 3 (#5693): the stop-path basePath restore + chdir routes
|
|
716
|
+
// through `Lifecycle.restoreToProjectRoot()`, the sole owner of both
|
|
717
|
+
// `s.basePath` mutation and the paired `process.chdir` for auto-loop
|
|
718
|
+
// transitions. The verb assigns `s.basePath` before any throwable work, so
|
|
719
|
+
// a thrown error still leaves basePath restored.
|
|
721
720
|
if (s.originalBasePath) {
|
|
722
721
|
try {
|
|
723
722
|
buildLifecycle().restoreToProjectRoot();
|
|
@@ -725,12 +724,6 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
725
724
|
catch (err) {
|
|
726
725
|
logWarning("engine", `restore project root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
727
726
|
}
|
|
728
|
-
try {
|
|
729
|
-
process.chdir(s.originalBasePath);
|
|
730
|
-
}
|
|
731
|
-
catch (err) {
|
|
732
|
-
logWarning("engine", `basePath restore/chdir failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
733
|
-
}
|
|
734
727
|
}
|
|
735
728
|
if (s.originalBasePath && s.cmdCtx) {
|
|
736
729
|
const result = await rerootCommandSession(s.cmdCtx, s.originalBasePath);
|
|
@@ -1001,8 +994,8 @@ export async function stopAuto(ctx, pi, reason, options = {}) {
|
|
|
1001
994
|
}
|
|
1002
995
|
}
|
|
1003
996
|
// ── Step 7: Restore basePath and chdir (ADR-016 phase 3, #5693) ──
|
|
1004
|
-
// `restoreToProjectRoot`
|
|
1005
|
-
// no
|
|
997
|
+
// `restoreToProjectRoot` owns both s.basePath restore and process.chdir;
|
|
998
|
+
// no paired chdir is needed at the call site.
|
|
1006
999
|
if (s.originalBasePath) {
|
|
1007
1000
|
try {
|
|
1008
1001
|
buildLifecycle().restoreToProjectRoot();
|
|
@@ -1010,13 +1003,6 @@ export async function stopAuto(ctx, pi, reason, options = {}) {
|
|
|
1010
1003
|
catch (e) {
|
|
1011
1004
|
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
1012
1005
|
}
|
|
1013
|
-
try {
|
|
1014
|
-
process.chdir(s.basePath);
|
|
1015
|
-
}
|
|
1016
|
-
catch (err) {
|
|
1017
|
-
/* best-effort */
|
|
1018
|
-
logWarning("engine", `chdir failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1019
|
-
}
|
|
1020
1006
|
}
|
|
1021
1007
|
// Re-root the active command session/tool runtime after worktree teardown.
|
|
1022
1008
|
// mergeAndExit restores process.cwd(), but AgentSession has already captured
|
|
@@ -1385,6 +1371,66 @@ export function buildWorktreeLifecycleDeps() {
|
|
|
1385
1371
|
function buildLifecycle() {
|
|
1386
1372
|
return new WorktreeLifecycle(s, buildWorktreeLifecycleDeps());
|
|
1387
1373
|
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Build the production `DispatchAdapter` used by `createWiredAutoOrchestrationModule`.
|
|
1376
|
+
*
|
|
1377
|
+
* Exported so tests can verify parity with `runDispatch`'s `resolveDispatch` call —
|
|
1378
|
+
* the wired adapter must derive `structuredQuestionsAvailable`, `sessionContextWindow`,
|
|
1379
|
+
* `sessionProvider`, and `modelRegistry` the same way phases.ts:runDispatch does.
|
|
1380
|
+
*/
|
|
1381
|
+
export function createWiredDispatchAdapter(ctx, pi, dispatchBasePath) {
|
|
1382
|
+
return {
|
|
1383
|
+
async decideNextUnit(input) {
|
|
1384
|
+
const state = input.stateSnapshot;
|
|
1385
|
+
const active = state.activeMilestone;
|
|
1386
|
+
if (!active)
|
|
1387
|
+
return null;
|
|
1388
|
+
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
1389
|
+
// Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
|
|
1390
|
+
// (#5789). Prefer caller-supplied values when present so test harnesses and
|
|
1391
|
+
// alternative wirings can inject deterministic snapshots; otherwise pull from
|
|
1392
|
+
// the captured pi/ctx references.
|
|
1393
|
+
const sessionProvider = input.sessionProvider ?? ctx.model?.provider;
|
|
1394
|
+
const sessionContextWindow = input.sessionContextWindow ?? ctx.model?.contextWindow;
|
|
1395
|
+
const modelRegistry = input.modelRegistry ?? ctx.modelRegistry;
|
|
1396
|
+
const authMode = sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
1397
|
+
? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
|
|
1398
|
+
: undefined;
|
|
1399
|
+
const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
|
|
1400
|
+
// Mirrors runDispatch: deep-planning keeps approval gates in plain chat
|
|
1401
|
+
// because structured questions can be cancelled outside the chat turn on
|
|
1402
|
+
// some transports.
|
|
1403
|
+
const structuredQuestionsAvailable = input.structuredQuestionsAvailable ??
|
|
1404
|
+
(prefs?.planning_depth === "deep"
|
|
1405
|
+
? "false"
|
|
1406
|
+
: supportsStructuredQuestions(activeTools, {
|
|
1407
|
+
authMode,
|
|
1408
|
+
baseUrl: ctx.model?.baseUrl,
|
|
1409
|
+
})
|
|
1410
|
+
? "true"
|
|
1411
|
+
: "false");
|
|
1412
|
+
const action = await resolveDispatch({
|
|
1413
|
+
basePath: dispatchBasePath,
|
|
1414
|
+
mid: active.id,
|
|
1415
|
+
midTitle: active.title,
|
|
1416
|
+
state,
|
|
1417
|
+
prefs,
|
|
1418
|
+
structuredQuestionsAvailable,
|
|
1419
|
+
sessionContextWindow,
|
|
1420
|
+
sessionProvider,
|
|
1421
|
+
modelRegistry,
|
|
1422
|
+
});
|
|
1423
|
+
if (action.action !== "dispatch")
|
|
1424
|
+
return null;
|
|
1425
|
+
return {
|
|
1426
|
+
unitType: action.unitType,
|
|
1427
|
+
unitId: action.unitId,
|
|
1428
|
+
reason: action.matchedRule ?? "dispatch",
|
|
1429
|
+
preconditions: [],
|
|
1430
|
+
};
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1388
1434
|
/**
|
|
1389
1435
|
* Thin entry glue for the new Auto Orchestration module.
|
|
1390
1436
|
*
|
|
@@ -1392,7 +1438,7 @@ function buildLifecycle() {
|
|
|
1392
1438
|
* no behavior changes to the existing auto loop. It provides a concrete seam
|
|
1393
1439
|
* the next refactor steps can adopt incrementally.
|
|
1394
1440
|
*/
|
|
1395
|
-
export function createWiredAutoOrchestrationModule(ctx,
|
|
1441
|
+
export function createWiredAutoOrchestrationModule(ctx, pi, dispatchBasePath, runtimeBasePath = resolveProjectRoot(dispatchBasePath)) {
|
|
1396
1442
|
const flowId = `auto-orchestrator-${Date.now()}`;
|
|
1397
1443
|
let seq = 0;
|
|
1398
1444
|
const deps = {
|
|
@@ -1416,30 +1462,7 @@ export function createWiredAutoOrchestrationModule(ctx, _pi, dispatchBasePath, r
|
|
|
1416
1462
|
};
|
|
1417
1463
|
},
|
|
1418
1464
|
},
|
|
1419
|
-
dispatch:
|
|
1420
|
-
async decideNextUnit(input) {
|
|
1421
|
-
const state = input.stateSnapshot;
|
|
1422
|
-
const active = state.activeMilestone;
|
|
1423
|
-
if (!active)
|
|
1424
|
-
return null;
|
|
1425
|
-
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
1426
|
-
const action = await resolveDispatch({
|
|
1427
|
-
basePath: dispatchBasePath,
|
|
1428
|
-
mid: active.id,
|
|
1429
|
-
midTitle: active.title,
|
|
1430
|
-
state,
|
|
1431
|
-
prefs,
|
|
1432
|
-
});
|
|
1433
|
-
if (action.action !== "dispatch")
|
|
1434
|
-
return null;
|
|
1435
|
-
return {
|
|
1436
|
-
unitType: action.unitType,
|
|
1437
|
-
unitId: action.unitId,
|
|
1438
|
-
reason: action.matchedRule ?? "dispatch",
|
|
1439
|
-
preconditions: [],
|
|
1440
|
-
};
|
|
1441
|
-
},
|
|
1442
|
-
},
|
|
1465
|
+
dispatch: createWiredDispatchAdapter(ctx, pi, dispatchBasePath),
|
|
1443
1466
|
recovery: {
|
|
1444
1467
|
async classifyAndRecover(input) {
|
|
1445
1468
|
const recovery = classifyFailure(input);
|
|
@@ -1488,12 +1511,26 @@ export function createWiredAutoOrchestrationModule(ctx, _pi, dispatchBasePath, r
|
|
|
1488
1511
|
async cleanupOnStop() { },
|
|
1489
1512
|
},
|
|
1490
1513
|
health: {
|
|
1514
|
+
checkResourcesStale() {
|
|
1515
|
+
return checkResourcesStale(s.resourceVersionOnStart);
|
|
1516
|
+
},
|
|
1491
1517
|
async preAdvanceGate() {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1518
|
+
try {
|
|
1519
|
+
const gate = await preDispatchHealthGate(dispatchBasePath);
|
|
1520
|
+
if (gate.proceed) {
|
|
1521
|
+
return {
|
|
1522
|
+
kind: "pass",
|
|
1523
|
+
fixesApplied: gate.fixesApplied,
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
kind: "fail",
|
|
1528
|
+
reason: gate.reason ?? "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
return { kind: "threw", error };
|
|
1533
|
+
}
|
|
1497
1534
|
},
|
|
1498
1535
|
async postAdvanceRecord(result) {
|
|
1499
1536
|
if (result.kind === "error") {
|
|
@@ -1561,6 +1598,45 @@ export function createWiredAutoOrchestrationModule(ctx, _pi, dispatchBasePath, r
|
|
|
1561
1598
|
}
|
|
1562
1599
|
},
|
|
1563
1600
|
},
|
|
1601
|
+
uokGate: {
|
|
1602
|
+
async emit(input) {
|
|
1603
|
+
const prefs = loadEffectiveGSDPreferences(dispatchBasePath)?.preferences;
|
|
1604
|
+
const uokFlags = resolveUokFlags(prefs);
|
|
1605
|
+
if (!uokFlags.gates)
|
|
1606
|
+
return;
|
|
1607
|
+
const milestoneId = input.milestoneId ?? s.currentMilestoneId ?? undefined;
|
|
1608
|
+
try {
|
|
1609
|
+
const { UokGateRunner } = await import("./uok/gate-runner.js");
|
|
1610
|
+
const runner = new UokGateRunner();
|
|
1611
|
+
runner.register({
|
|
1612
|
+
id: input.gateId,
|
|
1613
|
+
type: input.gateType,
|
|
1614
|
+
execute: async () => ({
|
|
1615
|
+
outcome: input.outcome,
|
|
1616
|
+
failureClass: input.failureClass,
|
|
1617
|
+
rationale: input.rationale,
|
|
1618
|
+
findings: input.findings ?? "",
|
|
1619
|
+
}),
|
|
1620
|
+
});
|
|
1621
|
+
await runner.run(input.gateId, {
|
|
1622
|
+
basePath: dispatchBasePath,
|
|
1623
|
+
traceId: `pre-dispatch:${flowId}`,
|
|
1624
|
+
turnId: `orch-${seq}`,
|
|
1625
|
+
milestoneId,
|
|
1626
|
+
unitType: "pre-dispatch",
|
|
1627
|
+
unitId: `orch-${seq}`,
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
catch (err) {
|
|
1631
|
+
logWarning("engine", `uok gate emit failed: ${getErrorMessage(err)}`, {
|
|
1632
|
+
file: "auto.ts",
|
|
1633
|
+
gateId: input.gateId,
|
|
1634
|
+
gateType: input.gateType,
|
|
1635
|
+
...(milestoneId ? { milestoneId } : {}),
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
},
|
|
1639
|
+
},
|
|
1564
1640
|
};
|
|
1565
1641
|
return createAutoOrchestrator(deps);
|
|
1566
1642
|
}
|
|
@@ -355,6 +355,9 @@ async function writeContextModeCompactionSnapshot(basePath) {
|
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
export function registerHooks(pi, ecosystemHandlers) {
|
|
358
|
+
// ADR-005 Phase 3b: surface pi-ai ProviderSwitchReport via audit, notification, and counter.
|
|
359
|
+
// Idempotent — only the first registerHooks call installs.
|
|
360
|
+
void import("../provider-switch-observer.js").then((m) => m.installProviderSwitchObserver());
|
|
358
361
|
pi.on("session_start", async (_event, ctx) => {
|
|
359
362
|
const basePath = contextBasePath(ctx);
|
|
360
363
|
initSessionNotifications(ctx);
|
|
@@ -411,6 +414,8 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
411
414
|
}
|
|
412
415
|
await loadToolApiKeysForSession();
|
|
413
416
|
if (!isAutoActive()) {
|
|
417
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
418
|
+
ctx.ui.setWidget("gsd-outcome", undefined);
|
|
414
419
|
const { initHealthWidget } = await import("../health-widget.js");
|
|
415
420
|
initHealthWidget(ctx);
|
|
416
421
|
}
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
export function extractSubagentAgentClasses(input) {
|
|
2
2
|
if (!input || typeof input !== "object")
|
|
3
3
|
return [];
|
|
4
|
-
const record = input;
|
|
5
4
|
const agentClasses = [];
|
|
5
|
+
const visited = new WeakSet();
|
|
6
6
|
const addAgentClass = (value) => {
|
|
7
7
|
if (typeof value === "string" && value.trim().length > 0)
|
|
8
8
|
agentClasses.push(value.trim());
|
|
9
9
|
};
|
|
10
|
-
const
|
|
10
|
+
const visitItems = (value) => {
|
|
11
11
|
if (!Array.isArray(value))
|
|
12
12
|
return;
|
|
13
13
|
for (const item of value) {
|
|
14
|
-
|
|
15
|
-
addAgentClass(item.agent);
|
|
14
|
+
visit(item);
|
|
16
15
|
}
|
|
17
16
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const visit = (value) => {
|
|
18
|
+
if (!value || typeof value !== "object")
|
|
19
|
+
return;
|
|
20
|
+
if (visited.has(value))
|
|
21
|
+
return;
|
|
22
|
+
visited.add(value);
|
|
23
|
+
const record = value;
|
|
24
|
+
addAgentClass(record.agent);
|
|
25
|
+
visitItems(record.tasks);
|
|
26
|
+
visitItems(record.chain);
|
|
27
|
+
visitItems(record.parallel);
|
|
28
|
+
};
|
|
29
|
+
visit(input);
|
|
21
30
|
return agentClasses;
|
|
22
31
|
}
|
|
@@ -10,6 +10,7 @@ import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffect
|
|
|
10
10
|
import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
|
|
11
11
|
import { resolveSkillReference } from "../preferences-skills.js";
|
|
12
12
|
import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
|
|
13
|
+
import { extractIntroAndRules } from "../knowledge-parser.js";
|
|
13
14
|
import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
|
|
14
15
|
import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
|
|
15
16
|
import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
|
|
@@ -116,19 +117,19 @@ export async function buildBeforeAgentStartResult(event, ctx) {
|
|
|
116
117
|
catch (e) {
|
|
117
118
|
logWarning("bootstrap", `cmux prompt setup skipped: ${e.message}`);
|
|
118
119
|
}
|
|
120
|
+
const ctxProjectRoot = ctx.projectRoot;
|
|
121
|
+
const basePath = typeof ctxProjectRoot === "string" && ctxProjectRoot.length > 0
|
|
122
|
+
? ctxProjectRoot
|
|
123
|
+
: process.cwd();
|
|
119
124
|
let preferenceBlock = "";
|
|
120
125
|
if (loadedPreferences) {
|
|
121
|
-
const cwd =
|
|
126
|
+
const cwd = basePath;
|
|
122
127
|
const report = resolveAllSkillReferences(loadedPreferences.preferences, cwd);
|
|
123
128
|
preferenceBlock = `\n\n${renderPreferencesForSystemPrompt(loadedPreferences.preferences, report.resolutions)}`;
|
|
124
129
|
if (report.warnings.length > 0) {
|
|
125
130
|
ctx.ui.notify(`GSD skill preferences: ${report.warnings.length} unresolved skill${report.warnings.length === 1 ? "" : "s"}: ${report.warnings.join(", ")}`, "warning");
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
|
-
const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome(), process.cwd());
|
|
129
|
-
if (globalSizeKb > 4) {
|
|
130
|
-
ctx.ui.notify(`GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`, "warning");
|
|
131
|
-
}
|
|
132
133
|
// ADR-013 step 5: opportunistic decisions->memories backfill. Idempotent
|
|
133
134
|
// and best-effort — first run absorbs the existing decisions table into
|
|
134
135
|
// the memory store; subsequent runs are a single sentinel SELECT.
|
|
@@ -136,12 +137,47 @@ export async function buildBeforeAgentStartResult(event, ctx) {
|
|
|
136
137
|
const { backfillDecisionsToMemories } = await import("../memory-backfill.js");
|
|
137
138
|
const written = backfillDecisionsToMemories();
|
|
138
139
|
if (written > 0) {
|
|
139
|
-
ctx.ui.notify(`GSD: backfilled ${written} decision${written === 1 ? "" : "s"} into the memory store
|
|
140
|
+
ctx.ui.notify(`GSD: backfilled ${written} decision${written === 1 ? "" : "s"} into the memory store.`, "info");
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
catch (e) {
|
|
143
144
|
logWarning("bootstrap", `decisions backfill failed: ${e.message}`);
|
|
144
145
|
}
|
|
146
|
+
// ADR-013 Stage 2b: KNOWLEDGE.md Patterns + Lessons backfill, then
|
|
147
|
+
// re-render the hybrid projection (manual Rules + projected Patterns +
|
|
148
|
+
// projected Lessons). Both are idempotent and best-effort — failures here
|
|
149
|
+
// can't block agent startup.
|
|
150
|
+
try {
|
|
151
|
+
const { backfillKnowledgeToMemories } = await import("../knowledge-backfill.js");
|
|
152
|
+
const writtenK = backfillKnowledgeToMemories(basePath);
|
|
153
|
+
if (writtenK > 0) {
|
|
154
|
+
ctx.ui.notify(`GSD: backfilled ${writtenK} KNOWLEDGE.md row${writtenK === 1 ? "" : "s"} into the memory store.`, "info");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
logWarning("bootstrap", `KNOWLEDGE.md backfill failed: ${e.message}`);
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const { renderKnowledgeProjection } = await import("../knowledge-projection.js");
|
|
162
|
+
renderKnowledgeProjection(basePath);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
logWarning("bootstrap", `KNOWLEDGE.md projection render failed: ${e.message}`);
|
|
166
|
+
}
|
|
167
|
+
// ADR-013 step 6 preflight: warn when decisions / KNOWLEDGE.md rows are not
|
|
168
|
+
// yet in the memories table. Read-only; never throws. Runs after the two
|
|
169
|
+
// backfills above so the gap report reflects post-backfill state.
|
|
170
|
+
try {
|
|
171
|
+
const { reportConsolidationGaps } = await import("../memory-consolidation-scanner.js");
|
|
172
|
+
reportConsolidationGaps(basePath);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
logWarning("bootstrap", `memory consolidation scan failed: ${e.message}`);
|
|
176
|
+
}
|
|
177
|
+
const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome(), basePath);
|
|
178
|
+
if (globalSizeKb > 4) {
|
|
179
|
+
ctx.ui.notify(`GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`, "warning");
|
|
180
|
+
}
|
|
145
181
|
let newSkillsBlock = "";
|
|
146
182
|
if (hasSkillSnapshot()) {
|
|
147
183
|
const newSkills = detectNewSkills();
|
|
@@ -320,7 +356,9 @@ export async function loadMemoryBlock(userPrompt, opts = {}) {
|
|
|
320
356
|
}
|
|
321
357
|
}
|
|
322
358
|
export function loadKnowledgeBlock(gsdHomeDir, cwd) {
|
|
323
|
-
// 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project,
|
|
359
|
+
// 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project,
|
|
360
|
+
// user-maintained. NOT migrated to memories (which are project-scoped),
|
|
361
|
+
// so the full file is injected unchanged.
|
|
324
362
|
let globalKnowledge = "";
|
|
325
363
|
let globalSizeKb = 0;
|
|
326
364
|
const globalKnowledgePath = join(gsdHomeDir, "agent", "KNOWLEDGE.md");
|
|
@@ -336,14 +374,19 @@ export function loadKnowledgeBlock(gsdHomeDir, cwd) {
|
|
|
336
374
|
logWarning("bootstrap", `global knowledge file read failed: ${e.message}`);
|
|
337
375
|
}
|
|
338
376
|
}
|
|
339
|
-
// 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific
|
|
377
|
+
// 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific.
|
|
378
|
+
// ADR-013 Stage 2b: Patterns and Lessons are projected from the
|
|
379
|
+
// memories table and already reach the LLM via loadMemoryBlock. Inject
|
|
380
|
+
// only the intro prose + `## Rules` section here to avoid duplicating
|
|
381
|
+
// Patterns/Lessons content in the prompt. Rules stay manual per
|
|
382
|
+
// ADR-013 line 39 and have no memory equivalent.
|
|
340
383
|
let projectKnowledge = "";
|
|
341
384
|
const knowledgePath = resolveGsdRootFile(cwd, "KNOWLEDGE");
|
|
342
385
|
if (existsSync(knowledgePath)) {
|
|
343
386
|
try {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
projectKnowledge =
|
|
387
|
+
const raw = readFileSync(knowledgePath, "utf-8").trim();
|
|
388
|
+
if (raw)
|
|
389
|
+
projectKnowledge = extractIntroAndRules(raw).trim();
|
|
347
390
|
}
|
|
348
391
|
catch (e) {
|
|
349
392
|
logWarning("bootstrap", `project knowledge file read failed: ${e.message}`);
|
|
@@ -361,7 +404,7 @@ export function loadKnowledgeBlock(gsdHomeDir, cwd) {
|
|
|
361
404
|
}
|
|
362
405
|
const body = limitKnowledgeBlock(parts.join("\n\n"), getKnowledgeCharLimit());
|
|
363
406
|
return {
|
|
364
|
-
block: `\n\n[KNOWLEDGE — Rules
|
|
407
|
+
block: `\n\n[KNOWLEDGE — Rules from KNOWLEDGE.md (Patterns and Lessons reach the LLM via the memory block)]\n\n${body}`,
|
|
365
408
|
globalSizeKb,
|
|
366
409
|
};
|
|
367
410
|
}
|
|
@@ -3,6 +3,7 @@ import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkS
|
|
|
3
3
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
5
|
import { getIsolationMode } from "../preferences.js";
|
|
6
|
+
import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
|
|
6
7
|
import { logWarning } from "../workflow-logger.js";
|
|
7
8
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
8
9
|
/**
|
|
@@ -681,7 +682,8 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
681
682
|
if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
682
683
|
if (policy.mode === "planning-dispatch") {
|
|
683
684
|
const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
|
|
684
|
-
const
|
|
685
|
+
const dispatchContract = compileSubagentPermissionContract(policy);
|
|
686
|
+
const allowedSubagents = dispatchContract.allowedSubagents;
|
|
685
687
|
const allowed = new Set(allowedSubagents);
|
|
686
688
|
// When agentClasses is undefined, the caller has not been updated to extract
|
|
687
689
|
// agent identities yet. Block and warn so stale callers surface in telemetry
|
|
@@ -8,14 +8,153 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Design constraints (from Trek-e approval):
|
|
10
10
|
* - Warn the user before stashing (no silent surprises)
|
|
11
|
-
* - git stash push / git stash
|
|
12
|
-
* - Stash/
|
|
11
|
+
* - git stash push / git stash apply+drop for targeted restore
|
|
12
|
+
* - Stash/apply errors are logged but MUST NOT block the merge itself
|
|
13
13
|
* - Fast-path status check — clean trees pay no extra cost
|
|
14
14
|
*/
|
|
15
15
|
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
16
18
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
17
19
|
import { logWarning } from "./workflow-logger.js";
|
|
18
20
|
import { nativeHasChanges } from "./native-git-bridge.js";
|
|
21
|
+
function gitText(basePath, args) {
|
|
22
|
+
return execFileSync("git", args, {
|
|
23
|
+
cwd: basePath,
|
|
24
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
env: GIT_NO_PROMPT_ENV,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function gitBuffer(basePath, args) {
|
|
30
|
+
return execFileSync("git", args, {
|
|
31
|
+
cwd: basePath,
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
env: GIT_NO_PROMPT_ENV,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function errorText(err) {
|
|
37
|
+
if (!err || typeof err !== "object")
|
|
38
|
+
return String(err);
|
|
39
|
+
const parts = [];
|
|
40
|
+
const stderr = err.stderr;
|
|
41
|
+
const stdout = err.stdout;
|
|
42
|
+
for (const value of [stderr, stdout]) {
|
|
43
|
+
if (typeof value === "string")
|
|
44
|
+
parts.push(value);
|
|
45
|
+
else if (value instanceof Uint8Array)
|
|
46
|
+
parts.push(Buffer.from(value).toString("utf-8"));
|
|
47
|
+
}
|
|
48
|
+
parts.push(err instanceof Error ? err.message : String(err));
|
|
49
|
+
return parts.filter(Boolean).join("\n");
|
|
50
|
+
}
|
|
51
|
+
function parseAlreadyExistsNoCheckoutPaths(text) {
|
|
52
|
+
const paths = [];
|
|
53
|
+
for (const line of text.split(/\r?\n/)) {
|
|
54
|
+
const match = /^(.+?) already exists, no checkout$/i.exec(line.trim());
|
|
55
|
+
if (match?.[1])
|
|
56
|
+
paths.push(match[1]);
|
|
57
|
+
}
|
|
58
|
+
return [...new Set(paths)];
|
|
59
|
+
}
|
|
60
|
+
function readZeroDelimitedPaths(output) {
|
|
61
|
+
return output.split("\0").filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
function listStashUntrackedPaths(basePath, stashRef) {
|
|
64
|
+
try {
|
|
65
|
+
const output = gitText(basePath, ["ls-tree", "-r", "-z", "--name-only", `${stashRef}^3`]);
|
|
66
|
+
return readZeroDelimitedPaths(output);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function listStashTrackedPaths(basePath, stashRef) {
|
|
73
|
+
try {
|
|
74
|
+
const output = gitText(basePath, ["diff", "--name-only", "-z", `${stashRef}^1`, stashRef]);
|
|
75
|
+
return readZeroDelimitedPaths(output);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isWorktreeClean(basePath) {
|
|
82
|
+
try {
|
|
83
|
+
return gitText(basePath, ["status", "--porcelain"]).trim() === "";
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function stashBlobEqualsWorktreeFile(basePath, stashRef, path) {
|
|
90
|
+
try {
|
|
91
|
+
const worktreePath = join(basePath, path);
|
|
92
|
+
if (!existsSync(worktreePath))
|
|
93
|
+
return false;
|
|
94
|
+
const worktreeContent = readFileSync(worktreePath);
|
|
95
|
+
const stashContent = gitBuffer(basePath, ["show", `${stashRef}^3:${path}`]);
|
|
96
|
+
return Buffer.compare(worktreeContent, stashContent) === 0;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function reconcileAlreadyPresentUntrackedStash(basePath, milestoneId, stashRef, err) {
|
|
103
|
+
const text = errorText(err);
|
|
104
|
+
const collidedPaths = parseAlreadyExistsNoCheckoutPaths(text);
|
|
105
|
+
if (collidedPaths.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
const untrackedPaths = listStashUntrackedPaths(basePath, stashRef);
|
|
108
|
+
if (!untrackedPaths || untrackedPaths.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
const trackedPaths = listStashTrackedPaths(basePath, stashRef);
|
|
111
|
+
if (trackedPaths === null || trackedPaths.length > 0)
|
|
112
|
+
return null;
|
|
113
|
+
const untrackedPathSet = new Set(untrackedPaths);
|
|
114
|
+
if (!collidedPaths.every((path) => untrackedPathSet.has(path)))
|
|
115
|
+
return null;
|
|
116
|
+
if (!untrackedPaths.every((path) => existsSync(join(basePath, path))))
|
|
117
|
+
return null;
|
|
118
|
+
if (isWorktreeClean(basePath) !== true)
|
|
119
|
+
return null;
|
|
120
|
+
const blobComparisons = untrackedPaths.map((path) => stashBlobEqualsWorktreeFile(basePath, stashRef, path));
|
|
121
|
+
if (blobComparisons.some((result) => result === null))
|
|
122
|
+
return null;
|
|
123
|
+
const allIdentical = blobComparisons.every(Boolean);
|
|
124
|
+
if (allIdentical) {
|
|
125
|
+
let dropped = true;
|
|
126
|
+
try {
|
|
127
|
+
execFileSync("git", ["stash", "drop", stashRef], {
|
|
128
|
+
cwd: basePath,
|
|
129
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
130
|
+
encoding: "utf-8",
|
|
131
|
+
env: GIT_NO_PROMPT_ENV,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
dropped = false;
|
|
136
|
+
logWarning("preflight", `git stash drop ${stashRef} failed after identical preflight stash reconciliation: ${err instanceof Error ? err.message : String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
restored: true,
|
|
140
|
+
needsManualRecovery: false,
|
|
141
|
+
message: dropped
|
|
142
|
+
? `Preflight stash for milestone ${milestoneId} contained files already present after merge; identical stash dropped.`
|
|
143
|
+
: `Preflight stash for milestone ${milestoneId} contained files already present after merge, but ${stashRef} could not be dropped and remains as a backup.`,
|
|
144
|
+
stashRef,
|
|
145
|
+
resolution: dropped ? "already-present-dropped" : "already-present-preserved",
|
|
146
|
+
collidedPaths,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
restored: false,
|
|
151
|
+
needsManualRecovery: false,
|
|
152
|
+
message: `Preflight stash for milestone ${milestoneId} contained untracked files already present after merge. Keeping merged files and preserving ${stashRef} as a backup.`,
|
|
153
|
+
stashRef,
|
|
154
|
+
resolution: "already-present-preserved",
|
|
155
|
+
collidedPaths,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
19
158
|
function findPreflightStashRef(basePath, milestoneId, stashMarker) {
|
|
20
159
|
const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
|
|
21
160
|
let fallbackRef = null;
|
|
@@ -115,28 +254,50 @@ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
|
|
|
115
254
|
message: msg,
|
|
116
255
|
};
|
|
117
256
|
}
|
|
118
|
-
execFileSync("git", ["stash", "
|
|
257
|
+
execFileSync("git", ["stash", "apply", stashRef], {
|
|
119
258
|
cwd: basePath,
|
|
120
259
|
stdio: ["ignore", "pipe", "pipe"],
|
|
121
260
|
encoding: "utf-8",
|
|
122
261
|
env: GIT_NO_PROMPT_ENV,
|
|
123
262
|
});
|
|
263
|
+
let dropWarning = null;
|
|
264
|
+
try {
|
|
265
|
+
execFileSync("git", ["stash", "drop", stashRef], {
|
|
266
|
+
cwd: basePath,
|
|
267
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
268
|
+
encoding: "utf-8",
|
|
269
|
+
env: GIT_NO_PROMPT_ENV,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
dropWarning = ` Stash was restored, but git stash drop ${stashRef} failed: ${err instanceof Error ? err.message : String(err)}.`;
|
|
274
|
+
logWarning("preflight", dropWarning.trim());
|
|
275
|
+
}
|
|
124
276
|
const msg = `Restored stashed changes after milestone ${milestoneId} merge.`;
|
|
125
|
-
notify(msg
|
|
277
|
+
notify(`${msg}${dropWarning ?? ""}`, dropWarning ? "warning" : "info");
|
|
126
278
|
return {
|
|
127
279
|
restored: true,
|
|
128
280
|
needsManualRecovery: false,
|
|
129
|
-
message: msg
|
|
281
|
+
message: `${msg}${dropWarning ?? ""}`,
|
|
130
282
|
stashRef,
|
|
283
|
+
resolution: "applied",
|
|
131
284
|
};
|
|
132
285
|
}
|
|
133
286
|
catch (err) {
|
|
134
|
-
|
|
287
|
+
if (stashRef) {
|
|
288
|
+
const reconciled = reconcileAlreadyPresentUntrackedStash(basePath, milestoneId, stashRef, err);
|
|
289
|
+
if (reconciled) {
|
|
290
|
+
logWarning("preflight", reconciled.message);
|
|
291
|
+
notify(reconciled.message, reconciled.resolution === "already-present-preserved" ? "warning" : "info");
|
|
292
|
+
return reconciled;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Apply conflicts mean the merged code collides with the stashed changes.
|
|
135
296
|
// Log a warning — the user needs to resolve manually, but the merge succeeded.
|
|
136
297
|
const restoreHint = stashRef
|
|
137
|
-
? `Run "git stash
|
|
298
|
+
? `Run "git stash apply ${stashRef}" manually to restore the correct stash, then "git stash drop ${stashRef}" after recovery.`
|
|
138
299
|
: `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
|
|
139
|
-
const msg = `git stash
|
|
300
|
+
const msg = `git stash apply ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
|
|
140
301
|
logWarning("preflight", msg);
|
|
141
302
|
notify(msg, "warning");
|
|
142
303
|
return {
|
|
@@ -144,6 +305,7 @@ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
|
|
|
144
305
|
needsManualRecovery: true,
|
|
145
306
|
message: msg,
|
|
146
307
|
...(stashRef ? { stashRef } : {}),
|
|
308
|
+
resolution: "manual-recovery",
|
|
147
309
|
};
|
|
148
310
|
}
|
|
149
311
|
}
|