@opengsd/gsd-pi 1.8.1-dev.897b3c90 → 1.9.0-dev.5c1a3ff2
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 +10 -8
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -17
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
- package/dist/resources/extensions/gsd/constants.js +12 -13
- package/dist/resources/extensions/gsd/context-masker.js +97 -21
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
- package/dist/resources/extensions/gsd/git-service.js +19 -7
- package/dist/resources/extensions/gsd/guided-flow.js +9 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +2 -12
- package/dist/resources/extensions/gsd/md-importer.js +5 -6
- package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +11 -0
- package/dist/resources/extensions/gsd/paths.js +51 -10
- package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
- package/dist/resources/extensions/gsd/preferences-types.js +5 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
- package/dist/resources/extensions/gsd/preferences.js +17 -0
- package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
- package/dist/resources/extensions/gsd/skill-activation.js +17 -3
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
- package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
- package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
- package/dist/resources/extensions/gsd/workspace.js +2 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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 +5 -5
- 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/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +6 -6
- 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/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +1 -0
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/package.json +1 -1
- package/packages/mcp-server/README.md +7 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +5 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +59 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +53 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +6 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +104 -15
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
- package/src/resources/extensions/gsd/constants.ts +14 -13
- package/src/resources/extensions/gsd/context-masker.ts +98 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
- package/src/resources/extensions/gsd/git-service.ts +19 -6
- package/src/resources/extensions/gsd/guided-flow.ts +9 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +2 -14
- package/src/resources/extensions/gsd/md-importer.ts +5 -5
- package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
- package/src/resources/extensions/gsd/model-router.ts +12 -0
- package/src/resources/extensions/gsd/paths.ts +58 -10
- package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
- package/src/resources/extensions/gsd/preferences.ts +25 -0
- package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
- package/src/resources/extensions/gsd/skill-activation.ts +17 -3
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
- package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
- package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
- package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
- package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
- package/src/resources/extensions/gsd/workspace.ts +2 -1
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -28,14 +28,16 @@ See [CHANGELOG.md](./CHANGELOG.md) for release-by-release fixes and [Legacy Rele
|
|
|
28
28
|
## Latest Release Highlights
|
|
29
29
|
|
|
30
30
|
<!-- release-highlights:start -->
|
|
31
|
-
Latest release: **v1.
|
|
32
|
-
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
31
|
+
Latest release: **v1.9.0**
|
|
32
|
+
|
|
33
|
+
- **gsd:** Add task rework briefs.
|
|
34
|
+
- **web:** Delete unused chart.tsx and recharts.
|
|
35
|
+
- **deps:** Move ajv to devDependencies.
|
|
36
|
+
- **daemon:** Dedupe @anthropic-ai/sdk to ^0.91.1.
|
|
37
|
+
- **deps:** Override hono to >=4.12.25.
|
|
38
|
+
- **gsd:** Batch plan-v2 task queries and skip unchanged graph writes.
|
|
39
|
+
- **gsd:** Open the worker DB once per parallel-monitor tick.
|
|
40
|
+
- **gsd:** Batch parallel-eligibility task queries.
|
|
39
41
|
|
|
40
42
|
<!-- release-highlights:end -->
|
|
41
43
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1252616a01ee43bc
|
|
@@ -367,7 +367,7 @@ When verification finds gaps, include a **Gaps** section with what's missing, im
|
|
|
367
367
|
### Phase 6: Summarize
|
|
368
368
|
|
|
369
369
|
**Purpose:** Record what happened for downstream tasks.
|
|
370
|
-
**Produces:** `T##-SUMMARY.md
|
|
370
|
+
**Produces:** `S##-T##-SUMMARY.md` in flat-phase projects, and when slice completes, `S##-SUMMARY.md`. Legacy flat `T##-SUMMARY.md` task summaries are still readable.
|
|
371
371
|
|
|
372
372
|
**Task summary format:**
|
|
373
373
|
```markdown
|
|
@@ -494,7 +494,7 @@ It is NOT the source of truth. It's a convenience dashboard.
|
|
|
494
494
|
**Sources of truth:**
|
|
495
495
|
- `ROADMAP.md` → which slices exist and which are done
|
|
496
496
|
- `<NN>-<MM>-PLAN.md` → which tasks exist within a slice and what each task must do
|
|
497
|
-
- `T##-SUMMARY.md` → what happened during a task
|
|
497
|
+
- `S##-T##-SUMMARY.md` → what happened during a task in flat-phase projects (legacy `T##-SUMMARY.md` is still readable)
|
|
498
498
|
- `S##-SUMMARY.md` and `M###-SUMMARY.md` → compressed slice and milestone outcomes
|
|
499
499
|
|
|
500
500
|
**Update `STATE.md`** after every significant action:
|
|
@@ -9,7 +9,7 @@ import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
|
|
|
9
9
|
import { isValidationTerminal } from "./state.js";
|
|
10
10
|
import { getErrorMessage } from "./error-utils.js";
|
|
11
11
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
12
|
-
import { resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, relSliceFile, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
12
|
+
import { resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relSliceFile, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
14
|
import { execFileSync } from "node:child_process";
|
|
15
15
|
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
@@ -185,10 +185,9 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
185
185
|
const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
|
|
186
186
|
if (batchIds.length === 0)
|
|
187
187
|
return false;
|
|
188
|
-
const tDir = resolveTasksDir(base, mid, sid) ?? slicePath;
|
|
189
|
-
const existingSummaries = new Set(resolveTaskFiles(tDir, "SUMMARY").map((f) => f.replace(/-SUMMARY\.md$/i, "").toUpperCase()));
|
|
190
188
|
for (const tid of batchIds) {
|
|
191
|
-
|
|
189
|
+
const summaryPath = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
|
|
190
|
+
if (!summaryPath || !existsSync(summaryPath))
|
|
192
191
|
return false;
|
|
193
192
|
}
|
|
194
193
|
return true;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from
|
|
6
6
|
// auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification
|
|
7
7
|
// function was removed entirely — callers now query WorkflowEngine directly.
|
|
8
|
-
import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, normalizeRealPath, } from "./paths.js";
|
|
8
|
+
import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildFlatTaskFileName, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, normalizeRealPath, } from "./paths.js";
|
|
9
9
|
import { milestoneIdToPhaseNum } from "./layout-policy.js";
|
|
10
10
|
import { parseUnitId } from "./unit-id.js";
|
|
11
11
|
import { basename, dirname, join, relative } from "node:path";
|
|
@@ -79,12 +79,15 @@ function resolveProjectMilestonePath(base, mid) {
|
|
|
79
79
|
const dir = resolveDir(milestonesDir, mid);
|
|
80
80
|
if (!dir)
|
|
81
81
|
return null;
|
|
82
|
-
// git-service.ts
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
82
|
+
// Historically git-service.ts wrote integration-branch metadata into
|
|
83
|
+
// milestones/<MID>/<MID>-META.json, which could create that dir in flat-phase
|
|
84
|
+
// projects and poison layout detection. As of ADR-045 that META lives flat at
|
|
85
|
+
// .gsd/<MID>-META.json and no longer poisons detection. A dir that holds ONLY
|
|
86
|
+
// *-META.json files (a pre-migration on-disk tree) must still not be treated
|
|
87
|
+
// as a real legacy milestone dir — otherwise this early-return resolves
|
|
88
|
+
// CONTEXT/ROADMAP/SUMMARY to the legacy path (milestones/<MID>/<MID>-<SUFFIX>.md)
|
|
89
|
+
// before the flat-phase fallback can run, trapping the unit in a
|
|
90
|
+
// finalize-retry loop (#852 follow-up).
|
|
88
91
|
//
|
|
89
92
|
// We use dirIsMetaOnlyLegacyMilestone rather than !dirIsContentBearingLegacyMilestone
|
|
90
93
|
// so that an EMPTY dir (a new milestone before any content is written) is NOT
|
|
@@ -203,7 +206,10 @@ export function resolveExpectedArtifactPath(unitType, unitId, base) {
|
|
|
203
206
|
const summaryDir = isLegacySlice
|
|
204
207
|
? (resolveTasksDir(base, mid, sid) ?? slicePath)
|
|
205
208
|
: slicePath;
|
|
206
|
-
|
|
209
|
+
const fileName = isLegacySlice
|
|
210
|
+
? buildTaskFileName(tid, "SUMMARY")
|
|
211
|
+
: buildFlatTaskFileName(sid, tid, "SUMMARY");
|
|
212
|
+
return join(summaryDir, fileName);
|
|
207
213
|
}
|
|
208
214
|
case "complete-slice": {
|
|
209
215
|
return resolveSliceArtifactPath(base, mid, sid, "SUMMARY");
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
11
11
|
import { extractVerdict } from "./verdict-parser.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
13
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, taskIdFromTaskFileName, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
14
14
|
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
15
15
|
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
16
16
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
17
17
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
18
|
-
import { join, relative, sep } from "node:path";
|
|
18
|
+
import { join, basename, relative, sep } from "node:path";
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
20
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
21
21
|
import { getBlockingReworkFindingsForTask, getGateResults, getPendingGatesForTurn } from "./gsd-db.js";
|
|
@@ -62,13 +62,29 @@ function resolvePromptBudgets() {
|
|
|
62
62
|
const prefs = loadEffectiveGSDPreferences();
|
|
63
63
|
const sessionWindow = prefs?.preferences.context_window_override;
|
|
64
64
|
const windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences, sessionWindow);
|
|
65
|
-
return computeBudgets(windowTokens);
|
|
65
|
+
return computeBudgets(windowTokens, resolveExecutionProvider(prefs?.preferences));
|
|
66
66
|
}
|
|
67
67
|
catch (e) {
|
|
68
68
|
logWarning("prompt", `resolvePromptBudgets failed: ${e.message}`);
|
|
69
69
|
return computeBudgets(200_000);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Best-effort provider for the chars/token ratio: the executor's configured
|
|
74
|
+
* model profile is the only provider source reachable here (the runtime
|
|
75
|
+
* `sessionProvider` that reaches `formatExecutorConstraints` isn't threaded to
|
|
76
|
+
* `capPreamble`/summary callers). Undefined when no explicit provider is set —
|
|
77
|
+
* `computeBudgets` then uses the 4.0 default, i.e. today's behavior.
|
|
78
|
+
*/
|
|
79
|
+
function resolveExecutionProvider(prefs) {
|
|
80
|
+
const exec = prefs?.models?.execution;
|
|
81
|
+
if (exec && typeof exec === "object" && "provider" in exec) {
|
|
82
|
+
const provider = exec.provider;
|
|
83
|
+
if (typeof provider === "string" && provider)
|
|
84
|
+
return provider;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
72
88
|
/**
|
|
73
89
|
* Character budget for dependency/prior slice summaries injected into dispatch
|
|
74
90
|
* prompts. Scales with the executor's configured context window (issue #4435).
|
|
@@ -200,7 +216,7 @@ function formatCloseoutReviewInstructions(validationContent, validationRel, curr
|
|
|
200
216
|
`No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
|
|
201
217
|
].join("\n");
|
|
202
218
|
}
|
|
203
|
-
function capPreamble(preamble) {
|
|
219
|
+
export function capPreamble(preamble) {
|
|
204
220
|
// Cap inlined context at min(static ceiling, scaled inline budget).
|
|
205
221
|
// The ceiling bounds repeated auto prompt payloads; the scaled
|
|
206
222
|
// budget tightens the cap for small-window users whose true safe limit is
|
|
@@ -210,6 +226,35 @@ function capPreamble(preamble) {
|
|
|
210
226
|
return preamble;
|
|
211
227
|
return truncateAtSectionBoundary(preamble, budget).content;
|
|
212
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Cap the execute-task inline blocks against one shared budget in priority
|
|
231
|
+
* order. Unlike `capPreamble` (single concatenated preamble), execute-task
|
|
232
|
+
* feeds these blocks into separate, non-contiguous template placeholders, so
|
|
233
|
+
* they can't be joined and re-split — the budget is spent block-by-block.
|
|
234
|
+
*
|
|
235
|
+
* The task plan is the authoritative execution contract: it is never truncated
|
|
236
|
+
* here (it consumes budget first, whole). Remaining budget flows to the slice
|
|
237
|
+
* excerpt, then templates, each truncated at section boundaries so the lowest-
|
|
238
|
+
* priority context drops whole trailing sections first. When the task plan
|
|
239
|
+
* alone meets or exceeds the budget, the trailing blocks are dropped rather
|
|
240
|
+
* than the task plan being cut.
|
|
241
|
+
*/
|
|
242
|
+
function capExecuteTaskInlineBlocks(taskPlan, slicePlan, templates, budgetChars) {
|
|
243
|
+
let remaining = budgetChars - taskPlan.length;
|
|
244
|
+
if (remaining <= 0) {
|
|
245
|
+
return { taskPlan, slicePlan: "", templates: "" };
|
|
246
|
+
}
|
|
247
|
+
const cappedSlice = slicePlan.length <= remaining
|
|
248
|
+
? slicePlan
|
|
249
|
+
: truncateAtSectionBoundary(slicePlan, remaining).content;
|
|
250
|
+
remaining -= cappedSlice.length;
|
|
251
|
+
const cappedTemplates = remaining <= 0
|
|
252
|
+
? ""
|
|
253
|
+
: templates.length <= remaining
|
|
254
|
+
? templates
|
|
255
|
+
: truncateAtSectionBoundary(templates, remaining).content;
|
|
256
|
+
return { taskPlan, slicePlan: cappedSlice, templates: cappedTemplates };
|
|
257
|
+
}
|
|
213
258
|
function trackPromptContext(entries, key, mode, body, reason) {
|
|
214
259
|
entries.push({
|
|
215
260
|
key,
|
|
@@ -253,7 +298,7 @@ function renderContextModeBlockForPrompt(unitType, base, renderMode = "standalon
|
|
|
253
298
|
return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
|
|
254
299
|
}
|
|
255
300
|
function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
|
|
256
|
-
const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode });
|
|
301
|
+
const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode, basePath: base });
|
|
257
302
|
const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
|
|
258
303
|
const guidance = [toolSurface, contextMode].filter(Boolean).join("\n\n");
|
|
259
304
|
if (!guidance)
|
|
@@ -310,7 +355,7 @@ function formatExecutorConstraints(sessionContextWindow, modelRegistry, sessionP
|
|
|
310
355
|
// so DEFAULT_CONTEXT_WINDOW stays the single source of truth.
|
|
311
356
|
windowTokens = resolveExecutorContextWindow(undefined, undefined, sessionContextWindow, sessionProvider);
|
|
312
357
|
}
|
|
313
|
-
const budgets = computeBudgets(windowTokens);
|
|
358
|
+
const budgets = computeBudgets(windowTokens, sessionProvider);
|
|
314
359
|
const { min, max } = budgets.taskCountRange;
|
|
315
360
|
const execWindowK = Math.round(windowTokens / 1000);
|
|
316
361
|
const perTaskBudgetK = Math.round(budgets.inlineContextBudgetChars / 1000);
|
|
@@ -1245,6 +1290,13 @@ function resolveTaskSummariesLocation(base, mid, sid) {
|
|
|
1245
1290
|
return { dir: tDir, relPrefix: `${sRel}/tasks` };
|
|
1246
1291
|
return { dir: slicePath, relPrefix: sRel };
|
|
1247
1292
|
}
|
|
1293
|
+
function summaryFileBelongsToSlice(fileName, base, mid, sid) {
|
|
1294
|
+
const tid = taskIdFromTaskFileName(fileName, "SUMMARY");
|
|
1295
|
+
if (!tid)
|
|
1296
|
+
return false;
|
|
1297
|
+
const resolved = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
|
|
1298
|
+
return resolved !== null && basename(resolved) === fileName;
|
|
1299
|
+
}
|
|
1248
1300
|
export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
1249
1301
|
const loc = resolveTaskSummariesLocation(base, mid, sid);
|
|
1250
1302
|
if (!loc)
|
|
@@ -1253,7 +1305,12 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
|
1253
1305
|
const currentNum = parseInt(currentTid.replace(/^T/, ""), 10);
|
|
1254
1306
|
return summaryFiles
|
|
1255
1307
|
.filter(f => {
|
|
1256
|
-
|
|
1308
|
+
if (!summaryFileBelongsToSlice(f, base, mid, sid))
|
|
1309
|
+
return false;
|
|
1310
|
+
const tid = taskIdFromTaskFileName(f, "SUMMARY");
|
|
1311
|
+
if (!tid)
|
|
1312
|
+
return false;
|
|
1313
|
+
const num = parseInt(tid.replace(/^T/, ""), 10);
|
|
1257
1314
|
return num < currentNum;
|
|
1258
1315
|
})
|
|
1259
1316
|
.map(f => `${loc.relPrefix}/${f}`);
|
|
@@ -1280,8 +1337,11 @@ export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, depend
|
|
|
1280
1337
|
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
1281
1338
|
return summaryFiles
|
|
1282
1339
|
.filter((f) => {
|
|
1283
|
-
|
|
1284
|
-
|
|
1340
|
+
if (!summaryFileBelongsToSlice(f, base, mid, sid))
|
|
1341
|
+
return false;
|
|
1342
|
+
const tid = taskIdFromTaskFileName(f, "SUMMARY");
|
|
1343
|
+
if (!tid)
|
|
1344
|
+
return false;
|
|
1285
1345
|
return depSet.has(tid);
|
|
1286
1346
|
})
|
|
1287
1347
|
.map((f) => `${loc.relPrefix}/${f}`);
|
|
@@ -2307,7 +2367,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
2307
2367
|
// Compute verification budget for the executor's context window (issue #707)
|
|
2308
2368
|
const prefs = loadEffectiveGSDPreferences();
|
|
2309
2369
|
const contextWindow = resolveExecutorContextWindow(opts.modelRegistry, prefs?.preferences, opts.sessionContextWindow, opts.sessionProvider);
|
|
2310
|
-
const budgets = computeBudgets(contextWindow);
|
|
2370
|
+
const budgets = computeBudgets(contextWindow, opts.sessionProvider);
|
|
2311
2371
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
2312
2372
|
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
2313
2373
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
@@ -2409,10 +2469,19 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
2409
2469
|
}
|
|
2410
2470
|
},
|
|
2411
2471
|
});
|
|
2412
|
-
const
|
|
2413
|
-
const
|
|
2472
|
+
const rawTaskPlanInline = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "task-plan");
|
|
2473
|
+
const rawSlicePlanExcerpt = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "slice-plan");
|
|
2414
2474
|
const contractedCarryForward = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "prior-task-summaries");
|
|
2415
|
-
const
|
|
2475
|
+
const rawContractedTemplates = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "templates");
|
|
2476
|
+
// Cap the static inline blocks against one shared budget so a small-window
|
|
2477
|
+
// executor can't overflow (the sibling builders cap via `capPreamble`;
|
|
2478
|
+
// execute-task's blocks feed separate template slots, so they share a budget
|
|
2479
|
+
// block-by-block). Carry-forward already has its own truncation above.
|
|
2480
|
+
const inlineBudget = Math.min(MAX_PREAMBLE_CHARS, budgets.inlineContextBudgetChars);
|
|
2481
|
+
const { taskPlan: taskPlanInline, slicePlan: slicePlanExcerpt, templates: contractedTemplates } = capExecuteTaskInlineBlocks(rawTaskPlanInline, rawSlicePlanExcerpt, rawContractedTemplates, inlineBudget);
|
|
2482
|
+
const inlineDropped = (rawTaskPlanInline.length + rawSlicePlanExcerpt.length + rawContractedTemplates.length)
|
|
2483
|
+
- (taskPlanInline.length + slicePlanExcerpt.length + contractedTemplates.length);
|
|
2484
|
+
trackPromptContext(contextTelemetry, "inline-cap", inlineDropped > 0 ? "excerpt" : "skipped", null, inlineDropped > 0 ? `dropped ${inlineDropped} chars (budget ${inlineBudget})` : "within budget");
|
|
2416
2485
|
const onDemandResult = renderExecuteTaskOnDemandContext(base, mid, sid, contractedContext.onDemand);
|
|
2417
2486
|
const onDemandContext = onDemandResult.text;
|
|
2418
2487
|
trackPromptContext(contextTelemetry, "slice-research", onDemandContext ? "on-demand" : "skipped", onDemandContext, onDemandContext ? undefined : onDemandResult.skipReason ?? undefined);
|
|
@@ -2497,12 +2566,14 @@ export async function buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
2497
2566
|
trackPromptContext(contextTelemetry, "prior-task-summaries", "skipped", null, "missing tasks dir");
|
|
2498
2567
|
return null;
|
|
2499
2568
|
}
|
|
2500
|
-
const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY")
|
|
2569
|
+
const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY")
|
|
2570
|
+
.filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
|
|
2571
|
+
.sort();
|
|
2501
2572
|
const blocks = [];
|
|
2502
2573
|
for (const file of summaryFiles) {
|
|
2503
2574
|
const absPath = join(loc.dir, file);
|
|
2504
2575
|
const relPath = `${loc.relPrefix}/${file}`;
|
|
2505
|
-
const taskId = file.replace(/-SUMMARY\.md$/i, "");
|
|
2576
|
+
const taskId = taskIdFromTaskFileName(file, "SUMMARY") ?? file.replace(/-SUMMARY\.md$/i, "");
|
|
2506
2577
|
blocks.push(await buildTaskSummaryExcerpt(absPath, relPath, taskId));
|
|
2507
2578
|
}
|
|
2508
2579
|
const body = blocks.length > 0 ? blocks.join("\n\n---\n\n") : null;
|
|
@@ -3072,7 +3143,9 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
3072
3143
|
let blockerTaskId = "";
|
|
3073
3144
|
const summaryLoc = resolveTaskSummariesLocation(base, mid, sid);
|
|
3074
3145
|
if (summaryLoc) {
|
|
3075
|
-
const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY")
|
|
3146
|
+
const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY")
|
|
3147
|
+
.filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
|
|
3148
|
+
.sort();
|
|
3076
3149
|
for (const file of summaryFiles) {
|
|
3077
3150
|
const absPath = join(summaryLoc.dir, file);
|
|
3078
3151
|
const content = await loadFile(absPath);
|
|
@@ -3081,7 +3154,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
3081
3154
|
const summary = parseSummary(content);
|
|
3082
3155
|
const relPath = `${summaryLoc.relPrefix}/${file}`;
|
|
3083
3156
|
if (summary.frontmatter.blocker_discovered) {
|
|
3084
|
-
blockerTaskId = summary.frontmatter.id || file.replace(/-SUMMARY\.md$/i, "");
|
|
3157
|
+
blockerTaskId = summary.frontmatter.id || taskIdFromTaskFileName(file, "SUMMARY") || file.replace(/-SUMMARY\.md$/i, "");
|
|
3085
3158
|
inlined.push(await buildTaskSummaryExcerpt(absPath, relPath, blockerTaskId, { blocker: true }));
|
|
3086
3159
|
}
|
|
3087
3160
|
}
|
|
@@ -17,7 +17,7 @@ import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
|
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning } from "./workflow-logger.js";
|
|
19
19
|
import { isClosedStatus, isInactiveStatus } from "./status-guards.js";
|
|
20
|
-
import { resolveSlicePath,
|
|
20
|
+
import { resolveSlicePath, resolveTaskFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
22
22
|
import { dirname } from "node:path";
|
|
23
23
|
import { resolveExpectedArtifactPath, diagnoseExpectedArtifact, } from "./auto-artifact-paths.js";
|
|
@@ -227,10 +227,15 @@ export function writeReactiveExecuteBlocker(unitId, base, reason) {
|
|
|
227
227
|
const slicePath = resolveSlicePath(base, mid, sid);
|
|
228
228
|
if (!slicePath)
|
|
229
229
|
return null;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
230
|
+
// Resolve each batch task's SUMMARY with slice-qualified paths so flat-phase
|
|
231
|
+
// slices that reuse a task id (e.g. two T03s in one phase) don't collide: a
|
|
232
|
+
// sibling slice's summary must never make this slice's task look complete.
|
|
233
|
+
const hasSummary = (tid) => {
|
|
234
|
+
const summaryPath = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
|
|
235
|
+
return summaryPath !== null && existsSync(summaryPath);
|
|
236
|
+
};
|
|
237
|
+
const summaryPresent = batchIds.filter((tid) => hasSummary(tid));
|
|
238
|
+
const summaryMissing = batchIds.filter((tid) => !hasSummary(tid));
|
|
234
239
|
const completedTaskIds = [];
|
|
235
240
|
const skippedTaskIds = [];
|
|
236
241
|
const unchangedTaskIds = [];
|
|
@@ -8,6 +8,7 @@ import { execFileSync } from "node:child_process";
|
|
|
8
8
|
import { existsSync, realpathSync, unlinkSync } from "node:fs";
|
|
9
9
|
import { isAbsolute, join, relative } from "node:path";
|
|
10
10
|
import { gsdRoot } from "./paths.js";
|
|
11
|
+
import { milestoneMetaPath } from "./git-service.js";
|
|
11
12
|
import { logWarning } from "./workflow-logger.js";
|
|
12
13
|
function isSamePath(a, b) {
|
|
13
14
|
try {
|
|
@@ -73,6 +74,9 @@ export function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
|
73
74
|
// worth removing on teardown.
|
|
74
75
|
const transientFiles = [
|
|
75
76
|
join(gsdDir, "STATE.md"),
|
|
77
|
+
// Integration-branch META now lives flat at .gsd/<MID>-META.json (ADR-045).
|
|
78
|
+
milestoneMetaPath(basePath, milestoneId),
|
|
79
|
+
// Legacy location — still cleaned for pre-migration trees.
|
|
76
80
|
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
77
81
|
];
|
|
78
82
|
for (const file of transientFiles) {
|
|
@@ -45,7 +45,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
45
45
|
throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
46
46
|
}
|
|
47
47
|
// Mirror cleanup steps from mergeMilestoneToMain abort path:
|
|
48
|
-
// 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
|
|
48
|
+
// 1. Remove transient state files (STATE.md, auto.lock, .gsd/{MID}-META.json).
|
|
49
49
|
// Non-fatal — must not block teardown.
|
|
50
50
|
try {
|
|
51
51
|
clearProjectRootStateFiles(originalBasePath, milestoneId);
|
|
@@ -46,6 +46,8 @@ function registerAlias(pi, toolDef, aliasName, canonicalName) {
|
|
|
46
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- toolDef shape matches ToolDefinition but varies by schema
|
|
47
47
|
function registerWorkflowTool(pi, toolDef) {
|
|
48
48
|
pi.registerTool(toolDef);
|
|
49
|
+
if (process.env.GSD_ADVERTISE_TOOL_ALIASES !== "1")
|
|
50
|
+
return; // canonical-only model surface (see plan 035)
|
|
49
51
|
for (const alias of aliasesForWorkflowTool(toolDef.name)) {
|
|
50
52
|
registerAlias(pi, toolDef, alias, toolDef.name);
|
|
51
53
|
}
|
|
@@ -22,13 +22,14 @@ import { saveActivityLog } from "../activity-log.js";
|
|
|
22
22
|
import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
|
|
23
23
|
import { parseUnitId } from "../unit-id.js";
|
|
24
24
|
import { classifyCommand } from "../safety/destructive-guard.js";
|
|
25
|
-
import { confirmDestructiveCommand, consumeDestructiveConfirmation, isDestructiveConfirmGateId, requestDestructiveConfirmation, } from "../safety/destructive-confirmation.js";
|
|
25
|
+
import { confirmDestructiveCommand, consumeDestructiveConfirmation, isDestructiveConfirmGateId, peekPendingDestructiveCommand, requestDestructiveConfirmation, resetDestructiveConfirmation, } from "../safety/destructive-confirmation.js";
|
|
26
26
|
import { logWarning as safetyLogWarning, setStderrLoggingEnabled } from "../workflow-logger.js";
|
|
27
27
|
import { isUnitCloseoutTool, runInteractiveUnitCloseout } from "../unit-closeout.js";
|
|
28
28
|
import { installNotifyInterceptor } from "./notify-interceptor.js";
|
|
29
29
|
import { initNotificationStore } from "../notification-store.js";
|
|
30
30
|
import { initNotificationWidget } from "../notification-widget.js";
|
|
31
31
|
import { notifyPreferenceDiagnostics } from "../preferences-diagnostics.js";
|
|
32
|
+
import { resolveEffectivePlanningToolsPolicy } from "../planning-subagent-policy.js";
|
|
32
33
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
33
34
|
import { extractSubagentAgentClasses } from "./subagent-input.js";
|
|
34
35
|
import { approvalGateIdForUnit, evaluateAskUserQuestionsRound, formatUnansweredConsentQuestionMessage, isExplicitApprovalResponse, messageHasPendingAskUserQuestionsTool, shouldPauseForQuestion, } from "../consent-question.js";
|
|
@@ -136,6 +137,7 @@ function suppressWelcomeHeader(ctx) {
|
|
|
136
137
|
* are bounded — cleared on activation, session boundaries, and verification.
|
|
137
138
|
*/
|
|
138
139
|
const deferredApprovalGates = new Map();
|
|
140
|
+
const deferredDestructiveConfirmationPauses = new Set();
|
|
139
141
|
export const MINIMAL_GSD_TOOL_NAMES = [
|
|
140
142
|
"gsd_exec",
|
|
141
143
|
"gsd_exec_search",
|
|
@@ -456,6 +458,21 @@ function clearDeferredApprovalGate(basePath) {
|
|
|
456
458
|
deferredApprovalGates.delete(basePath);
|
|
457
459
|
}
|
|
458
460
|
}
|
|
461
|
+
function deferDestructiveConfirmationPause(basePath) {
|
|
462
|
+
deferredDestructiveConfirmationPauses.add(basePath);
|
|
463
|
+
}
|
|
464
|
+
function clearDeferredDestructiveConfirmationPause(basePath) {
|
|
465
|
+
if (!basePath) {
|
|
466
|
+
deferredDestructiveConfirmationPauses.clear();
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
deferredDestructiveConfirmationPauses.delete(basePath);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function isDestructiveConfirmationBlocking(basePath) {
|
|
473
|
+
return deferredDestructiveConfirmationPauses.has(basePath)
|
|
474
|
+
&& Boolean(peekPendingDestructiveCommand(basePath));
|
|
475
|
+
}
|
|
459
476
|
function deferApprovalGate(gateId, basePath) {
|
|
460
477
|
// Verified-on-disk wins (same adapter policy as activation/re-arm): if the
|
|
461
478
|
// workflow MCP child already verified this gate, deferring would block
|
|
@@ -798,6 +815,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
798
815
|
await applyToolCallLoopGuardConfig(basePath);
|
|
799
816
|
approvalQuestionAbortInFlight = false;
|
|
800
817
|
clearDeferredApprovalGate();
|
|
818
|
+
clearDeferredDestructiveConfirmationPause();
|
|
801
819
|
await resetAskUserQuestionsTurnCache();
|
|
802
820
|
await syncServiceTierStatus(ctx);
|
|
803
821
|
await applyDisabledModelProviderPolicy(ctx);
|
|
@@ -883,6 +901,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
883
901
|
resetToolCallLoopGuard();
|
|
884
902
|
await applyToolCallLoopGuardConfig(basePath);
|
|
885
903
|
clearDeferredApprovalGate();
|
|
904
|
+
clearDeferredDestructiveConfirmationPause();
|
|
886
905
|
await resetAskUserQuestionsTurnCache();
|
|
887
906
|
clearDiscussionFlowState(basePath);
|
|
888
907
|
// /clear or /new destroys the conversation holding a discuss interview, so
|
|
@@ -933,6 +952,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
933
952
|
}
|
|
934
953
|
}
|
|
935
954
|
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
955
|
+
clearDeferredDestructiveConfirmationPause(beforeAgentBasePath);
|
|
936
956
|
// session_start can fire before the active provider has settled. By
|
|
937
957
|
// before_agent_start, Claude Code CLI sessions should get the same
|
|
938
958
|
// project MCP config that /gsd mcp init would write.
|
|
@@ -1010,7 +1030,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1010
1030
|
}
|
|
1011
1031
|
finally {
|
|
1012
1032
|
activateDeferredApprovalGate(agentEndBasePath);
|
|
1033
|
+
const destructiveConfirmationBlocking = isDestructiveConfirmationBlocking(agentEndBasePath);
|
|
1034
|
+
clearDeferredDestructiveConfirmationPause(agentEndBasePath);
|
|
1013
1035
|
await maybePauseAutoForApprovalGate(ctx, pi, isApprovalGateBlocking(agentEndBasePath), "Depth confirmation is waiting for your answer — pausing auto-mode.");
|
|
1036
|
+
await maybePauseAutoForApprovalGate(ctx, pi, destructiveConfirmationBlocking, "Destructive-command confirmation is waiting for your answer — pausing auto-mode.");
|
|
1014
1037
|
}
|
|
1015
1038
|
});
|
|
1016
1039
|
pi.on("message_end", async (event) => {
|
|
@@ -1276,6 +1299,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1276
1299
|
const activeUnitType = dash.currentUnit?.type ?? guidedUnit?.unitType;
|
|
1277
1300
|
if (activeUnitType) {
|
|
1278
1301
|
const manifest = resolveManifest(activeUnitType);
|
|
1302
|
+
const planningBasePath = dash.basePath || guidedUnit?.basePath || discussionBasePath;
|
|
1279
1303
|
let planningInput = "";
|
|
1280
1304
|
let agentClasses;
|
|
1281
1305
|
if (isToolCallEventType("write", event)) {
|
|
@@ -1291,7 +1315,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1291
1315
|
// Subagent inputs use { agent }, { tasks: [{ agent }] }, or { chain: [{ agent }] }.
|
|
1292
1316
|
agentClasses = extractSubagentAgentClasses(event.input);
|
|
1293
1317
|
}
|
|
1294
|
-
const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput,
|
|
1318
|
+
const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput, planningBasePath, activeUnitType, resolveEffectivePlanningToolsPolicy(activeUnitType, manifest?.tools, planningBasePath), agentClasses, event.input, dash.currentUnit?.id);
|
|
1295
1319
|
if (planningGuard.block)
|
|
1296
1320
|
return planningGuard;
|
|
1297
1321
|
}
|
|
@@ -1373,6 +1397,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1373
1397
|
// Record the command as pending so an affirmative answer to a
|
|
1374
1398
|
// destructive_confirm gate (handled in tool_result) can confirm it.
|
|
1375
1399
|
requestDestructiveConfirmation(command, guardBasePath);
|
|
1400
|
+
deferDestructiveConfirmationPause(guardBasePath);
|
|
1376
1401
|
const reason = [
|
|
1377
1402
|
"HARD BLOCK: destructive Bash command requires explicit human confirmation.",
|
|
1378
1403
|
`Detected: ${classification.labels.join(", ")}`,
|
|
@@ -1383,9 +1408,6 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1383
1408
|
safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
|
|
1384
1409
|
command: String(command).slice(0, 200),
|
|
1385
1410
|
});
|
|
1386
|
-
if (ctx) {
|
|
1387
|
-
await maybePauseAutoForApprovalGate(ctx, pi, isAutoActive(), "Destructive-command confirmation is waiting for your answer — pausing auto-mode.");
|
|
1388
|
-
}
|
|
1389
1411
|
return { block: true, reason };
|
|
1390
1412
|
}
|
|
1391
1413
|
}
|
|
@@ -1541,6 +1563,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1541
1563
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
1542
1564
|
confirmDestructiveCommand(basePath);
|
|
1543
1565
|
}
|
|
1566
|
+
else {
|
|
1567
|
+
resetDestructiveConfirmation(basePath);
|
|
1568
|
+
}
|
|
1569
|
+
clearDeferredDestructiveConfirmationPause(basePath);
|
|
1544
1570
|
break;
|
|
1545
1571
|
}
|
|
1546
1572
|
}
|
|
@@ -18,6 +18,13 @@ import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile,
|
|
|
18
18
|
import { toPosixPath } from "../../shared/mod.js";
|
|
19
19
|
import { autoEnableCmuxPreferences } from "../commands-cmux.js";
|
|
20
20
|
import { gsdHome } from "../gsd-home.js";
|
|
21
|
+
import { GSD_CONTEXT_MESSAGE_SENTINEL } from "../constants.js";
|
|
22
|
+
// Single source of truth lives in ../constants.js; re-exported here because
|
|
23
|
+
// buildContextMessage() stamps this marker on every context injection and the
|
|
24
|
+
// provider payload policy (filterSupersededContextInjections in
|
|
25
|
+
// context-masker.ts) matches it to dedupe. Adding a new context customType?
|
|
26
|
+
// Ensure buildContextMessage still prepends this sentinel.
|
|
27
|
+
export { GSD_CONTEXT_MESSAGE_SENTINEL };
|
|
21
28
|
const DEFAULT_CONTEXT_MESSAGE_MAX_CHARS = 4_000;
|
|
22
29
|
const DEFAULT_KNOWLEDGE_MAX_CHARS = 12_000;
|
|
23
30
|
const DEFAULT_CODEBASE_MAX_CHARS = 8_000;
|
|
@@ -385,17 +392,17 @@ export function buildContextMessage(opts) {
|
|
|
385
392
|
const contextCharLimit = getContextMessageCharLimit();
|
|
386
393
|
const memoryContent = markMemoryContextSupplied(opts.memoryBlock.trim());
|
|
387
394
|
if (opts.injection) {
|
|
388
|
-
const content = limitContextMessageContent(memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection
|
|
395
|
+
const content = limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection}`, contextCharLimit);
|
|
389
396
|
return { customType: "gsd-guided-context", content, display: false };
|
|
390
397
|
}
|
|
391
398
|
if (opts.forensicsInjection) {
|
|
392
|
-
const content = limitContextMessageContent(memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection
|
|
399
|
+
const content = limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection}`, contextCharLimit);
|
|
393
400
|
return { customType: "gsd-forensics", content, display: false };
|
|
394
401
|
}
|
|
395
402
|
if (memoryContent) {
|
|
396
403
|
return {
|
|
397
404
|
customType: "gsd-memory",
|
|
398
|
-
content: limitContextMessageContent(memoryContent
|
|
405
|
+
content: limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent}`, contextCharLimit),
|
|
399
406
|
display: false,
|
|
400
407
|
};
|
|
401
408
|
}
|
|
@@ -7,6 +7,7 @@ import { canonicalToolName } from "../engine-hook-contract.js";
|
|
|
7
7
|
import { loadJsonFileOrNull } from "../json-persistence.js";
|
|
8
8
|
import { getIsolationMode } from "../preferences.js";
|
|
9
9
|
import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
|
|
10
|
+
import { allowedPlanningDispatchAgentsList, isReadOnlyPlanningDispatchAgent, } from "../planning-subagent-registry.js";
|
|
10
11
|
import { logWarning } from "../workflow-logger.js";
|
|
11
12
|
import { acquireSyncLock, releaseSyncLock } from "../sync-lock.js";
|
|
12
13
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
@@ -981,29 +982,8 @@ export function shouldBlockQueueExecutionInSnapshot(snapshot, toolName, input, q
|
|
|
981
982
|
// guards.
|
|
982
983
|
const PLANNING_WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "notebook_edit"]);
|
|
983
984
|
const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
|
|
984
|
-
|
|
985
|
-
* Canonical registry for agents that planning-dispatch may consider. Unit
|
|
986
|
-
* manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
|
|
987
|
-
*/
|
|
988
|
-
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
989
|
-
mnemo: { readOnlySpecialist: true },
|
|
990
|
-
scout: { readOnlySpecialist: true },
|
|
991
|
-
planner: { readOnlySpecialist: true },
|
|
992
|
-
reviewer: { readOnlySpecialist: true },
|
|
993
|
-
security: { readOnlySpecialist: true },
|
|
994
|
-
tester: { readOnlySpecialist: true },
|
|
995
|
-
};
|
|
996
|
-
export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set(Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
|
|
997
|
-
.filter(([, metadata]) => metadata.readOnlySpecialist)
|
|
998
|
-
.map(([agentId]) => agentId));
|
|
985
|
+
export { ALLOWED_PLANNING_DISPATCH_AGENTS } from "../planning-subagent-registry.js";
|
|
999
986
|
let warnedMissingControlledDispatchAgentClasses = false;
|
|
1000
|
-
function isReadOnlySpecialist(agentId) {
|
|
1001
|
-
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId];
|
|
1002
|
-
return metadata?.readOnlySpecialist === true;
|
|
1003
|
-
}
|
|
1004
|
-
function allowedPlanningDispatchAgentsList() {
|
|
1005
|
-
return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
|
|
1006
|
-
}
|
|
1007
987
|
function allowsControlledSubagentDispatch(policy) {
|
|
1008
988
|
return ((policy.mode === "planning-dispatch" || policy.mode === "verification" || policy.mode === "workflow-only") &&
|
|
1009
989
|
Array.isArray(policy.allowedSubagents));
|
|
@@ -1151,13 +1131,13 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
1151
1131
|
if (requested.length === 0) {
|
|
1152
1132
|
return { block: false };
|
|
1153
1133
|
}
|
|
1154
|
-
const globallyDisallowed = requested.find(a => !
|
|
1134
|
+
const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a));
|
|
1155
1135
|
if (globallyDisallowed) {
|
|
1156
1136
|
return planningBlock(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`);
|
|
1157
1137
|
}
|
|
1158
1138
|
const disallowedByPolicy = requested.find(a => !allowed.has(a));
|
|
1159
1139
|
if (disallowedByPolicy) {
|
|
1160
|
-
return planningBlock(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`);
|
|
1140
|
+
return planningBlock(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by unit allowlist (ToolsPolicy.allowedSubagents/planning_subagents); permitted agents for this unit: ${allowedSubagents.join(", ")}`);
|
|
1161
1141
|
}
|
|
1162
1142
|
return { block: false };
|
|
1163
1143
|
}
|