gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27
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 +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +14 -14
- 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/required-server-files.json +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.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 +14 -14
- 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/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -10,7 +10,7 @@ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@g
|
|
|
10
10
|
import type { GSDState } from "./types.js";
|
|
11
11
|
import { showNextAction } from "../shared/tui.js";
|
|
12
12
|
import { loadFile, saveFile } from "./files.js";
|
|
13
|
-
import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
|
|
13
|
+
import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
|
|
14
14
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
15
15
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
16
16
|
import {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
buildPlanSlicePrompt,
|
|
22
22
|
buildSkillActivationBlock,
|
|
23
23
|
} from "./auto-prompts.js";
|
|
24
|
-
import { deriveState } from "./state.js";
|
|
24
|
+
import { deriveState, isGhostMilestone } from "./state.js";
|
|
25
25
|
import { invalidateAllCaches } from "./cache.js";
|
|
26
26
|
import { startAutoDetached } from "./auto.js";
|
|
27
27
|
import { clearLock } from "./crash-recovery.js";
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
formatPriorContextBrief,
|
|
70
70
|
} from "./preparation.js";
|
|
71
71
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
72
|
+
import { createWorkspace, scopeMilestone, type MilestoneScope } from "./workspace.js";
|
|
72
73
|
|
|
73
74
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
74
75
|
export {
|
|
@@ -82,6 +83,48 @@ export {
|
|
|
82
83
|
buildExistingMilestonesContext,
|
|
83
84
|
} from "./guided-flow-queue.js";
|
|
84
85
|
import { logWarning } from "./workflow-logger.js";
|
|
86
|
+
import { deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
87
|
+
import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
|
|
88
|
+
|
|
89
|
+
// ─── Scope-based validator wrappers ──────────────────────────────────────────
|
|
90
|
+
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
91
|
+
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
92
|
+
// The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
|
|
93
|
+
// state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Scope-based overload of verifyExpectedArtifact.
|
|
97
|
+
* Uses scope.workspace.projectRoot as the authoritative base path, making
|
|
98
|
+
* the check immune to cwd-drift and worktree-path divergence.
|
|
99
|
+
*/
|
|
100
|
+
export function verifyExpectedArtifactForScope(
|
|
101
|
+
scope: MilestoneScope,
|
|
102
|
+
unitType: string,
|
|
103
|
+
unitId: string,
|
|
104
|
+
): boolean {
|
|
105
|
+
return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Scope-based overload of resolveExpectedArtifactPath.
|
|
110
|
+
* Returns the canonical absolute path (or null) using the scope's projectRoot.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveExpectedArtifactPathForScope(
|
|
113
|
+
scope: MilestoneScope,
|
|
114
|
+
unitType: string,
|
|
115
|
+
unitId: string,
|
|
116
|
+
): string | null {
|
|
117
|
+
return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Scope-based overload of isGhostMilestone.
|
|
122
|
+
* Binds basePath and milestoneId from the scope, ensuring path resolution
|
|
123
|
+
* uses the canonical project root regardless of the cwd at call time.
|
|
124
|
+
*/
|
|
125
|
+
export function isGhostMilestoneByScope(scope: MilestoneScope): boolean {
|
|
126
|
+
return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
|
|
127
|
+
}
|
|
85
128
|
|
|
86
129
|
function needsPlanV2Gate(state: GSDState): boolean {
|
|
87
130
|
return state.phase === "executing"
|
|
@@ -135,6 +178,13 @@ interface PendingAutoStartEntry {
|
|
|
135
178
|
// #4573: counter for how many times the LLM emitted the ready phrase
|
|
136
179
|
// without writing the required artifacts. Cleared on entry delete/recreate.
|
|
137
180
|
readyRejectCount?: number;
|
|
181
|
+
// C1: scope is pinned at reservation time so path resolution is immune to
|
|
182
|
+
// cwd-drift between discuss and checkAutoStartAfterDiscuss.
|
|
183
|
+
// TODO(C3): basePath becomes redundant once all consumers migrate to scope.
|
|
184
|
+
scope: MilestoneScope;
|
|
185
|
+
// H1: retry counter for Gate 1b plan-blocked recovery. Capped at
|
|
186
|
+
// MAX_PLAN_BLOCKED_RECOVERIES to prevent infinite recovery loops (#5012).
|
|
187
|
+
planBlockedRecoveryCount: number;
|
|
138
188
|
}
|
|
139
189
|
|
|
140
190
|
interface PendingDeepProjectSetupEntry {
|
|
@@ -152,6 +202,11 @@ interface PendingDeepProjectSetupEntry {
|
|
|
152
202
|
// phrase before giving up and asking the user to re-run /gsd.
|
|
153
203
|
const MAX_READY_REJECTS = 2;
|
|
154
204
|
|
|
205
|
+
// H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
|
|
206
|
+
// consecutive recovery attempts the loop is stopped and the user is directed
|
|
207
|
+
// to investigate manually.
|
|
208
|
+
const MAX_PLAN_BLOCKED_RECOVERIES = 3;
|
|
209
|
+
|
|
155
210
|
// #4573: matches the canonical ready phrase the discuss prompt asks the LLM
|
|
156
211
|
// to emit. Accepts any M-prefixed milestone ID (three digits + optional
|
|
157
212
|
// suffix) with optional trailing punctuation.
|
|
@@ -187,9 +242,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
|
|
|
187
242
|
/**
|
|
188
243
|
* Backward-compat bridge: returns a mutable reference to the entry matching
|
|
189
244
|
* basePath, or the sole entry when only one session exists.
|
|
190
|
-
*
|
|
245
|
+
* Exported for testing — internal use only in production code.
|
|
191
246
|
*/
|
|
192
|
-
function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
|
|
247
|
+
export function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
|
|
193
248
|
if (basePath) return pendingAutoStartMap.get(basePath) ?? null;
|
|
194
249
|
if (pendingAutoStartMap.size === 1) return pendingAutoStartMap.values().next().value!;
|
|
195
250
|
return null;
|
|
@@ -233,7 +288,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath: string, entries: st
|
|
|
233
288
|
* Exported for testing (#2985).
|
|
234
289
|
*/
|
|
235
290
|
export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean; createdAt?: number }): void {
|
|
236
|
-
|
|
291
|
+
const ws = createWorkspace(entry.basePath);
|
|
292
|
+
const scope = scopeMilestone(ws, entry.milestoneId);
|
|
293
|
+
pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope } as PendingAutoStartEntry);
|
|
237
294
|
}
|
|
238
295
|
|
|
239
296
|
/**
|
|
@@ -343,6 +400,10 @@ export async function checkDeepProjectSetupAfterTurn(
|
|
|
343
400
|
if (!entry) return false;
|
|
344
401
|
|
|
345
402
|
if (entry.currentUnitType && entry.currentUnitId) {
|
|
403
|
+
// TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
|
|
404
|
+
// because deep-project-setup units span non-milestone unit types (discuss-project,
|
|
405
|
+
// discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
|
|
406
|
+
// PendingDeepProjectSetupEntry is extended with a scope field.
|
|
346
407
|
const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
|
|
347
408
|
if (!artifactReady) {
|
|
348
409
|
return false;
|
|
@@ -426,17 +487,77 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
426
487
|
|
|
427
488
|
// Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
|
|
428
489
|
// The "discuss" path creates CONTEXT.md; the "plan" path creates ROADMAP.md.
|
|
429
|
-
|
|
430
|
-
const
|
|
490
|
+
// Use pinned scope (immune to cwd-drift) for existence checks.
|
|
491
|
+
const contextFilePath = entry.scope.contextFile();
|
|
492
|
+
const roadmapFilePath = entry.scope.roadmapFile();
|
|
493
|
+
const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
|
|
494
|
+
const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
|
|
431
495
|
if (!contextFile && !roadmapFile) return false; // neither artifact yet — keep waiting
|
|
432
496
|
|
|
497
|
+
// Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
|
|
498
|
+
// If the DB is available and the row is still "queued" but CONTEXT.md already exists on
|
|
499
|
+
// disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
|
|
500
|
+
// depth-verification gate. Emit a recovery hint so the next agent turn can retry
|
|
501
|
+
// gsd_plan_milestone, then return false (keep blocking auto-start).
|
|
502
|
+
// If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
|
|
503
|
+
if (isDbAvailable()) {
|
|
504
|
+
const dbRow = getMilestone(milestoneId);
|
|
505
|
+
if (dbRow?.status === "queued" && contextFile) {
|
|
506
|
+
if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
|
|
507
|
+
// H1: recovery loop cap reached — stop triggering new turns, escalate to user.
|
|
508
|
+
logWarning(
|
|
509
|
+
"guided",
|
|
510
|
+
`Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
|
|
511
|
+
`(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`,
|
|
512
|
+
);
|
|
513
|
+
ctx.ui.notify(
|
|
514
|
+
`Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
|
|
515
|
+
`Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`,
|
|
516
|
+
"error",
|
|
517
|
+
);
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
logWarning(
|
|
521
|
+
"guided",
|
|
522
|
+
`Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
|
|
523
|
+
`plan_milestone was blocked; emitting recovery hint ` +
|
|
524
|
+
`(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`,
|
|
525
|
+
);
|
|
526
|
+
ctx.ui.notify(
|
|
527
|
+
`Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
|
|
528
|
+
`Retrying gsd_plan_milestone to complete the blocked planning step.`,
|
|
529
|
+
"warning",
|
|
530
|
+
);
|
|
531
|
+
try {
|
|
532
|
+
pi.sendMessage(
|
|
533
|
+
{
|
|
534
|
+
customType: "gsd-plan-milestone-blocked-recovery",
|
|
535
|
+
content:
|
|
536
|
+
`Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
|
|
537
|
+
`"queued". The gsd_plan_milestone tool was previously blocked by the ` +
|
|
538
|
+
`depth-verification gate. Call gsd_plan_milestone now to complete the ` +
|
|
539
|
+
`planning phase.`,
|
|
540
|
+
display: false,
|
|
541
|
+
},
|
|
542
|
+
{ triggerTurn: true },
|
|
543
|
+
);
|
|
544
|
+
// Increment only after a successful dispatch so transient sendMessage
|
|
545
|
+
// failures do not consume recovery budget.
|
|
546
|
+
entry.planBlockedRecoveryCount += 1;
|
|
547
|
+
} catch (e) {
|
|
548
|
+
logWarning("guided", `Gate 1b recovery sendMessage failed: ${(e as Error).message}`);
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
433
554
|
// Gate 2: STATE.md must exist — written as the last step in the discuss
|
|
434
555
|
// output phase. This prevents auto-start from firing during Phase 3
|
|
435
556
|
// (sequential readiness gates for remaining milestones) in multi-milestone
|
|
436
557
|
// discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
|
|
437
558
|
// processed yet.
|
|
438
|
-
const
|
|
439
|
-
if (!
|
|
559
|
+
const stateFilePath = entry.scope.stateFile();
|
|
560
|
+
if (!existsSync(stateFilePath)) return false; // discussion not finalized yet
|
|
440
561
|
|
|
441
562
|
// Gate 3: Multi-milestone completeness warning
|
|
442
563
|
// Parse PROJECT.md for milestone sequence, warn if any are missing context.
|
|
@@ -469,7 +590,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
469
590
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
470
591
|
// When it exists, validate it before auto-starting. Project history alone is
|
|
471
592
|
// not a reliable signal for the current discussion mode.
|
|
472
|
-
const manifestPath = join(
|
|
593
|
+
const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
|
|
473
594
|
if (existsSync(manifestPath)) {
|
|
474
595
|
try {
|
|
475
596
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -1104,7 +1225,7 @@ export async function showHeadlessMilestoneCreation(
|
|
|
1104
1225
|
const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
|
|
1105
1226
|
|
|
1106
1227
|
// Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
|
|
1107
|
-
|
|
1228
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
|
|
1108
1229
|
|
|
1109
1230
|
// Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
|
|
1110
1231
|
// and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
|
|
@@ -1301,12 +1422,12 @@ export async function showDiscuss(
|
|
|
1301
1422
|
const seed = draftContent
|
|
1302
1423
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
1303
1424
|
: basePrompt;
|
|
1304
|
-
|
|
1425
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1305
1426
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1306
1427
|
} else if (choice === "discuss_fresh") {
|
|
1307
1428
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1308
1429
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1309
|
-
|
|
1430
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1310
1431
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1311
1432
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1312
1433
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -1318,7 +1439,7 @@ export async function showDiscuss(
|
|
|
1318
1439
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1319
1440
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1320
1441
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1321
|
-
|
|
1442
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
|
|
1322
1443
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1323
1444
|
}
|
|
1324
1445
|
return;
|
|
@@ -1609,6 +1730,9 @@ function selfHealRuntimeRecords(basePath: string, ctx: ExtensionContext): { clea
|
|
|
1609
1730
|
for (const record of records) {
|
|
1610
1731
|
const { unitType, unitId, phase } = record;
|
|
1611
1732
|
// Clear records whose expected artifact already exists (completed but not cleaned up)
|
|
1733
|
+
// TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
|
|
1734
|
+
// units), so it cannot be converted to resolveExpectedArtifactPathForScope without
|
|
1735
|
+
// first establishing a per-record scope. Migrate once unit runtime records carry scope info.
|
|
1612
1736
|
const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
|
|
1613
1737
|
if (artifactPath && existsSync(artifactPath)) {
|
|
1614
1738
|
clearUnitRuntimeRecord(basePath, unitType, unitId);
|
|
@@ -1723,7 +1847,7 @@ async function handleMilestoneActions(
|
|
|
1723
1847
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1724
1848
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1725
1849
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1726
|
-
|
|
1850
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1727
1851
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1728
1852
|
`New milestone ${nextId}.`,
|
|
1729
1853
|
basePath
|
|
@@ -1841,10 +1965,12 @@ export async function showSmartEntry(
|
|
|
1841
1965
|
if (interrupted.classification === "stale") {
|
|
1842
1966
|
clearLock(basePath);
|
|
1843
1967
|
if (interrupted.pausedSession) {
|
|
1968
|
+
// Phase C pt 2: paused-session.json migrated to runtime_kv
|
|
1969
|
+
// (global scope, key PAUSED_SESSION_KV_KEY).
|
|
1844
1970
|
try {
|
|
1845
|
-
|
|
1971
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
1846
1972
|
} catch (e) {
|
|
1847
|
-
logWarning("guided", `stale
|
|
1973
|
+
logWarning("guided", `stale paused-session DB cleanup failed: ${(e as Error).message}`, { file: "guided-flow.ts" });
|
|
1848
1974
|
}
|
|
1849
1975
|
}
|
|
1850
1976
|
} else if (interrupted.classification === "recoverable") {
|
|
@@ -1951,7 +2077,7 @@ export async function showSmartEntry(
|
|
|
1951
2077
|
|
|
1952
2078
|
if (isFirst) {
|
|
1953
2079
|
// First ever — skip wizard, just ask directly
|
|
1954
|
-
|
|
2080
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1955
2081
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1956
2082
|
`New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
|
|
1957
2083
|
basePath
|
|
@@ -1972,7 +2098,7 @@ export async function showSmartEntry(
|
|
|
1972
2098
|
});
|
|
1973
2099
|
|
|
1974
2100
|
if (choice === "new_milestone") {
|
|
1975
|
-
|
|
2101
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1976
2102
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1977
2103
|
`New milestone ${nextId}.`,
|
|
1978
2104
|
basePath
|
|
@@ -1986,7 +2112,7 @@ export async function showSmartEntry(
|
|
|
1986
2112
|
const milestoneTitle = state.activeMilestone.title;
|
|
1987
2113
|
|
|
1988
2114
|
if (planV2GateDecision === "recover-missing-context") {
|
|
1989
|
-
|
|
2115
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1990
2116
|
await dispatchWorkflow(
|
|
1991
2117
|
pi,
|
|
1992
2118
|
await buildDiscussMilestonePrompt(
|
|
@@ -2028,7 +2154,7 @@ export async function showSmartEntry(
|
|
|
2028
2154
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2029
2155
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2030
2156
|
|
|
2031
|
-
|
|
2157
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2032
2158
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2033
2159
|
`New milestone ${nextId}.`,
|
|
2034
2160
|
basePath
|
|
@@ -2080,12 +2206,12 @@ export async function showSmartEntry(
|
|
|
2080
2206
|
const seed = draftContent
|
|
2081
2207
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
2082
2208
|
: basePrompt;
|
|
2083
|
-
|
|
2209
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2084
2210
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
2085
2211
|
} else if (choice === "discuss_fresh") {
|
|
2086
2212
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
2087
2213
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
2088
|
-
|
|
2214
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2089
2215
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
2090
2216
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
2091
2217
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -2095,7 +2221,7 @@ export async function showSmartEntry(
|
|
|
2095
2221
|
const milestoneIds = findMilestoneIds(basePath);
|
|
2096
2222
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2097
2223
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2098
|
-
|
|
2224
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2099
2225
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2100
2226
|
`New milestone ${nextId}.`,
|
|
2101
2227
|
basePath
|
|
@@ -2160,7 +2286,7 @@ export async function showSmartEntry(
|
|
|
2160
2286
|
});
|
|
2161
2287
|
|
|
2162
2288
|
if (choice === "plan") {
|
|
2163
|
-
|
|
2289
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2164
2290
|
await dispatchWorkflow(
|
|
2165
2291
|
pi,
|
|
2166
2292
|
await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath),
|
|
@@ -2180,7 +2306,7 @@ export async function showSmartEntry(
|
|
|
2180
2306
|
const milestoneIds = findMilestoneIds(basePath);
|
|
2181
2307
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2182
2308
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2183
|
-
|
|
2309
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2184
2310
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2185
2311
|
`New milestone ${nextId}.`,
|
|
2186
2312
|
basePath
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./session-forensics.js";
|
|
17
17
|
import { deriveState } from "./state.js";
|
|
18
18
|
import type { GSDState } from "./types.js";
|
|
19
|
+
import { getRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
19
20
|
|
|
20
21
|
export type InterruptedSessionClassification =
|
|
21
22
|
| "none"
|
|
@@ -82,22 +83,28 @@ function isStalePseudoMilestonePause(meta: PausedSessionMetadata): boolean {
|
|
|
82
83
|
&& LEGACY_DEEP_SETUP_UNITS.has(`${meta.unitType}:${meta.unitId}`);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* runtime_kv key (global scope) that stores the most recent paused-session
|
|
88
|
+
* metadata. Phase C pt 2: replaces runtime/paused-session.json. The key is
|
|
89
|
+
* project-wide (not worker-scoped) because the paused state represents the
|
|
90
|
+
* last time auto-mode paused on this project — there is at most one paused
|
|
91
|
+
* session per project at a time.
|
|
92
|
+
*/
|
|
93
|
+
export const PAUSED_SESSION_KV_KEY = "paused_session";
|
|
94
|
+
|
|
85
95
|
export function readPausedSessionMetadata(
|
|
86
96
|
basePath: string,
|
|
87
97
|
): PausedSessionMetadata | null {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return meta;
|
|
98
|
-
} catch {
|
|
98
|
+
// basePath is unused now (the DB is workspace-scoped via the connection
|
|
99
|
+
// openDatabase opened on it) but kept in the signature for callers.
|
|
100
|
+
void basePath;
|
|
101
|
+
const meta = getRuntimeKv<PausedSessionMetadata>("global", "", PAUSED_SESSION_KV_KEY);
|
|
102
|
+
if (!meta) return null;
|
|
103
|
+
if (isStalePseudoMilestonePause(meta)) {
|
|
104
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
99
105
|
return null;
|
|
100
106
|
}
|
|
107
|
+
return meta;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
export function isBootstrapCrashLock(lock: LockData | null): boolean {
|