gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.cf9433f56
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 +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +66 -45
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +77 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/detection.js +106 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +80 -14
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
- 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 +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/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 +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -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 +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -0
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- 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 +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +77 -48
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +92 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/detection.ts +128 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +96 -14
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +72 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +101 -2
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → -5nHJWzSdG-WkPMul_khA}/_ssgManifest.js +0 -0
|
@@ -16,6 +16,31 @@ import { execFileSync } from "node:child_process";
|
|
|
16
16
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
17
17
|
import { logWarning } from "./workflow-logger.js";
|
|
18
18
|
import { nativeHasChanges } from "./native-git-bridge.js";
|
|
19
|
+
function findPreflightStashRef(basePath, milestoneId, stashMarker) {
|
|
20
|
+
const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
|
|
21
|
+
let fallbackRef = null;
|
|
22
|
+
try {
|
|
23
|
+
const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
|
|
24
|
+
cwd: basePath,
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
env: GIT_NO_PROMPT_ENV,
|
|
28
|
+
});
|
|
29
|
+
for (const line of list.split("\n")) {
|
|
30
|
+
const [ref, subject] = line.split("\x00");
|
|
31
|
+
if (!ref || !subject)
|
|
32
|
+
continue;
|
|
33
|
+
if (stashMarker && subject.includes(stashMarker))
|
|
34
|
+
return ref;
|
|
35
|
+
if (!fallbackRef && subject.includes(markerPrefix))
|
|
36
|
+
fallbackRef = ref;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logWarning("preflight", `stash list failed before restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
|
+
}
|
|
42
|
+
return fallbackRef;
|
|
43
|
+
}
|
|
19
44
|
/**
|
|
20
45
|
* Check the working tree for dirty files before a milestone merge.
|
|
21
46
|
*
|
|
@@ -47,7 +72,8 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
47
72
|
notify(warnMsg, "warning");
|
|
48
73
|
// Push the stash
|
|
49
74
|
try {
|
|
50
|
-
|
|
75
|
+
const stashMarker = `gsd-preflight-stash:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
76
|
+
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd-preflight-stash [${stashMarker}]`], {
|
|
51
77
|
cwd: basePath,
|
|
52
78
|
stdio: ["ignore", "pipe", "pipe"],
|
|
53
79
|
encoding: "utf-8",
|
|
@@ -55,6 +81,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
55
81
|
});
|
|
56
82
|
return {
|
|
57
83
|
stashPushed: true,
|
|
84
|
+
stashMarker,
|
|
58
85
|
summary: `Stashed uncommitted changes before merge (milestone ${milestoneId}).`,
|
|
59
86
|
};
|
|
60
87
|
}
|
|
@@ -73,9 +100,17 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
73
100
|
* Any pop error (e.g. conflict) is logged and notified but does NOT throw —
|
|
74
101
|
* the merge already completed successfully.
|
|
75
102
|
*/
|
|
76
|
-
export function postflightPopStash(basePath, milestoneId, notify) {
|
|
103
|
+
export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
|
|
104
|
+
let stashRef = null;
|
|
77
105
|
try {
|
|
78
|
-
|
|
106
|
+
stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
|
|
107
|
+
if (!stashRef) {
|
|
108
|
+
const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
|
|
109
|
+
logWarning("preflight", msg);
|
|
110
|
+
notify(msg, "warning");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
execFileSync("git", ["stash", "pop", stashRef], {
|
|
79
114
|
cwd: basePath,
|
|
80
115
|
stdio: ["ignore", "pipe", "pipe"],
|
|
81
116
|
encoding: "utf-8",
|
|
@@ -86,7 +121,10 @@ export function postflightPopStash(basePath, milestoneId, notify) {
|
|
|
86
121
|
catch (err) {
|
|
87
122
|
// Pop conflicts mean the merged code collides with the stashed changes.
|
|
88
123
|
// Log a warning — the user needs to resolve manually, but the merge succeeded.
|
|
89
|
-
const
|
|
124
|
+
const restoreHint = stashRef
|
|
125
|
+
? `Run "git stash pop ${stashRef}" or "git stash apply ${stashRef}" manually to restore the correct stash.`
|
|
126
|
+
: `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
|
|
127
|
+
const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
|
|
90
128
|
logWarning("preflight", msg);
|
|
91
129
|
notify(msg, "warning");
|
|
92
130
|
}
|
|
@@ -33,5 +33,10 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
33
33
|
}
|
|
34
34
|
if (handled)
|
|
35
35
|
return;
|
|
36
|
+
if (trimmed.includes(" ")) {
|
|
37
|
+
const { handleDo } = await import("../commands-do.js");
|
|
38
|
+
await handleDo(trimmed, ctx, pi);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
37
42
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
|
|
9
9
|
*/
|
|
10
|
-
import { getCharsPerToken } from "./token-counter.js";
|
|
10
|
+
import { getCharsPerToken, isAccurateCountingAvailable, countTokensSync, } from "./token-counter.js";
|
|
11
11
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
12
12
|
// Percentages of total context window allocated to each budget category.
|
|
13
13
|
// These are applied after tokens→chars conversion.
|
|
@@ -23,6 +23,22 @@ const CHARS_PER_TOKEN = 4;
|
|
|
23
23
|
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
24
24
|
/** Conservative effective context for Claude Code subscription routing (#4676) */
|
|
25
25
|
const CLAUDE_CODE_EFFECTIVE_CONTEXT_WINDOW = 200_000;
|
|
26
|
+
/**
|
|
27
|
+
* Cached empirical chars-per-token from a tiktoken probe, keyed by provider.
|
|
28
|
+
* countTokensSync's fallback path is provider-aware, so we cache per-provider
|
|
29
|
+
* to preserve that distinction once the encoder warms. The cl100k_base encoder
|
|
30
|
+
* itself gives a stable ratio for ASCII English so a single probe per provider
|
|
31
|
+
* key is sufficient. Empty map means "not yet probed" or "encoder unavailable".
|
|
32
|
+
*/
|
|
33
|
+
const _empiricalCharsPerTokenByProvider = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Test hook — clears the empirical chars-per-token cache so test cases that
|
|
36
|
+
* assert against the static char-ratio fallback aren't polluted by a prior
|
|
37
|
+
* tiktoken-warmed run in the same process. Production code must not call this.
|
|
38
|
+
*/
|
|
39
|
+
export function _resetEmpiricalCacheForTest() {
|
|
40
|
+
_empiricalCharsPerTokenByProvider.clear();
|
|
41
|
+
}
|
|
26
42
|
/** Percentage of context consumed before suggesting a continue-here checkpoint */
|
|
27
43
|
const CONTINUE_THRESHOLD_PERCENT = 70;
|
|
28
44
|
// ─── Task count bounds ───────────────────────────────────────────────────────
|
|
@@ -46,7 +62,26 @@ const TASK_COUNT_TIERS = [
|
|
|
46
62
|
export function computeBudgets(contextWindow, provider) {
|
|
47
63
|
const effectiveWindow = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
48
64
|
const charsPerToken = provider ? getCharsPerToken(provider) : CHARS_PER_TOKEN;
|
|
49
|
-
|
|
65
|
+
// Prefer the tiktoken encoder for total-char estimation when it has been
|
|
66
|
+
// warmed (initTokenCounter resolved). The cl100k_base ratio is stable for
|
|
67
|
+
// ASCII English, so probe once per provider and cache — computeBudgets is
|
|
68
|
+
// called multiple times per prompt build and the probe encode is otherwise
|
|
69
|
+
// wasted work.
|
|
70
|
+
let totalChars;
|
|
71
|
+
if (isAccurateCountingAvailable()) {
|
|
72
|
+
const providerKey = provider ?? "__default__";
|
|
73
|
+
let empirical = _empiricalCharsPerTokenByProvider.get(providerKey);
|
|
74
|
+
if (empirical === undefined) {
|
|
75
|
+
const probe = "the quick brown fox jumps over the lazy dog ".repeat(64);
|
|
76
|
+
const probeTokens = countTokensSync(probe, provider);
|
|
77
|
+
empirical = probeTokens > 0 ? probe.length / probeTokens : charsPerToken;
|
|
78
|
+
_empiricalCharsPerTokenByProvider.set(providerKey, empirical);
|
|
79
|
+
}
|
|
80
|
+
totalChars = effectiveWindow * empirical;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
totalChars = effectiveWindow * charsPerToken;
|
|
84
|
+
}
|
|
50
85
|
return {
|
|
51
86
|
summaryBudgetChars: Math.floor(totalChars * SUMMARY_RATIO),
|
|
52
87
|
inlineContextBudgetChars: Math.floor(totalChars * INLINE_CONTEXT_RATIO),
|
|
@@ -29,6 +29,7 @@ import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
|
29
29
|
import { gsdRoot, normalizeRealPath } from "./paths.js";
|
|
30
30
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
31
31
|
import { effectiveLockFile } from "./session-lock.js";
|
|
32
|
+
import { isInFlightRuntimePhase, listUnitRuntimeRecords } from "./unit-runtime.js";
|
|
32
33
|
const SESSION_FILE_KV_KEY = "session_file";
|
|
33
34
|
function lockPath(basePath) {
|
|
34
35
|
return join(gsdRoot(basePath), effectiveLockFile());
|
|
@@ -72,9 +73,37 @@ function getLatestDispatchForWorker(workerId) {
|
|
|
72
73
|
LIMIT 1`).get({ ":worker_id": workerId });
|
|
73
74
|
return row ?? null;
|
|
74
75
|
}
|
|
75
|
-
function
|
|
76
|
+
function latestInFlightRuntimeRecord(basePath) {
|
|
77
|
+
const records = listUnitRuntimeRecords(basePath).filter((record) => isInFlightRuntimePhase(record.phase));
|
|
78
|
+
if (records.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return records.sort((a, b) => {
|
|
81
|
+
const bTime = b.updatedAt || b.startedAt || 0;
|
|
82
|
+
const aTime = a.updatedAt || a.startedAt || 0;
|
|
83
|
+
return bTime - aTime;
|
|
84
|
+
})[0] ?? null;
|
|
85
|
+
}
|
|
86
|
+
function runtimeRecordToLockData(worker, record, sessionFile) {
|
|
87
|
+
const startedAt = Number.isFinite(record.startedAt)
|
|
88
|
+
? new Date(record.startedAt).toISOString()
|
|
89
|
+
: worker.started_at;
|
|
90
|
+
return {
|
|
91
|
+
pid: worker.pid,
|
|
92
|
+
startedAt: worker.started_at,
|
|
93
|
+
unitType: record.unitType,
|
|
94
|
+
unitId: record.unitId,
|
|
95
|
+
unitStartedAt: startedAt,
|
|
96
|
+
sessionFile,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function workerToLockData(basePath, worker) {
|
|
76
100
|
const dispatch = getLatestDispatchForWorker(worker.worker_id);
|
|
77
101
|
const sessionFile = getRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY) ?? undefined;
|
|
102
|
+
if (!dispatch) {
|
|
103
|
+
const runtimeRecord = latestInFlightRuntimeRecord(basePath);
|
|
104
|
+
if (runtimeRecord)
|
|
105
|
+
return runtimeRecordToLockData(worker, runtimeRecord, sessionFile);
|
|
106
|
+
}
|
|
78
107
|
return {
|
|
79
108
|
pid: worker.pid,
|
|
80
109
|
startedAt: worker.started_at,
|
|
@@ -172,7 +201,7 @@ export function readCrashLock(basePath) {
|
|
|
172
201
|
const projectRoot = normalizeRealPath(basePath);
|
|
173
202
|
const stale = findStaleWorkerForProject(projectRoot);
|
|
174
203
|
if (stale)
|
|
175
|
-
return workerToLockData(stale);
|
|
204
|
+
return workerToLockData(basePath, stale);
|
|
176
205
|
}
|
|
177
206
|
catch {
|
|
178
207
|
// Fall through to the legacy lock-file compatibility path.
|
|
@@ -232,18 +261,32 @@ export function formatCrashInfo(lock) {
|
|
|
232
261
|
export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
233
262
|
if (!lock.unitType || !lock.unitId || lock.unitType === "starting")
|
|
234
263
|
return;
|
|
264
|
+
emitOpenUnitEndForUnit(basePath, lock.unitType, lock.unitId, "crash-recovered");
|
|
265
|
+
}
|
|
266
|
+
export function emitOpenUnitEndForUnit(basePath, unitType, unitId, status, errorContext) {
|
|
235
267
|
try {
|
|
236
268
|
const all = queryJournal(basePath);
|
|
237
|
-
const starts = all.filter((e) => e.eventType === "unit-start" &&
|
|
269
|
+
const starts = all.filter((e) => e.eventType === "unit-start" &&
|
|
270
|
+
e.data?.unitType === unitType &&
|
|
271
|
+
e.data?.unitId === unitId);
|
|
238
272
|
if (starts.length === 0)
|
|
239
|
-
return;
|
|
240
|
-
const lastStart =
|
|
273
|
+
return false;
|
|
274
|
+
const lastStart = [...starts].reverse().find((start) => {
|
|
275
|
+
return !all.some((e) => e.eventType === "unit-end" &&
|
|
276
|
+
e.data?.unitType === unitType &&
|
|
277
|
+
e.data?.unitId === unitId &&
|
|
278
|
+
e.causedBy?.flowId === start.flowId &&
|
|
279
|
+
e.causedBy?.seq === start.seq);
|
|
280
|
+
});
|
|
281
|
+
if (!lastStart)
|
|
282
|
+
return false;
|
|
241
283
|
const alreadyClosed = all.some((e) => e.eventType === "unit-end" &&
|
|
242
|
-
e.data?.
|
|
284
|
+
e.data?.unitType === unitType &&
|
|
285
|
+
e.data?.unitId === unitId &&
|
|
243
286
|
e.causedBy?.flowId === lastStart.flowId &&
|
|
244
287
|
e.causedBy?.seq === lastStart.seq);
|
|
245
288
|
if (alreadyClosed)
|
|
246
|
-
return;
|
|
289
|
+
return false;
|
|
247
290
|
const maxSeq = all
|
|
248
291
|
.filter((e) => e.flowId === lastStart.flowId)
|
|
249
292
|
.reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
|
|
@@ -253,16 +296,19 @@ export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
|
253
296
|
seq: maxSeq + 1,
|
|
254
297
|
eventType: "unit-end",
|
|
255
298
|
data: {
|
|
256
|
-
unitType
|
|
257
|
-
unitId
|
|
258
|
-
status
|
|
299
|
+
unitType,
|
|
300
|
+
unitId,
|
|
301
|
+
status,
|
|
259
302
|
artifactVerified: false,
|
|
303
|
+
...(errorContext ? { errorContext } : {}),
|
|
260
304
|
},
|
|
261
305
|
causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
|
|
262
306
|
});
|
|
307
|
+
return true;
|
|
263
308
|
}
|
|
264
309
|
catch {
|
|
265
310
|
// Never throw from crash recovery path.
|
|
311
|
+
return false;
|
|
266
312
|
}
|
|
267
313
|
}
|
|
268
314
|
/**
|
|
@@ -253,6 +253,45 @@ export function markCanceled(dispatchId, reason) {
|
|
|
253
253
|
SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
|
|
254
254
|
WHERE id = :id AND status IN ('pending','claimed','running')`).run({ ":id": dispatchId, ":ended_at": now, ":reason": reason });
|
|
255
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Best-effort signal/crash cleanup: cancel the latest active dispatch owned by
|
|
258
|
+
* a worker when the process is exiting before the normal loop can settle it.
|
|
259
|
+
*/
|
|
260
|
+
export function markLatestActiveForWorkerCanceled(workerId, reason) {
|
|
261
|
+
if (!isDbAvailable())
|
|
262
|
+
return false;
|
|
263
|
+
const now = new Date().toISOString();
|
|
264
|
+
const db = _getAdapter();
|
|
265
|
+
const result = transaction(() => {
|
|
266
|
+
return db.prepare(`UPDATE unit_dispatches
|
|
267
|
+
SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
|
|
268
|
+
WHERE id = (
|
|
269
|
+
SELECT id FROM unit_dispatches
|
|
270
|
+
WHERE worker_id = :worker_id
|
|
271
|
+
AND status IN ('pending','claimed','running')
|
|
272
|
+
ORDER BY id DESC
|
|
273
|
+
LIMIT 1
|
|
274
|
+
)`).run({
|
|
275
|
+
":ended_at": now,
|
|
276
|
+
":reason": reason,
|
|
277
|
+
":worker_id": workerId,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
const changes = typeof result.changes === "number"
|
|
281
|
+
? result.changes
|
|
282
|
+
: 0;
|
|
283
|
+
if (changes <= 0)
|
|
284
|
+
return false;
|
|
285
|
+
insertAuditEvent({
|
|
286
|
+
eventId: randomUUID(),
|
|
287
|
+
traceId: workerId,
|
|
288
|
+
category: "orchestration",
|
|
289
|
+
type: "dispatch-canceled",
|
|
290
|
+
ts: now,
|
|
291
|
+
payload: { workerId, reason },
|
|
292
|
+
});
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
256
295
|
/**
|
|
257
296
|
* Fetch the most recent N dispatches for a unit. Used by recordDispatchClaim
|
|
258
297
|
* callers to compute attempt_n and by detect-stuck.ts (B3) to consult
|
|
@@ -46,7 +46,8 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
46
46
|
slice_id TEXT DEFAULT NULL,
|
|
47
47
|
task_id TEXT DEFAULT NULL,
|
|
48
48
|
full_content TEXT NOT NULL DEFAULT '',
|
|
49
|
-
imported_at TEXT NOT NULL DEFAULT ''
|
|
49
|
+
imported_at TEXT NOT NULL DEFAULT '',
|
|
50
|
+
content_hash TEXT DEFAULT NULL
|
|
50
51
|
)
|
|
51
52
|
`);
|
|
52
53
|
db.exec(`
|
|
@@ -64,7 +65,8 @@ export function createBaseSchemaObjects(db, hooks) {
|
|
|
64
65
|
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
65
66
|
scope TEXT NOT NULL DEFAULT 'project',
|
|
66
67
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
67
|
-
structured_fields TEXT DEFAULT NULL
|
|
68
|
+
structured_fields TEXT DEFAULT NULL,
|
|
69
|
+
last_hit_at TEXT DEFAULT NULL
|
|
68
70
|
)
|
|
69
71
|
`);
|
|
70
72
|
db.exec(`
|
|
@@ -379,6 +379,12 @@ export function applyMigrationV26MilestoneCommitAttributions(db) {
|
|
|
379
379
|
`);
|
|
380
380
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
381
381
|
}
|
|
382
|
+
export function applyMigrationV27ArtifactHash(db) {
|
|
383
|
+
ensureColumn(db, "artifacts", "content_hash", "ALTER TABLE artifacts ADD COLUMN content_hash TEXT DEFAULT NULL");
|
|
384
|
+
}
|
|
385
|
+
export function applyMigrationV28MemoryLastHitAt(db) {
|
|
386
|
+
ensureColumn(db, "memories", "last_hit_at", "ALTER TABLE memories ADD COLUMN last_hit_at TEXT DEFAULT NULL");
|
|
387
|
+
}
|
|
382
388
|
export function applyMigrationV22QualityGateRepair(db, hooks) {
|
|
383
389
|
const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
|
|
384
390
|
const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Used by init-wizard.ts and guided-flow.ts to determine what onboarding
|
|
6
6
|
* flow to show when entering a project directory.
|
|
7
7
|
*/
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
8
9
|
import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
10
|
import { dirname, join, parse as parsePath } from "node:path";
|
|
10
11
|
import { homedir } from "node:os";
|
|
@@ -171,6 +172,7 @@ const TEST_MARKERS = [
|
|
|
171
172
|
const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
|
|
172
173
|
".git",
|
|
173
174
|
".gsd",
|
|
175
|
+
".bg-shell",
|
|
174
176
|
".planning",
|
|
175
177
|
".plans",
|
|
176
178
|
".claude",
|
|
@@ -194,6 +196,7 @@ const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
|
|
|
194
196
|
"DerivedData",
|
|
195
197
|
"out",
|
|
196
198
|
]);
|
|
199
|
+
const PROJECT_CONTENT_EXCLUDE_DIRS = RECURSIVE_SCAN_IGNORED_DIRS;
|
|
197
200
|
/** Project file markers safe to detect recursively via suffix matching. */
|
|
198
201
|
const ROOT_ONLY_PROJECT_FILES = new Set([
|
|
199
202
|
".github/workflows",
|
|
@@ -429,6 +432,109 @@ export function detectProjectSignals(basePath) {
|
|
|
429
432
|
verificationCommands,
|
|
430
433
|
};
|
|
431
434
|
}
|
|
435
|
+
function normalizeGitPath(file) {
|
|
436
|
+
return file.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
437
|
+
}
|
|
438
|
+
function isProjectContentFile(file) {
|
|
439
|
+
const normalized = normalizeGitPath(file);
|
|
440
|
+
if (!normalized || normalized.endsWith("/"))
|
|
441
|
+
return false;
|
|
442
|
+
if (normalized === ".gitignore" || normalized === ".gitattributes")
|
|
443
|
+
return false;
|
|
444
|
+
const parts = normalized.split("/");
|
|
445
|
+
if (parts.some((part) => PROJECT_CONTENT_EXCLUDE_DIRS.has(part)))
|
|
446
|
+
return false;
|
|
447
|
+
if (normalized.endsWith(".DS_Store"))
|
|
448
|
+
return false;
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
function runGitLines(basePath, args) {
|
|
452
|
+
try {
|
|
453
|
+
const output = execFileSync("git", args, {
|
|
454
|
+
cwd: basePath,
|
|
455
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
456
|
+
encoding: "utf-8",
|
|
457
|
+
}).trim();
|
|
458
|
+
return output ? output.split("\n").map((line) => line.trim()).filter(Boolean) : [];
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function listTrackedProjectFiles(basePath) {
|
|
465
|
+
return runGitLines(basePath, ["ls-files"])
|
|
466
|
+
.map(normalizeGitPath)
|
|
467
|
+
.filter(isProjectContentFile);
|
|
468
|
+
}
|
|
469
|
+
function listUntrackedProjectFiles(basePath) {
|
|
470
|
+
return runGitLines(basePath, ["ls-files", "--others", "--exclude-standard"])
|
|
471
|
+
.map(normalizeGitPath)
|
|
472
|
+
.filter(isProjectContentFile);
|
|
473
|
+
}
|
|
474
|
+
function hasKnownProjectMarkers(basePath, signals) {
|
|
475
|
+
if (signals.detectedFiles.length > 0)
|
|
476
|
+
return true;
|
|
477
|
+
if (signals.xcodePlatforms.length > 0)
|
|
478
|
+
return true;
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Classify repo presence separately from ecosystem/tooling markers.
|
|
483
|
+
*
|
|
484
|
+
* Known project files identify tooling. Git-tracked/non-ignored content
|
|
485
|
+
* identifies whether this is an existing project at all. This keeps small
|
|
486
|
+
* static or documentation repos from being mislabeled as greenfield.
|
|
487
|
+
*/
|
|
488
|
+
export function classifyProject(basePath) {
|
|
489
|
+
const signals = detectProjectSignals(basePath);
|
|
490
|
+
const markers = [...signals.detectedFiles];
|
|
491
|
+
if (!signals.isGitRepo) {
|
|
492
|
+
return {
|
|
493
|
+
kind: "invalid-repo",
|
|
494
|
+
signals,
|
|
495
|
+
trackedFiles: [],
|
|
496
|
+
untrackedFiles: [],
|
|
497
|
+
contentFiles: [],
|
|
498
|
+
markers,
|
|
499
|
+
reason: "missing .git",
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const trackedFiles = listTrackedProjectFiles(basePath);
|
|
503
|
+
const untrackedFiles = listUntrackedProjectFiles(basePath);
|
|
504
|
+
const contentFiles = [...new Set([...trackedFiles, ...untrackedFiles])];
|
|
505
|
+
const hasMarkers = hasKnownProjectMarkers(basePath, signals);
|
|
506
|
+
if (hasMarkers) {
|
|
507
|
+
return {
|
|
508
|
+
kind: "typed-existing",
|
|
509
|
+
signals,
|
|
510
|
+
trackedFiles,
|
|
511
|
+
untrackedFiles,
|
|
512
|
+
contentFiles,
|
|
513
|
+
markers,
|
|
514
|
+
reason: markers.length > 0 ? `detected markers: ${markers.join(", ")}` : "detected project structure",
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (contentFiles.length > 0) {
|
|
518
|
+
return {
|
|
519
|
+
kind: "untyped-existing",
|
|
520
|
+
signals,
|
|
521
|
+
trackedFiles,
|
|
522
|
+
untrackedFiles,
|
|
523
|
+
contentFiles,
|
|
524
|
+
markers,
|
|
525
|
+
reason: "project content exists but no recognized tooling markers were found",
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
kind: "greenfield",
|
|
530
|
+
signals,
|
|
531
|
+
trackedFiles,
|
|
532
|
+
untrackedFiles,
|
|
533
|
+
contentFiles,
|
|
534
|
+
markers,
|
|
535
|
+
reason: "no tracked or non-ignored project content",
|
|
536
|
+
};
|
|
537
|
+
}
|
|
432
538
|
// ─── Xcode Platform Detection ───────────────────────────────────────────────────
|
|
433
539
|
/** Known SDKROOT values → canonical platform names. */
|
|
434
540
|
const SDKROOT_MAP = {
|
|
@@ -13,6 +13,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
|
+
import { logWarning } from "./workflow-logger.js";
|
|
16
17
|
import { detectWorktreeName, } from "./worktree.js";
|
|
17
18
|
import { SLICE_BRANCH_RE, QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
18
19
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, nativeAddPaths, nativeResetSoft, nativeCommitSubject, _resetHasChangesCache, } from "./native-git-bridge.js";
|
|
@@ -535,14 +536,45 @@ export class GitServiceImpl {
|
|
|
535
536
|
if (keyFiles.length === 0)
|
|
536
537
|
return false;
|
|
537
538
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
538
|
-
const
|
|
539
|
+
const normalized = keyFiles
|
|
539
540
|
.map(file => normalizeRepoRelativePath(this.basePath, file))
|
|
540
541
|
.filter((file) => file !== null)
|
|
541
|
-
.filter(file => !isExcludedScopedPath(file, allExclusions))
|
|
542
|
+
.filter(file => !isExcludedScopedPath(file, allExclusions));
|
|
543
|
+
// Drop entries that don't exist on disk. The LLM occasionally lists files
|
|
544
|
+
// it intended to write but didn't (or names them with wrong casing/path).
|
|
545
|
+
// Pre-`b304f738b` `git add -A` swallowed these silently; the scoped
|
|
546
|
+
// pathspec form passes each path explicitly, so a single bad entry made
|
|
547
|
+
// the whole commit fail (see #5500). Filter so valid paths still commit.
|
|
548
|
+
const missing = [];
|
|
549
|
+
const existing = [];
|
|
550
|
+
for (const path of normalized) {
|
|
551
|
+
if (existsSync(join(this.basePath, path))) {
|
|
552
|
+
existing.push(path);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
missing.push(path);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (missing.length > 0) {
|
|
559
|
+
logWarning("engine", `scoped stage: dropping ${missing.length} non-existent keyFile(s) from task commit: ${missing.join(", ")}`, { file: "git-service.ts" });
|
|
560
|
+
}
|
|
561
|
+
const paths = Array.from(new Set(existing));
|
|
542
562
|
if (paths.length === 0)
|
|
543
563
|
return false;
|
|
544
|
-
|
|
545
|
-
|
|
564
|
+
try {
|
|
565
|
+
nativeAddPaths(this.basePath, paths);
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
// Defense-in-depth: even after existence filtering, libgit2/git can
|
|
570
|
+
// still reject paths (gitignore matches, case-only differences on
|
|
571
|
+
// case-insensitive FS, submodule boundaries). Returning false lets
|
|
572
|
+
// autoCommit fall through to smartStage so the commit still goes out
|
|
573
|
+
// — restoring the resilience the unscoped path used to provide.
|
|
574
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
575
|
+
logWarning("engine", `scoped stage failed (${msg}); falling back to smartStage`, { file: "git-service.ts" });
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
546
578
|
}
|
|
547
579
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
548
580
|
_runtimeFilesCleanedUp = false;
|