@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/index.js +39 -22
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +43 -10
- package/dist/resources/extensions/gsd/auto/session.js +20 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
- package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +14 -11
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +110 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- 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 +8 -8
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +23 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +25 -24
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +39 -22
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +42 -10
- package/src/resources/extensions/gsd/auto/session.ts +22 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +112 -43
- package/src/resources/extensions/gsd/preferences-types.ts +39 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +28 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
invalidateStateCache as defaultInvalidate,
|
|
8
8
|
} from "../state.js";
|
|
9
9
|
import { clearParseCache as defaultClearParseCache } from "../files.js";
|
|
10
|
+
import { clearPathCache } from "../paths.js";
|
|
11
|
+
import { logWarning } from "../workflow-logger.js";
|
|
10
12
|
import type { GSDState } from "../types.js";
|
|
11
13
|
|
|
12
14
|
import {
|
|
@@ -68,18 +70,24 @@ export async function reconcileBeforeDispatch(
|
|
|
68
70
|
const stateSnapshot = await deps.deriveState(basePath, deps.deriveStateOptions);
|
|
69
71
|
const ctx: DriftContext = { basePath, state: stateSnapshot };
|
|
70
72
|
|
|
71
|
-
const
|
|
73
|
+
const detection = await detectAllDrift(stateSnapshot, ctx, registry);
|
|
74
|
+
const drift = detection.records;
|
|
72
75
|
if (drift.length === 0) {
|
|
73
76
|
return {
|
|
74
77
|
ok: true,
|
|
75
78
|
stateSnapshot,
|
|
76
79
|
repaired,
|
|
77
|
-
blockers:
|
|
80
|
+
blockers: [
|
|
81
|
+
...new Set([
|
|
82
|
+
...(stateSnapshot.blockers ?? []),
|
|
83
|
+
...detection.detectBlockers,
|
|
84
|
+
]),
|
|
85
|
+
],
|
|
78
86
|
};
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
const failures: ReconciliationFailureDetail[] = [];
|
|
82
|
-
const blockers: string[] = [];
|
|
90
|
+
const blockers: string[] = [...detection.detectBlockers];
|
|
83
91
|
let repairedThisPass = false;
|
|
84
92
|
for (const record of drift) {
|
|
85
93
|
const handler = registry.find((h) => h.kind === record.kind);
|
|
@@ -107,7 +115,11 @@ export async function reconcileBeforeDispatch(
|
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
if (repairedThisPass) {
|
|
118
|
+
// A repair may have mutated on-disk structure (e.g. quarantined a slice
|
|
119
|
+
// dir). Clear both the parse cache and the path/dir cache centrally so
|
|
120
|
+
// later passes and any subsequent repair see fresh filesystem state.
|
|
110
121
|
clearParseCache();
|
|
122
|
+
clearPathCache();
|
|
111
123
|
}
|
|
112
124
|
if (blockers.length > 0) {
|
|
113
125
|
let blockerState = stateSnapshot;
|
|
@@ -132,10 +144,11 @@ export async function reconcileBeforeDispatch(
|
|
|
132
144
|
deps.invalidateStateCache();
|
|
133
145
|
const finalState = await deps.deriveState(basePath, deps.deriveStateOptions);
|
|
134
146
|
const finalCtx: DriftContext = { basePath, state: finalState };
|
|
135
|
-
const
|
|
147
|
+
const finalDetection = await detectAllDrift(finalState, finalCtx, registry);
|
|
148
|
+
const persistent = finalDetection.records;
|
|
136
149
|
|
|
137
150
|
if (persistent.length > 0) {
|
|
138
|
-
const blockers: string[] = [];
|
|
151
|
+
const blockers: string[] = [...finalDetection.detectBlockers];
|
|
139
152
|
const unblockedPersistent: DriftRecord[] = [];
|
|
140
153
|
for (const record of persistent) {
|
|
141
154
|
const handler = registry.find((h) => h.kind === record.kind);
|
|
@@ -161,28 +174,45 @@ export async function reconcileBeforeDispatch(
|
|
|
161
174
|
ok: true,
|
|
162
175
|
stateSnapshot: finalState,
|
|
163
176
|
repaired,
|
|
164
|
-
blockers:
|
|
177
|
+
blockers: [
|
|
178
|
+
...new Set([
|
|
179
|
+
...(finalState.blockers ?? []),
|
|
180
|
+
...finalDetection.detectBlockers,
|
|
181
|
+
]),
|
|
182
|
+
],
|
|
165
183
|
};
|
|
166
184
|
}
|
|
167
185
|
|
|
186
|
+
interface DetectionOutcome {
|
|
187
|
+
records: DriftRecord[];
|
|
188
|
+
/** One blocker string per handler whose detect() threw. */
|
|
189
|
+
detectBlockers: string[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Run every detector. A single detector throwing (e.g. a transient file read
|
|
194
|
+
* error) must NOT abort the whole cycle and hide every later handler's drift —
|
|
195
|
+
* it is collected as a blocker so dispatch is still gated, while the remaining
|
|
196
|
+
* detectors run and their drift gets repaired (graceful degradation, ADR-017).
|
|
197
|
+
*/
|
|
168
198
|
async function detectAllDrift(
|
|
169
199
|
state: GSDState,
|
|
170
200
|
ctx: DriftContext,
|
|
171
201
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
202
|
registry: ReadonlyArray<DriftHandler<any>>,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
203
|
+
): Promise<DetectionOutcome> {
|
|
204
|
+
const records: DriftRecord[] = [];
|
|
205
|
+
const detectBlockers: string[] = [];
|
|
176
206
|
for (const handler of registry) {
|
|
177
207
|
try {
|
|
178
208
|
const detected = await handler.detect(state, ctx);
|
|
179
|
-
|
|
209
|
+
records.push(...detected);
|
|
180
210
|
} catch (cause) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
212
|
+
const blocker = `Drift detection failed for "${handler.kind}": ${message}`;
|
|
213
|
+
logWarning("reconcile", blocker);
|
|
214
|
+
detectBlockers.push(blocker);
|
|
185
215
|
}
|
|
186
216
|
}
|
|
187
|
-
return
|
|
217
|
+
return { records, detectBlockers };
|
|
188
218
|
}
|
|
@@ -21,10 +21,10 @@ export interface SpawnGateDeps extends Partial<ReconciliationDeps> {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Run reconciliation before spawning workers. Returns ok=true when the run
|
|
25
|
-
* completed without throwing
|
|
26
|
-
*
|
|
27
|
-
* ReconciliationFailedError, returns ok=false with the error message so the
|
|
24
|
+
* Run reconciliation before spawning workers. Returns ok=true only when the run
|
|
25
|
+
* completed without throwing AND surfaced no blockers; any blocker fails the
|
|
26
|
+
* gate (ok=false, reason carries the first blocker) so callers must not spawn.
|
|
27
|
+
* On ReconciliationFailedError, returns ok=false with the error message so the
|
|
28
28
|
* caller can surface it to the user without re-throwing.
|
|
29
29
|
*
|
|
30
30
|
* Other unexpected errors propagate; they are not part of the drift
|
|
@@ -483,7 +483,7 @@ async function buildRegistryAndFindActive(
|
|
|
483
483
|
let activeMilestoneSlices: SliceRow[] = [];
|
|
484
484
|
let activeMilestoneFound = false;
|
|
485
485
|
let activeMilestoneHasDraft = false;
|
|
486
|
-
let firstDeferredQueuedShell: { id: string; title: string; deps: string[] } | null = null;
|
|
486
|
+
let firstDeferredQueuedShell: { id: string; title: string; deps: string[]; hasDraftContext: boolean } | null = null;
|
|
487
487
|
|
|
488
488
|
for (const m of milestones) {
|
|
489
489
|
if (parkedMilestoneIds.has(m.id)) {
|
|
@@ -505,6 +505,8 @@ async function buildRegistryAndFindActive(
|
|
|
505
505
|
const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
|
|
506
506
|
|
|
507
507
|
const title = stripMilestonePrefix(m.title) || m.id;
|
|
508
|
+
const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
509
|
+
const hasDraftContext = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
508
510
|
|
|
509
511
|
if (!activeMilestoneFound) {
|
|
510
512
|
const deps = m.depends_on;
|
|
@@ -515,9 +517,9 @@ async function buildRegistryAndFindActive(
|
|
|
515
517
|
continue;
|
|
516
518
|
}
|
|
517
519
|
|
|
518
|
-
if (m.status === 'queued' && slices.length === 0) {
|
|
520
|
+
if (m.status === 'queued' && slices.length === 0 && !hasContext) {
|
|
519
521
|
if (!firstDeferredQueuedShell) {
|
|
520
|
-
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
522
|
+
firstDeferredQueuedShell = { id: m.id, title, deps, hasDraftContext };
|
|
521
523
|
}
|
|
522
524
|
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
523
525
|
continue;
|
|
@@ -531,7 +533,7 @@ async function buildRegistryAndFindActive(
|
|
|
531
533
|
continue;
|
|
532
534
|
}
|
|
533
535
|
|
|
534
|
-
if (m.status === 'needs-discussion') activeMilestoneHasDraft = true;
|
|
536
|
+
if ((m.status === 'needs-discussion' && !hasContext) || hasDraftContext) activeMilestoneHasDraft = true;
|
|
535
537
|
|
|
536
538
|
activeMilestone = { id: m.id, title };
|
|
537
539
|
activeMilestoneSlices = slices;
|
|
@@ -548,6 +550,7 @@ async function buildRegistryAndFindActive(
|
|
|
548
550
|
activeMilestone = { id: shell.id, title: shell.title };
|
|
549
551
|
activeMilestoneSlices = [];
|
|
550
552
|
activeMilestoneFound = true;
|
|
553
|
+
if (shell.hasDraftContext) activeMilestoneHasDraft = true;
|
|
551
554
|
const entry = registry.find(e => e.id === shell.id);
|
|
552
555
|
if (entry) entry.status = 'active';
|
|
553
556
|
}
|
|
@@ -36,6 +36,7 @@ import { registerAutoWorker } from "../db/auto-workers.js";
|
|
|
36
36
|
import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
37
37
|
import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
|
|
38
38
|
import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
|
|
39
|
+
import { SourceObservationStore } from "../source-observations.js";
|
|
39
40
|
|
|
40
41
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
@@ -1102,6 +1103,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
|
|
|
1102
1103
|
currentMilestoneId: "M001",
|
|
1103
1104
|
currentUnit: null,
|
|
1104
1105
|
currentUnitRouting: null,
|
|
1106
|
+
sourceObservations: new SourceObservationStore(),
|
|
1105
1107
|
completedUnits: [],
|
|
1106
1108
|
resourceVersionOnStart: null,
|
|
1107
1109
|
lastPromptCharCount: undefined,
|
|
@@ -1126,6 +1128,19 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
|
|
|
1126
1128
|
newSession: () => Promise.resolve({ cancelled: false }),
|
|
1127
1129
|
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1128
1130
|
},
|
|
1131
|
+
setCurrentUnit(this: any, unit: any) {
|
|
1132
|
+
this.currentUnit = unit;
|
|
1133
|
+
this.sourceObservations.beginUnit({
|
|
1134
|
+
unitType: unit.type,
|
|
1135
|
+
unitId: unit.id,
|
|
1136
|
+
startedAt: unit.startedAt,
|
|
1137
|
+
basePath: unit.workspaceRoot ?? this.basePath,
|
|
1138
|
+
});
|
|
1139
|
+
},
|
|
1140
|
+
clearCurrentUnit(this: any) {
|
|
1141
|
+
this.currentUnit = null;
|
|
1142
|
+
this.sourceObservations.clear();
|
|
1143
|
+
},
|
|
1129
1144
|
clearTimers: () => {},
|
|
1130
1145
|
...overrides,
|
|
1131
1146
|
} as any;
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
10
|
-
import { ModelPolicyDispatchBlockedError, resolvePreferredModelConfig, resolveModelId, selectAndApplyModel } from "../auto-model-selection.js";
|
|
10
|
+
import { ModelPolicyDispatchBlockedError, resolvePreferredModelConfig, resolveModelId, selectAndApplyModel, floorThinkingLevelForUnit } from "../auto-model-selection.js";
|
|
11
11
|
|
|
12
12
|
function makeTempDir(prefix: string): string {
|
|
13
13
|
return mkdtempSync(join(tmpdir(), prefix));
|
|
@@ -534,6 +534,304 @@ test("selectAndApplyModel re-applies captured thinking level after setModel succ
|
|
|
534
534
|
assert.deepEqual(thinkingLevels, [{ effort: "high" }]);
|
|
535
535
|
});
|
|
536
536
|
|
|
537
|
+
// ─── floorThinkingLevelForUnit (#read-bash-thrash) ─────────────────────
|
|
538
|
+
test("floorThinkingLevelForUnit raises minimal/low to the floor for execute-task", () => {
|
|
539
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "off" as any), "medium");
|
|
540
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "minimal" as any), "medium");
|
|
541
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "low" as any), "medium");
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("floorThinkingLevelForUnit never lowers a level already at/above the floor", () => {
|
|
545
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "medium" as any), "medium");
|
|
546
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "high" as any), "high");
|
|
547
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", "xhigh" as any), "xhigh");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("floorThinkingLevelForUnit leaves non-execute-task units untouched", () => {
|
|
551
|
+
for (const unit of ["plan-slice", "plan-milestone", "research-milestone", "complete-slice", "validate-milestone"]) {
|
|
552
|
+
assert.equal(floorThinkingLevelForUnit(unit, "minimal" as any), "minimal");
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("floorThinkingLevelForUnit passes through null/undefined and unrecognized shapes", () => {
|
|
557
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", null), null);
|
|
558
|
+
assert.equal(floorThinkingLevelForUnit("execute-task", undefined), undefined);
|
|
559
|
+
// A richer host snapshot object must not be coerced into a bare string.
|
|
560
|
+
const snapshot = { effort: "minimal" } as any;
|
|
561
|
+
assert.deepEqual(floorThinkingLevelForUnit("execute-task", snapshot), snapshot);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("selectAndApplyModel raises minimal thinking to the floor for execute-task", async (t) => {
|
|
565
|
+
const originalCwd = process.cwd();
|
|
566
|
+
const tempProject = makeTempDir("gsd-routing-thinking-floor-");
|
|
567
|
+
const thinkingLevels: unknown[] = [];
|
|
568
|
+
t.after(() => {
|
|
569
|
+
process.chdir(originalCwd);
|
|
570
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
574
|
+
writeFileSync(
|
|
575
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
576
|
+
["---", "models:", " execute-task: claude-sonnet-4-6", "---"].join("\n"),
|
|
577
|
+
"utf-8",
|
|
578
|
+
);
|
|
579
|
+
process.chdir(tempProject);
|
|
580
|
+
|
|
581
|
+
await selectAndApplyModel(
|
|
582
|
+
{
|
|
583
|
+
modelRegistry: { getAvailable: () => [{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" }] },
|
|
584
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
585
|
+
ui: { notify: () => {} },
|
|
586
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
|
|
587
|
+
} as any,
|
|
588
|
+
{
|
|
589
|
+
setModel: async () => true,
|
|
590
|
+
setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
|
|
591
|
+
emitBeforeModelSelect: async () => undefined,
|
|
592
|
+
getActiveTools: () => [],
|
|
593
|
+
emitAdjustToolSet: async () => undefined,
|
|
594
|
+
setActiveTools: () => {},
|
|
595
|
+
} as any,
|
|
596
|
+
"execute-task",
|
|
597
|
+
"M001/S01/T01",
|
|
598
|
+
tempProject,
|
|
599
|
+
undefined,
|
|
600
|
+
false,
|
|
601
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
602
|
+
undefined,
|
|
603
|
+
true,
|
|
604
|
+
undefined,
|
|
605
|
+
"minimal" as any,
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
assert.deepEqual(thinkingLevels, ["medium"]);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("selectAndApplyModel capability-clamps an unsupported thinking level (ADR-026)", async (t) => {
|
|
612
|
+
const originalCwd = process.cwd();
|
|
613
|
+
const tempProject = makeTempDir("gsd-routing-thinking-clamp-");
|
|
614
|
+
const thinkingLevels: unknown[] = [];
|
|
615
|
+
t.after(() => {
|
|
616
|
+
process.chdir(originalCwd);
|
|
617
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// Reasoning-capable model whose map omits xhigh → xhigh must clamp to high.
|
|
621
|
+
const model = {
|
|
622
|
+
id: "claude-sonnet-4-6",
|
|
623
|
+
provider: "anthropic",
|
|
624
|
+
api: "anthropic-messages",
|
|
625
|
+
reasoning: true,
|
|
626
|
+
thinkingLevelMap: { low: "low", medium: "medium", high: "high" },
|
|
627
|
+
};
|
|
628
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
629
|
+
writeFileSync(
|
|
630
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
631
|
+
["---", "models:", " planning: claude-sonnet-4-6", "---"].join("\n"),
|
|
632
|
+
"utf-8",
|
|
633
|
+
);
|
|
634
|
+
process.chdir(tempProject);
|
|
635
|
+
|
|
636
|
+
await selectAndApplyModel(
|
|
637
|
+
{
|
|
638
|
+
modelRegistry: { getAvailable: () => [model] },
|
|
639
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
640
|
+
ui: { notify: () => {} },
|
|
641
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
|
|
642
|
+
} as any,
|
|
643
|
+
{
|
|
644
|
+
setModel: async () => true,
|
|
645
|
+
setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
|
|
646
|
+
emitBeforeModelSelect: async () => undefined,
|
|
647
|
+
getActiveTools: () => [],
|
|
648
|
+
emitAdjustToolSet: async () => undefined,
|
|
649
|
+
setActiveTools: () => {},
|
|
650
|
+
} as any,
|
|
651
|
+
"plan-slice",
|
|
652
|
+
"M001/S01",
|
|
653
|
+
tempProject,
|
|
654
|
+
undefined,
|
|
655
|
+
false,
|
|
656
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
657
|
+
undefined,
|
|
658
|
+
true,
|
|
659
|
+
undefined,
|
|
660
|
+
"xhigh" as any,
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
assert.deepEqual(thinkingLevels, ["high"]);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("selectAndApplyModel applies an explicit per-phase thinking level (ADR-026)", async (t) => {
|
|
667
|
+
const originalCwd = process.cwd();
|
|
668
|
+
const tempProject = makeTempDir("gsd-routing-thinking-explicit-");
|
|
669
|
+
const thinkingLevels: unknown[] = [];
|
|
670
|
+
t.after(() => {
|
|
671
|
+
process.chdir(originalCwd);
|
|
672
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const model = {
|
|
676
|
+
id: "claude-sonnet-4-6",
|
|
677
|
+
provider: "anthropic",
|
|
678
|
+
api: "anthropic-messages",
|
|
679
|
+
reasoning: true,
|
|
680
|
+
thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "xhigh" },
|
|
681
|
+
};
|
|
682
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
683
|
+
writeFileSync(
|
|
684
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
685
|
+
["---", "models:", " planning:", " model: claude-sonnet-4-6", " thinking: xhigh", "---"].join("\n"),
|
|
686
|
+
"utf-8",
|
|
687
|
+
);
|
|
688
|
+
process.chdir(tempProject);
|
|
689
|
+
|
|
690
|
+
await selectAndApplyModel(
|
|
691
|
+
{
|
|
692
|
+
modelRegistry: { getAvailable: () => [model] },
|
|
693
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
694
|
+
ui: { notify: () => {} },
|
|
695
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
|
|
696
|
+
} as any,
|
|
697
|
+
{
|
|
698
|
+
setModel: async () => true,
|
|
699
|
+
setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
|
|
700
|
+
emitBeforeModelSelect: async () => undefined,
|
|
701
|
+
getActiveTools: () => [],
|
|
702
|
+
emitAdjustToolSet: async () => undefined,
|
|
703
|
+
setActiveTools: () => {},
|
|
704
|
+
} as any,
|
|
705
|
+
"plan-slice",
|
|
706
|
+
"M001/S01",
|
|
707
|
+
tempProject,
|
|
708
|
+
undefined,
|
|
709
|
+
false,
|
|
710
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
711
|
+
undefined,
|
|
712
|
+
true,
|
|
713
|
+
undefined,
|
|
714
|
+
// Session level is "low"; the explicit planning thinking (xhigh) must win.
|
|
715
|
+
"low" as any,
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
assert.deepEqual(thinkingLevels, ["xhigh"]);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
test("selectAndApplyModel applies explicit thinking with no model pin (interactive, ADR-026)", async (t) => {
|
|
722
|
+
const originalCwd = process.cwd();
|
|
723
|
+
const tempProject = makeTempDir("gsd-routing-thinking-nomodel-");
|
|
724
|
+
const thinkingLevels: unknown[] = [];
|
|
725
|
+
t.after(() => {
|
|
726
|
+
process.chdir(originalCwd);
|
|
727
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const model = {
|
|
731
|
+
id: "claude-sonnet-4-6",
|
|
732
|
+
provider: "anthropic",
|
|
733
|
+
api: "anthropic-messages",
|
|
734
|
+
reasoning: true,
|
|
735
|
+
thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "xhigh" },
|
|
736
|
+
};
|
|
737
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
738
|
+
// A `thinking:` block with NO models config — the interactive guided-flow
|
|
739
|
+
// scenario the bug report flagged (no per-phase model, no start model).
|
|
740
|
+
writeFileSync(
|
|
741
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
742
|
+
["---", "thinking:", " planning: high", "---"].join("\n"),
|
|
743
|
+
"utf-8",
|
|
744
|
+
);
|
|
745
|
+
process.chdir(tempProject);
|
|
746
|
+
|
|
747
|
+
await selectAndApplyModel(
|
|
748
|
+
{
|
|
749
|
+
modelRegistry: { getAvailable: () => [model] },
|
|
750
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
751
|
+
ui: { notify: () => {} },
|
|
752
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
|
|
753
|
+
} as any,
|
|
754
|
+
{
|
|
755
|
+
setModel: async () => true,
|
|
756
|
+
setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
|
|
757
|
+
emitBeforeModelSelect: async () => undefined,
|
|
758
|
+
getActiveTools: () => [],
|
|
759
|
+
emitAdjustToolSet: async () => undefined,
|
|
760
|
+
setActiveTools: () => {},
|
|
761
|
+
} as any,
|
|
762
|
+
"plan-slice",
|
|
763
|
+
"M001/S01",
|
|
764
|
+
tempProject,
|
|
765
|
+
undefined,
|
|
766
|
+
false,
|
|
767
|
+
null, // no autoModeStartModel
|
|
768
|
+
undefined,
|
|
769
|
+
false, // isAutoMode = false (interactive)
|
|
770
|
+
undefined,
|
|
771
|
+
undefined, // no captured session thinking level
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
// No model branch runs, but the explicit block thinking must still apply.
|
|
775
|
+
assert.deepEqual(thinkingLevels, ["high"]);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test("selectAndApplyModel clamps explicit no-model thinking via ctx.model when registry lookup fails (ADR-026)", async (t) => {
|
|
779
|
+
const originalCwd = process.cwd();
|
|
780
|
+
const tempProject = makeTempDir("gsd-routing-thinking-ctxmodel-");
|
|
781
|
+
const thinkingLevels: unknown[] = [];
|
|
782
|
+
t.after(() => {
|
|
783
|
+
process.chdir(originalCwd);
|
|
784
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// ctx.model carries reasoning capability (map omits xhigh) but the registry
|
|
788
|
+
// returns nothing, so resolveModelId fails and ctx.model is the clamp source.
|
|
789
|
+
const ctxModel = {
|
|
790
|
+
id: "claude-sonnet-4-6",
|
|
791
|
+
provider: "anthropic",
|
|
792
|
+
api: "anthropic-messages",
|
|
793
|
+
reasoning: true,
|
|
794
|
+
thinkingLevelMap: { low: "low", medium: "medium", high: "high" },
|
|
795
|
+
};
|
|
796
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
797
|
+
writeFileSync(
|
|
798
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
799
|
+
["---", "thinking:", " planning: xhigh", "---"].join("\n"),
|
|
800
|
+
"utf-8",
|
|
801
|
+
);
|
|
802
|
+
process.chdir(tempProject);
|
|
803
|
+
|
|
804
|
+
await selectAndApplyModel(
|
|
805
|
+
{
|
|
806
|
+
modelRegistry: { getAvailable: () => [] }, // registry lookup fails
|
|
807
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
808
|
+
ui: { notify: () => {} },
|
|
809
|
+
model: ctxModel,
|
|
810
|
+
} as any,
|
|
811
|
+
{
|
|
812
|
+
setModel: async () => true,
|
|
813
|
+
setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
|
|
814
|
+
emitBeforeModelSelect: async () => undefined,
|
|
815
|
+
getActiveTools: () => [],
|
|
816
|
+
emitAdjustToolSet: async () => undefined,
|
|
817
|
+
setActiveTools: () => {},
|
|
818
|
+
} as any,
|
|
819
|
+
"plan-slice",
|
|
820
|
+
"M001/S01",
|
|
821
|
+
tempProject,
|
|
822
|
+
undefined,
|
|
823
|
+
false,
|
|
824
|
+
null,
|
|
825
|
+
undefined,
|
|
826
|
+
false,
|
|
827
|
+
undefined,
|
|
828
|
+
undefined,
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
// xhigh is unsupported by ctx.model → clamped to high, never sent verbatim.
|
|
832
|
+
assert.deepEqual(thinkingLevels, ["high"]);
|
|
833
|
+
});
|
|
834
|
+
|
|
537
835
|
test("resolveModelId: anthropic wins over claude-code when session provider is not claude-code", () => {
|
|
538
836
|
const availableModels = [
|
|
539
837
|
{ id: "claude-sonnet-4-6", provider: "claude-code" },
|
|
@@ -173,6 +173,38 @@ test("cleanupAfterLoopExit preserves completion closeout surface after stopAuto
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
+
test("cleanupAfterLoopExit clears completionStopInProgress even when preserveStepSurfaceAfterLoopExit is also set", async () => {
|
|
177
|
+
const statusCalls: unknown[] = [];
|
|
178
|
+
const widgetCalls: unknown[] = [];
|
|
179
|
+
|
|
180
|
+
autoSession.reset();
|
|
181
|
+
autoSession.active = true;
|
|
182
|
+
autoSession.paused = false;
|
|
183
|
+
autoSession.completionStopInProgress = true;
|
|
184
|
+
autoSession.preserveStepSurfaceAfterLoopExit = true;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
await cleanupAfterLoopExit({
|
|
188
|
+
hasUI: true,
|
|
189
|
+
ui: {
|
|
190
|
+
setStatus: (...args: unknown[]) => statusCalls.push(args),
|
|
191
|
+
setWidget: (...args: unknown[]) => widgetCalls.push(args),
|
|
192
|
+
setHeader: () => {},
|
|
193
|
+
notify: () => {},
|
|
194
|
+
},
|
|
195
|
+
} as any);
|
|
196
|
+
|
|
197
|
+
assert.equal(
|
|
198
|
+
autoSession.completionStopInProgress,
|
|
199
|
+
false,
|
|
200
|
+
"completionStopInProgress must be cleared even when preserveStepSurfaceAfterLoopExit was also set",
|
|
201
|
+
);
|
|
202
|
+
assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
|
|
203
|
+
} finally {
|
|
204
|
+
autoSession.reset();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
176
208
|
test("pauseAuto preserves artifact retry counts across pause/resume", async () => {
|
|
177
209
|
const base = mkdtempSync(join(tmpdir(), "gsd-pause-retry-count-"));
|
|
178
210
|
const previousCwd = process.cwd();
|