@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
|
@@ -24,36 +24,35 @@ export const CACHE_MAX = 50;
|
|
|
24
24
|
* By scoping tools to this allowlist during discuss dispatches, the grammar
|
|
25
25
|
* sent to the provider stays well under provider limits.
|
|
26
26
|
*
|
|
27
|
-
* Included tools and why
|
|
27
|
+
* Included tools and why (canonical names only — aliases are no longer
|
|
28
|
+
* advertised to models by default, see plan 035):
|
|
28
29
|
* - gsd_summary_save: writes CONTEXT.md artifacts (all discuss prompts)
|
|
29
|
-
* - gsd_save_summary: alias for above
|
|
30
30
|
* - gsd_decision_save: records decisions (discuss.md output phase)
|
|
31
|
-
* - gsd_save_decision: alias for above
|
|
32
31
|
* - gsd_plan_milestone: writes roadmap (discuss.md single/multi milestone)
|
|
33
|
-
* - gsd_milestone_plan: alias for above
|
|
34
32
|
* - gsd_milestone_generate_id: generates milestone IDs (discuss.md multi-milestone)
|
|
35
|
-
* - gsd_generate_milestone_id: alias for above
|
|
36
33
|
* - gsd_requirement_save: creates requirements during discuss
|
|
37
|
-
* - gsd_save_requirement: alias for above
|
|
38
34
|
* - gsd_requirement_update: updates requirements during discuss
|
|
39
|
-
* - gsd_update_requirement: alias for above
|
|
40
35
|
*/
|
|
41
36
|
export const DISCUSS_TOOLS_ALLOWLIST = [
|
|
42
37
|
// Context / summary writing
|
|
43
38
|
"gsd_summary_save",
|
|
44
|
-
"gsd_save_summary",
|
|
45
39
|
// Decision recording
|
|
46
40
|
"gsd_decision_save",
|
|
47
|
-
"gsd_save_decision",
|
|
48
41
|
// Milestone planning (needed for discuss.md output phase)
|
|
49
42
|
"gsd_plan_milestone",
|
|
50
|
-
"gsd_milestone_plan",
|
|
51
43
|
// Milestone ID generation (multi-milestone flow)
|
|
52
44
|
"gsd_milestone_generate_id",
|
|
53
|
-
"gsd_generate_milestone_id",
|
|
54
45
|
// Requirement updates
|
|
55
46
|
"gsd_requirement_save",
|
|
56
|
-
"gsd_save_requirement",
|
|
57
47
|
"gsd_requirement_update",
|
|
58
|
-
"gsd_update_requirement",
|
|
59
48
|
];
|
|
49
|
+
// ─── Context Injection ────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Leading marker stamped on every buildContextMessage() output
|
|
52
|
+
* (bootstrap/system-context.ts). Single source of truth for the producer and
|
|
53
|
+
* the consumer: the provider payload policy (filterSupersededContextInjections
|
|
54
|
+
* in context-masker.ts) matches this prefix to find and dedupe injected
|
|
55
|
+
* memory/guided/forensics messages after convertToLlm strips their customType.
|
|
56
|
+
* Both sides import it from here — do not inline the literal in either module.
|
|
57
|
+
*/
|
|
58
|
+
export const GSD_CONTEXT_MESSAGE_SENTINEL = "[GSD Context Injection]";
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Observation masking for GSD auto-mode sessions.
|
|
3
|
-
*
|
|
4
|
-
* Replaces tool result content older than N turns with a placeholder.
|
|
5
|
-
* Reduces context bloat between compactions with zero LLM overhead.
|
|
6
|
-
* Preserves message ordering, roles, and all assistant/user messages.
|
|
7
|
-
*
|
|
8
|
-
* Operates on provider payloads after convertToLlm:
|
|
9
|
-
*
|
|
10
|
-
* pi-ai Message[] payloads:
|
|
11
|
-
* - toolResult messages: { role: "toolResult", content: TextContent[] }
|
|
12
|
-
* - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
|
|
13
|
-
* and start with "Ran `" from bashExecutionToText.
|
|
14
|
-
*
|
|
15
|
-
* OpenAI/Codex Responses payloads:
|
|
16
|
-
* - conversation items live in `input`, not `messages`
|
|
17
|
-
* - tool results are { type: "function_call_output", output: string | content[] }
|
|
18
|
-
* - bash results are user items with input_text content starting with "Ran `"
|
|
19
|
-
*/
|
|
1
|
+
import { GSD_CONTEXT_MESSAGE_SENTINEL } from "./constants.js";
|
|
20
2
|
const MASK_PLACEHOLDER = "[result masked — within summarized history]";
|
|
21
3
|
const MASK_CONTENT_BLOCK = [{ type: "text", text: MASK_PLACEHOLDER }];
|
|
22
4
|
const RESPONSES_MASK_CONTENT_BLOCK = [{ type: "input_text", text: MASK_PLACEHOLDER }];
|
|
@@ -52,6 +34,26 @@ function findTurnBoundary(messages, keepRecentTurns) {
|
|
|
52
34
|
}
|
|
53
35
|
return 0;
|
|
54
36
|
}
|
|
37
|
+
function countRealUserTurns(messages) {
|
|
38
|
+
let count = 0;
|
|
39
|
+
for (const m of messages) {
|
|
40
|
+
if (m.role === "user" && !isBashResultUserMessage(m))
|
|
41
|
+
count++;
|
|
42
|
+
}
|
|
43
|
+
return count;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Quantizes how many recent turns stay unmasked so the boundary only moves
|
|
47
|
+
* forward once per full block of `keepRecentTurns` new turns, instead of
|
|
48
|
+
* every turn. Keeps the masked prefix byte-stable within a block, which is
|
|
49
|
+
* what lets the LLM provider's prompt cache survive across turns.
|
|
50
|
+
*/
|
|
51
|
+
function quantizedKeepTurns(totalTurns, keepRecentTurns) {
|
|
52
|
+
if (keepRecentTurns <= 0 || totalTurns < 2 * keepRecentTurns)
|
|
53
|
+
return totalTurns;
|
|
54
|
+
const maskedTurns = Math.floor((totalTurns - keepRecentTurns) / keepRecentTurns) * keepRecentTurns;
|
|
55
|
+
return totalTurns - maskedTurns;
|
|
56
|
+
}
|
|
55
57
|
/**
|
|
56
58
|
* Detect user messages that originated from bashExecution.
|
|
57
59
|
* After convertToLlm, these are {role: "user", content: [{type:"text", text:"Ran `cmd`\n..."}]}.
|
|
@@ -73,7 +75,8 @@ function isMaskableMessage(m) {
|
|
|
73
75
|
}
|
|
74
76
|
export function createObservationMask(keepRecentTurns = 8) {
|
|
75
77
|
return (messages) => {
|
|
76
|
-
const
|
|
78
|
+
const totalTurns = countRealUserTurns(messages);
|
|
79
|
+
const boundary = findTurnBoundary(messages, quantizedKeepTurns(totalTurns, keepRecentTurns));
|
|
77
80
|
if (boundary === 0)
|
|
78
81
|
return messages;
|
|
79
82
|
return messages.map((m, i) => {
|
|
@@ -104,6 +107,14 @@ function findResponsesTurnBoundary(items, keepRecentTurns) {
|
|
|
104
107
|
}
|
|
105
108
|
return 0;
|
|
106
109
|
}
|
|
110
|
+
function countResponsesRealUserTurns(items) {
|
|
111
|
+
let count = 0;
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
if (item.role === "user" && !isResponsesBashResultUserItem(item))
|
|
114
|
+
count++;
|
|
115
|
+
}
|
|
116
|
+
return count;
|
|
117
|
+
}
|
|
107
118
|
/**
|
|
108
119
|
* Observation masking for OpenAI/Codex Responses API payloads.
|
|
109
120
|
*
|
|
@@ -113,7 +124,8 @@ function findResponsesTurnBoundary(items, keepRecentTurns) {
|
|
|
113
124
|
*/
|
|
114
125
|
export function createResponsesInputObservationMask(keepRecentTurns = 8) {
|
|
115
126
|
return (items) => {
|
|
116
|
-
const
|
|
127
|
+
const totalTurns = countResponsesRealUserTurns(items);
|
|
128
|
+
const boundary = findResponsesTurnBoundary(items, quantizedKeepTurns(totalTurns, keepRecentTurns));
|
|
117
129
|
if (boundary === 0)
|
|
118
130
|
return items;
|
|
119
131
|
return items.map((item, i) => {
|
|
@@ -190,3 +202,67 @@ export function truncateResponsesInputResultItems(items, maxChars = 800) {
|
|
|
190
202
|
return item;
|
|
191
203
|
});
|
|
192
204
|
}
|
|
205
|
+
// GSD injects at most one context message per turn (memory/guided/forensics —
|
|
206
|
+
// see buildContextMessage in bootstrap/system-context.ts), marked with
|
|
207
|
+
// GSD_CONTEXT_MESSAGE_SENTINEL. convertToLlm strips the distinguishing
|
|
208
|
+
// customType before the payload reaches this hook, so detection is by content
|
|
209
|
+
// prefix instead. Pre-sentinel session history lacks the sentinel, so we also
|
|
210
|
+
// match the stable *bracketed* GSD block labels those injections begin with,
|
|
211
|
+
// letting resumed sessions dedupe older memory/guided blocks.
|
|
212
|
+
//
|
|
213
|
+
// Only GSD-specific bracketed markers belong here — never generic prose. A
|
|
214
|
+
// message is dropped from the payload when it matches, so a natural-language
|
|
215
|
+
// prefix (e.g. the forensics prompt "Debug GSD itself.") could silently delete
|
|
216
|
+
// a real user message that happens to start the same way. Legacy forensics
|
|
217
|
+
// injections without memory are therefore left un-deduped by design; those
|
|
218
|
+
// with memory still match via the "[GSD Context Metadata]" wrapper.
|
|
219
|
+
const LEGACY_GSD_CONTEXT_INJECTION_PREFIXES = [
|
|
220
|
+
"[GSD Context Metadata]",
|
|
221
|
+
"[MEMORY — Critical and prompt-relevant memories from the GSD memory store]",
|
|
222
|
+
"[GSD Guided Execute Context]",
|
|
223
|
+
];
|
|
224
|
+
function isGsdContextInjectionText(text) {
|
|
225
|
+
if (typeof text !== "string")
|
|
226
|
+
return false;
|
|
227
|
+
if (text.startsWith(GSD_CONTEXT_MESSAGE_SENTINEL))
|
|
228
|
+
return true;
|
|
229
|
+
return LEGACY_GSD_CONTEXT_INJECTION_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
230
|
+
}
|
|
231
|
+
function isGsdContextInjectionMessage(m) {
|
|
232
|
+
if (m.role !== "user")
|
|
233
|
+
return false;
|
|
234
|
+
return isGsdContextInjectionText(firstTextFromContent(m.content));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Removes every GSD context-injection user message except the latest one.
|
|
238
|
+
* Each turn re-injects a near-identical memory/guided/forensics block; left
|
|
239
|
+
* in place they duplicate verbatim across an N-turn session. Removal (not
|
|
240
|
+
* masking) — an empty placeholder still costs tokens and shifts message
|
|
241
|
+
* positions, breaking cache byte-stability. Pure function: stored history is
|
|
242
|
+
* never mutated, only the outgoing payload array.
|
|
243
|
+
*/
|
|
244
|
+
export function filterSupersededContextInjections(messages) {
|
|
245
|
+
let lastIndex = -1;
|
|
246
|
+
for (let i = 0; i < messages.length; i++) {
|
|
247
|
+
if (isGsdContextInjectionMessage(messages[i]))
|
|
248
|
+
lastIndex = i;
|
|
249
|
+
}
|
|
250
|
+
if (lastIndex === -1)
|
|
251
|
+
return messages;
|
|
252
|
+
return messages.filter((m, i) => i === lastIndex || !isGsdContextInjectionMessage(m));
|
|
253
|
+
}
|
|
254
|
+
function isResponsesGsdContextInjectionItem(item) {
|
|
255
|
+
if (item.role !== "user")
|
|
256
|
+
return false;
|
|
257
|
+
return isGsdContextInjectionText(firstTextFromContent(item.content));
|
|
258
|
+
}
|
|
259
|
+
export function filterSupersededResponsesContextInjections(items) {
|
|
260
|
+
let lastIndex = -1;
|
|
261
|
+
for (let i = 0; i < items.length; i++) {
|
|
262
|
+
if (isResponsesGsdContextInjectionItem(items[i]))
|
|
263
|
+
lastIndex = i;
|
|
264
|
+
}
|
|
265
|
+
if (lastIndex === -1)
|
|
266
|
+
return items;
|
|
267
|
+
return items.filter((item, i) => i === lastIndex || !isResponsesGsdContextInjectionItem(item));
|
|
268
|
+
}
|
|
@@ -357,6 +357,28 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
|
|
|
357
357
|
- `"replace"` requires `prompt`.
|
|
358
358
|
- `"skip"` is valid with no additional fields.
|
|
359
359
|
|
|
360
|
+
- `planning_subagents`: object — project-local widening for read-only planning subagent dispatch. Supported unit keys are `plan-milestone` and `plan-slice`; each has:
|
|
361
|
+
- `allowed`: string[] — additional planning-safe agents for that unit.
|
|
362
|
+
|
|
363
|
+
Agents must already be classified by GSD as read-only planning specialists: `mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the global read-only registry, and agents not listed for the active unit.
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
|
|
367
|
+
```yaml
|
|
368
|
+
planning_subagents:
|
|
369
|
+
plan-milestone:
|
|
370
|
+
allowed:
|
|
371
|
+
- scout
|
|
372
|
+
- planner
|
|
373
|
+
- security
|
|
374
|
+
plan-slice:
|
|
375
|
+
allowed:
|
|
376
|
+
- scout
|
|
377
|
+
- planner
|
|
378
|
+
- reviewer
|
|
379
|
+
- security
|
|
380
|
+
```
|
|
381
|
+
|
|
360
382
|
**Known unit types for `before`/`after`:** `research-milestone`, `plan-milestone`, `research-slice`, `plan-slice`, `execute-task`, `complete-slice`, `replan-slice`, `reassess-roadmap`, `run-uat`.
|
|
361
383
|
|
|
362
384
|
- `experimental`: opt-in experimental features. All features here are **off by default** — you must explicitly set each one to `true` to enable it. Features in this block may change or be removed without a deprecation cycle while in experimental status. Keys:
|
|
@@ -3,7 +3,7 @@ import { isAbsolute, join, relative, sep } from "node:path";
|
|
|
3
3
|
import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
|
|
4
4
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
5
5
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
6
|
-
import {
|
|
6
|
+
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "./paths.js";
|
|
7
7
|
import { deriveState } from "./state.js";
|
|
8
8
|
import { isClosedStatus } from "./status-guards.js";
|
|
9
9
|
import { workflowEventLogPath } from "./workflow-event-ledger.js";
|
|
@@ -152,11 +152,8 @@ function resolveFlatPhaseArtifactDiskPath(basePath, row) {
|
|
|
152
152
|
if (!artifactType)
|
|
153
153
|
return null;
|
|
154
154
|
if (row.slice_id && row.task_id) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
return null;
|
|
158
|
-
const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
|
|
159
|
-
return existsSync(candidate) ? candidate : null;
|
|
155
|
+
const candidate = resolveTaskFile(basePath, row.milestone_id, row.slice_id, row.task_id, artifactType);
|
|
156
|
+
return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
|
|
160
157
|
}
|
|
161
158
|
const candidate = row.slice_id
|
|
162
159
|
? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
|
|
@@ -229,10 +229,15 @@ export const RUNTIME_EXCLUSION_PATHS = [
|
|
|
229
229
|
const runtimeFilesCleanedUpRepos = new Set();
|
|
230
230
|
// ─── Integration Branch Metadata ───────────────────────────────────────────
|
|
231
231
|
/**
|
|
232
|
-
* Path to the milestone metadata file
|
|
233
|
-
* Format: .gsd
|
|
232
|
+
* Path to the milestone integration-branch metadata file.
|
|
233
|
+
* Format: .gsd/<MID>-META.json (deliberately OUTSIDE milestones/<MID>/ so it
|
|
234
|
+
* cannot poison isLegacyMilestonesLayout — see ADR-045).
|
|
234
235
|
*/
|
|
235
|
-
function milestoneMetaPath(basePath, milestoneId) {
|
|
236
|
+
export function milestoneMetaPath(basePath, milestoneId) {
|
|
237
|
+
return join(gsdRoot(basePath), `${milestoneId}-META.json`);
|
|
238
|
+
}
|
|
239
|
+
/** Legacy location (pre-ADR-045). Read-only fallback for existing on-disk trees. */
|
|
240
|
+
function legacyMilestoneMetaPath(basePath, milestoneId) {
|
|
236
241
|
return join(gsdRoot(basePath), "milestones", milestoneId, `${milestoneId}-META.json`);
|
|
237
242
|
}
|
|
238
243
|
/**
|
|
@@ -241,9 +246,13 @@ function milestoneMetaPath(basePath, milestoneId) {
|
|
|
241
246
|
*/
|
|
242
247
|
export function readIntegrationBranch(basePath, milestoneId) {
|
|
243
248
|
try {
|
|
244
|
-
|
|
245
|
-
if (!existsSync(metaFile))
|
|
246
|
-
|
|
249
|
+
let metaFile = milestoneMetaPath(basePath, milestoneId);
|
|
250
|
+
if (!existsSync(metaFile)) {
|
|
251
|
+
const legacy = legacyMilestoneMetaPath(basePath, milestoneId);
|
|
252
|
+
if (!existsSync(legacy))
|
|
253
|
+
return null;
|
|
254
|
+
metaFile = legacy;
|
|
255
|
+
}
|
|
247
256
|
const data = JSON.parse(readFileSync(metaFile, "utf-8"));
|
|
248
257
|
const branch = data?.integrationBranch;
|
|
249
258
|
if (typeof branch === "string" && branch.trim() !== "" && VALID_BRANCH_NAME.test(branch)) {
|
|
@@ -295,7 +304,10 @@ export function writeIntegrationBranch(basePath, milestoneId, branch) {
|
|
|
295
304
|
if (existingBranch === branch)
|
|
296
305
|
return;
|
|
297
306
|
const metaFile = milestoneMetaPath(basePath, milestoneId);
|
|
298
|
-
|
|
307
|
+
// Ensure the flat .gsd/ root exists (never milestones/<MID>/ — that would
|
|
308
|
+
// poison layout detection, ADR-045). In production .gsd/ already exists; this
|
|
309
|
+
// guards fresh trees so the write below can't ENOENT.
|
|
310
|
+
mkdirSync(gsdRoot(basePath), { recursive: true });
|
|
299
311
|
// Merge with existing metadata if present
|
|
300
312
|
let existing = {};
|
|
301
313
|
try {
|
|
@@ -11,7 +11,7 @@ import { loadFile, saveFile } from "./files.js";
|
|
|
11
11
|
import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
|
|
12
12
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
13
13
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
14
|
-
import { buildCompleteSlicePrompt, buildDiscussMilestonePrompt, buildExecuteTaskPrompt, buildPlanMilestonePrompt, buildPlanSlicePrompt, buildSkillActivationBlock, } from "./auto-prompts.js";
|
|
14
|
+
import { buildCompleteSlicePrompt, buildDiscussMilestonePrompt, buildExecuteTaskPrompt, buildPlanMilestonePrompt, buildPlanSlicePrompt, buildSkillActivationBlock, capPreamble, } from "./auto-prompts.js";
|
|
15
15
|
import { deriveState, isGhostMilestone } from "./state.js";
|
|
16
16
|
import { invalidateAllCaches } from "./cache.js";
|
|
17
17
|
import { startAutoDetached } from "./auto.js";
|
|
@@ -855,7 +855,9 @@ function resolveDiscussSliceBasePath(basePath, milestoneId) {
|
|
|
855
855
|
* slice summaries so the agent can ask grounded UX/behaviour questions
|
|
856
856
|
* without wasting a turn reading files.
|
|
857
857
|
*/
|
|
858
|
-
|
|
858
|
+
// Exported for tests (`prompt-budget-enforcement.test.ts`) — verifies the
|
|
859
|
+
// inlined-context cap. Not part of the public flow surface otherwise.
|
|
860
|
+
export async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
|
|
859
861
|
const inlined = [];
|
|
860
862
|
// Roadmap — always included so the agent sees surrounding slices
|
|
861
863
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
@@ -906,8 +908,12 @@ async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
|
|
|
906
908
|
}
|
|
907
909
|
}
|
|
908
910
|
}
|
|
911
|
+
// Cap the inlined block: it grows unbounded with each completed slice's full
|
|
912
|
+
// SUMMARY. `capPreamble` truncates at `### ` section boundaries — roadmap and
|
|
913
|
+
// milestone context come first (survive), completed-slice summaries come last
|
|
914
|
+
// (dropped first) when the executor's window can't hold everything.
|
|
909
915
|
const inlinedContext = inlined.length > 0
|
|
910
|
-
? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
|
|
916
|
+
? capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`)
|
|
911
917
|
: `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
|
|
912
918
|
const sliceDirPath = `.gsd/milestones/${mid}/slices/${sid}`;
|
|
913
919
|
const sliceContextPath = `${sliceDirPath}/${sid}-CONTEXT.md`;
|
|
@@ -13,7 +13,7 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
14
|
import { dirname, join, relative } from "node:path";
|
|
15
15
|
import { getAllMilestones, getMilestone, getMilestoneScopedArtifacts, getSliceScopedArtifacts, getMilestoneSlices, getSliceTasks, getTasksBySliceIds, getTask, getSlice, insertArtifact, deleteArtifactByPath, getGateResults, isDbAvailable, } from "./gsd-db.js";
|
|
16
|
-
import { resolveDir, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, targetMilestoneFile, targetSliceFile, gsdProjectionRoot, gsdRoot, buildTaskFileName, } from "./paths.js";
|
|
16
|
+
import { resolveDir, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, targetMilestoneFile, targetSliceFile, targetTaskFile, gsdProjectionRoot, gsdRoot, buildTaskFileName, } from "./paths.js";
|
|
17
17
|
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
18
18
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
@@ -612,17 +612,7 @@ export async function renderTaskSummary(basePath, milestoneId, sliceId, taskId)
|
|
|
612
612
|
if (!task || !task.full_summary_md) {
|
|
613
613
|
return false; // No summary to render — skip silently
|
|
614
614
|
}
|
|
615
|
-
|
|
616
|
-
const slicePath = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
617
|
-
if (!slicePath) {
|
|
618
|
-
process.stderr.write(`markdown-renderer: cannot resolve slice path for ${milestoneId}/${sliceId}\n`);
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
// Use the tasks/ subdir when it exists (legacy layout), otherwise use the
|
|
622
|
-
// slice path directly (flat-phase: task summaries live alongside plan files).
|
|
623
|
-
const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId) ?? slicePath;
|
|
624
|
-
const fileName = buildTaskFileName(taskId, "SUMMARY");
|
|
625
|
-
const absPath = join(tasksDir, fileName);
|
|
615
|
+
const absPath = targetTaskFile(basePath, milestoneId, sliceId, taskId, "SUMMARY", getMilestone(milestoneId)?.title);
|
|
626
616
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
627
617
|
await writeAndStore(absPath, artifactPath, task.full_summary_md, {
|
|
628
618
|
artifact_type: "SUMMARY",
|
|
@@ -7,7 +7,7 @@ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
|
7
7
|
import { join, basename, dirname } from 'node:path';
|
|
8
8
|
import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, transaction, updateMilestoneStatus, updateSliceStatus, getMilestoneSlices, getSliceTasks, _getAdapter, } from './gsd-db.js';
|
|
9
9
|
import { openWorkflowDatabasePath } from './db-workspace.js';
|
|
10
|
-
import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, legacyMilestonesDir, gsdRoot, resolveTaskFiles, resolveMilestonePath, } from './paths.js';
|
|
10
|
+
import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, legacyMilestonesDir, gsdRoot, resolveTaskFiles, resolveTaskFile, resolveMilestonePath, } from './paths.js';
|
|
11
11
|
import { findMilestoneIds } from './guided-flow.js';
|
|
12
12
|
import { milestoneIdToPhaseNum } from './layout-policy.js';
|
|
13
13
|
import { parseRoadmap, parsePlan } from './parsers-legacy.js';
|
|
@@ -762,8 +762,7 @@ export function migrateHierarchyToDb(basePath, opts) {
|
|
|
762
762
|
legacyTasksDir = isLegacySlice
|
|
763
763
|
? resolveTasksDir(basePath, milestoneId, sliceEntry.id)
|
|
764
764
|
: null;
|
|
765
|
-
|
|
766
|
-
taskSummaryFile = join(summaryDir, `${taskEntry.id}-SUMMARY.md`);
|
|
765
|
+
taskSummaryFile = resolveTaskFile(basePath, milestoneId, sliceEntry.id, taskEntry.id, 'SUMMARY');
|
|
767
766
|
}
|
|
768
767
|
const hasTaskSummary = taskSummaryFile !== null && existsSync(taskSummaryFile);
|
|
769
768
|
// Pre-migration consistency (legacy tasks/ layout only): if a task is
|
|
@@ -783,9 +782,9 @@ export function migrateHierarchyToDb(basePath, opts) {
|
|
|
783
782
|
// first execute-task. The surviving SUMMARY wins over a stale checkbox.
|
|
784
783
|
// reopen-task removes the SUMMARY, so this never re-completes an
|
|
785
784
|
// intentionally reopened task.
|
|
786
|
-
// Flat-phase summaries are phase-scoped
|
|
787
|
-
//
|
|
788
|
-
//
|
|
785
|
+
// Flat-phase summaries are phase-scoped; when a milestone has multiple
|
|
786
|
+
// slices, require the summary parent frontmatter to match so one slice's
|
|
787
|
+
// completion does not attest a sibling's same-named task.
|
|
789
788
|
let summaryAttestsCompletion = hasTaskSummary;
|
|
790
789
|
if (summaryAttestsCompletion && !isLegacySlice && roadmap.slices.length > 1 && taskSummaryFile) {
|
|
791
790
|
try {
|
|
@@ -48,6 +48,8 @@ export const BUNDLED_COST_TABLE = [
|
|
|
48
48
|
// GPT-5.5 API list price, also used for live Codex OAuth routing.
|
|
49
49
|
// Source: https://openai.com/api/pricing/
|
|
50
50
|
{ id: "gpt-5.5", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-04-23" },
|
|
51
|
+
// Mirrors gpt-5.5 list pricing until OpenAI publishes official gpt-5.6 pricing.
|
|
52
|
+
{ id: "gpt-5.6", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-07-08" },
|
|
51
53
|
// Google
|
|
52
54
|
{ id: "gemini-2.0-flash", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
|
|
53
55
|
{ id: "gemini-flash-2.0", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
|
|
@@ -46,10 +46,12 @@ export const MODEL_CAPABILITY_TIER = {
|
|
|
46
46
|
"gpt-5.3-codex": "heavy",
|
|
47
47
|
"gpt-5.4": "heavy",
|
|
48
48
|
"gpt-5.5": "heavy",
|
|
49
|
+
"gpt-5.6": "heavy",
|
|
49
50
|
"o1": "heavy",
|
|
50
51
|
"o3": "heavy",
|
|
51
52
|
"o4-mini": "heavy",
|
|
52
53
|
"o4-mini-deep-research": "heavy",
|
|
54
|
+
"grok-4.5": "heavy",
|
|
53
55
|
};
|
|
54
56
|
// ─── Cost Table (per 1K input tokens, approximate USD) ───────────────────────
|
|
55
57
|
// Used for cross-provider cost comparison when multiple providers offer
|
|
@@ -82,11 +84,13 @@ const MODEL_COST_PER_1K_INPUT = {
|
|
|
82
84
|
"gpt-5.3-codex-spark": 0.0003,
|
|
83
85
|
"gpt-5.4": 0.005,
|
|
84
86
|
"gpt-5.5": 0.005,
|
|
87
|
+
"gpt-5.6": 0.005,
|
|
85
88
|
"o4-mini": 0.005,
|
|
86
89
|
"o4-mini-deep-research": 0.005,
|
|
87
90
|
"gemini-2.0-flash": 0.0001,
|
|
88
91
|
"gemini-2.5-pro": 0.00125,
|
|
89
92
|
"deepseek-chat": 0.00014,
|
|
93
|
+
"grok-4.5": 0.002,
|
|
90
94
|
};
|
|
91
95
|
// ─── Capability Profiles Data Table ──────────────────────────────────────────
|
|
92
96
|
// Per-model capability profiles (0–100 scale). Used for capability-aware
|
|
@@ -128,6 +132,9 @@ export const MODEL_CAPABILITY_PROFILES = {
|
|
|
128
132
|
// OpenAI's 2026-04-23 published eval deltas across coding, tool use, and long context.
|
|
129
133
|
// Source: https://openai.com/index/introducing-gpt-5-5/
|
|
130
134
|
"gpt-5.5": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 42, longContext: 90, instruction: 93 },
|
|
135
|
+
// GPT-5.6 profile is provisional: a small uniform step over gpt-5.5, to be
|
|
136
|
+
// recalibrated once OpenAI publishes official eval results.
|
|
137
|
+
"gpt-5.6": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
|
|
131
138
|
// ── OpenAI o-series (reasoning-first) ──────────────────────────────────────
|
|
132
139
|
"o1": { coding: 78, debugging: 82, research: 78, reasoning: 90, speed: 20, longContext: 65, instruction: 82 },
|
|
133
140
|
"o3": { coding: 80, debugging: 85, research: 80, reasoning: 92, speed: 25, longContext: 70, instruction: 85 },
|
|
@@ -139,6 +146,10 @@ export const MODEL_CAPABILITY_PROFILES = {
|
|
|
139
146
|
"gemini-flash-2.0": { coding: 50, debugging: 40, research: 50, reasoning: 40, speed: 95, longContext: 60, instruction: 65 },
|
|
140
147
|
// ── DeepSeek ───────────────────────────────────────────────────────────────
|
|
141
148
|
"deepseek-chat": { coding: 75, debugging: 65, research: 55, reasoning: 70, speed: 70, longContext: 55, instruction: 65 },
|
|
149
|
+
// ── xAI ────────────────────────────────────────────────────────────────────
|
|
150
|
+
// Grok 4.5 (2026-07): Opus-class frontier model tuned for coding and agentic
|
|
151
|
+
// work; notably faster and more token-efficient than peer heavy models.
|
|
152
|
+
"grok-4.5": { coding: 95, debugging: 90, research: 82, reasoning: 93, speed: 55, longContext: 80, instruction: 90 },
|
|
142
153
|
};
|
|
143
154
|
// ─── Base Task Requirements Data Table ───────────────────────────────────────
|
|
144
155
|
// Per-unit-type base requirement vectors. Weights indicate how important each
|
|
@@ -198,6 +198,25 @@ export function buildTaskFileName(taskId, suffix) {
|
|
|
198
198
|
// This helper is deprecated but kept for backward-compat callers.
|
|
199
199
|
return `${taskId}-${suffix}.md`;
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Build a flat-phase task artifact file name.
|
|
203
|
+
* ("S06", "T03", "SUMMARY") → "S06-T03-SUMMARY.md"
|
|
204
|
+
*/
|
|
205
|
+
export function buildFlatTaskFileName(sliceId, taskId, suffix) {
|
|
206
|
+
return `${sliceId}-${taskId}-${suffix}.md`;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Extract the task ID from a task artifact filename.
|
|
210
|
+
* Supports flat-phase S##-T##-SUFFIX.md plus legacy T##-SUFFIX.md and
|
|
211
|
+
* T##-DESCRIPTOR-SUFFIX.md names.
|
|
212
|
+
*/
|
|
213
|
+
export function taskIdFromTaskFileName(fileName, suffix) {
|
|
214
|
+
const flat = new RegExp(`^S\\d+-(T\\d+)-${suffix}\\.md$`, "i").exec(fileName);
|
|
215
|
+
if (flat?.[1])
|
|
216
|
+
return flat[1].toUpperCase();
|
|
217
|
+
const legacy = new RegExp(`^(T\\d+)(?:-.*)?-${suffix}\\.md$`, "i").exec(fileName);
|
|
218
|
+
return legacy?.[1]?.toUpperCase() ?? null;
|
|
219
|
+
}
|
|
201
220
|
// ─── Resolvers ─────────────────────────────────────────────────────────────
|
|
202
221
|
/**
|
|
203
222
|
* Find a directory entry by ID prefix within a parent directory.
|
|
@@ -259,19 +278,16 @@ export function resolveFile(dir, idPrefix, suffix) {
|
|
|
259
278
|
}
|
|
260
279
|
}
|
|
261
280
|
/**
|
|
262
|
-
* Find all task files matching a pattern in a tasks directory.
|
|
263
|
-
* Returns sorted file names matching T##-SUFFIX.md
|
|
281
|
+
* Find all task files matching a pattern in a tasks directory or flat phase dir.
|
|
282
|
+
* Returns sorted file names matching S##-T##-SUFFIX.md, T##-SUFFIX.md,
|
|
283
|
+
* or legacy T##-*-SUFFIX.md.
|
|
264
284
|
*/
|
|
265
285
|
export function resolveTaskFiles(tasksDir, suffix) {
|
|
266
286
|
if (!existsSync(tasksDir))
|
|
267
287
|
return [];
|
|
268
288
|
try {
|
|
269
|
-
// Current convention: T01-PLAN.md
|
|
270
|
-
const currentPattern = new RegExp(`^T\\d+-${suffix}\\.md$`, "i");
|
|
271
|
-
// Legacy convention: T01-INSTALL-PACKAGES-PLAN.md
|
|
272
|
-
const legacyPattern = new RegExp(`^T\\d+-.*-${suffix}\\.md$`, "i");
|
|
273
289
|
return cachedReaddir(tasksDir)
|
|
274
|
-
.filter(f =>
|
|
290
|
+
.filter(f => taskIdFromTaskFileName(f, suffix) !== null)
|
|
275
291
|
.sort();
|
|
276
292
|
}
|
|
277
293
|
catch {
|
|
@@ -893,8 +909,11 @@ export function resolveTaskFile(basePath, milestoneId, sliceId, taskId, suffix)
|
|
|
893
909
|
const legacyBase = legacyMilestonesDir(basePath);
|
|
894
910
|
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
895
911
|
if (suffix !== "PLAN" && !isLegacy) {
|
|
896
|
-
const flatPath = join(phaseDir,
|
|
897
|
-
|
|
912
|
+
const flatPath = join(phaseDir, buildFlatTaskFileName(sliceId, taskId, suffix));
|
|
913
|
+
if (existsSync(flatPath))
|
|
914
|
+
return flatPath;
|
|
915
|
+
const legacyFlatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
|
|
916
|
+
return existsSync(legacyFlatPath) ? legacyFlatPath : null;
|
|
898
917
|
}
|
|
899
918
|
const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
900
919
|
if (tDir) {
|
|
@@ -1006,6 +1025,28 @@ export function targetSliceFile(basePath, milestoneId, sliceId, suffix, mileston
|
|
|
1006
1025
|
: planFileName(milestoneIdToPhaseNum(milestoneId), sliceIdToPlanNum(sliceId), suffix);
|
|
1007
1026
|
return join(dir, fileName);
|
|
1008
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Build the canonical absolute write target for a task file.
|
|
1030
|
+
* Readers that need legacy flat-phase fallback should call resolveTaskFile.
|
|
1031
|
+
*/
|
|
1032
|
+
export function targetTaskFile(basePath, milestoneId, sliceId, taskId, suffix, milestoneTitle) {
|
|
1033
|
+
if (suffix === "PLAN") {
|
|
1034
|
+
return targetSliceFile(basePath, milestoneId, sliceId, "PLAN", milestoneTitle);
|
|
1035
|
+
}
|
|
1036
|
+
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
1037
|
+
const legacyBase = legacyMilestonesDir(basePath);
|
|
1038
|
+
const isLegacy = mDir
|
|
1039
|
+
? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
|
|
1040
|
+
: isLegacyMilestonesLayout(basePath);
|
|
1041
|
+
const milestoneDir = mDir
|
|
1042
|
+
?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestoneTitle));
|
|
1043
|
+
if (isLegacy) {
|
|
1044
|
+
const slicesDir = join(milestoneDir, "slices");
|
|
1045
|
+
const sliceDir = join(slicesDir, resolveDir(slicesDir, sliceId) ?? sliceId);
|
|
1046
|
+
return join(sliceDir, "tasks", buildTaskFileName(taskId, suffix));
|
|
1047
|
+
}
|
|
1048
|
+
return join(milestoneDir, buildFlatTaskFileName(sliceId, taskId, suffix));
|
|
1049
|
+
}
|
|
1009
1050
|
/**
|
|
1010
1051
|
* Build relative .gsd/ path to a slice file.
|
|
1011
1052
|
* Preserves an existing compatibility filename when present; otherwise falls
|
|
@@ -1036,5 +1077,5 @@ export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix, mile
|
|
|
1036
1077
|
return relSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
1037
1078
|
}
|
|
1038
1079
|
const relS = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
|
|
1039
|
-
return `${relS}/${
|
|
1080
|
+
return `${relS}/${buildFlatTaskFileName(sliceId, taskId, suffix)}`;
|
|
1040
1081
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CONFIGURABLE_PLANNING_SUBAGENT_UNITS } from "./preferences-types.js";
|
|
2
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
3
|
+
const CONFIGURABLE_UNITS = new Set(CONFIGURABLE_PLANNING_SUBAGENT_UNITS);
|
|
4
|
+
function configuredAllowedSubagents(unitType, preferences) {
|
|
5
|
+
if (!CONFIGURABLE_UNITS.has(unitType))
|
|
6
|
+
return [];
|
|
7
|
+
return preferences?.planning_subagents?.[unitType]?.allowed ?? [];
|
|
8
|
+
}
|
|
9
|
+
function mergeAllowedSubagents(current, configured) {
|
|
10
|
+
return Array.from(new Set([...current, ...configured]));
|
|
11
|
+
}
|
|
12
|
+
export function applyPlanningSubagentPreferences(unitType, policy, preferences) {
|
|
13
|
+
if (!policy)
|
|
14
|
+
return policy;
|
|
15
|
+
const configured = configuredAllowedSubagents(unitType, preferences);
|
|
16
|
+
if (configured.length === 0)
|
|
17
|
+
return policy;
|
|
18
|
+
if (policy.mode === "planning") {
|
|
19
|
+
return {
|
|
20
|
+
mode: "planning-dispatch",
|
|
21
|
+
allowedSubagents: mergeAllowedSubagents([], configured),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (policy.mode === "planning-dispatch") {
|
|
25
|
+
return {
|
|
26
|
+
...policy,
|
|
27
|
+
allowedSubagents: mergeAllowedSubagents(policy.allowedSubagents, configured),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return policy;
|
|
31
|
+
}
|
|
32
|
+
export function resolveEffectivePlanningToolsPolicy(unitType, policy, basePath) {
|
|
33
|
+
if (!basePath)
|
|
34
|
+
return policy;
|
|
35
|
+
const preferences = loadEffectiveGSDPreferences(basePath)?.preferences;
|
|
36
|
+
return applyPlanningSubagentPreferences(unitType, policy, preferences);
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only planning specialists that controlled planning dispatch may use.
|
|
3
|
+
*
|
|
4
|
+
* Unit manifests and project preferences still declare per-unit subsets. This
|
|
5
|
+
* registry is the global safety classification checked by the write gate and
|
|
6
|
+
* preference validation before any configured planning allowlist takes effect.
|
|
7
|
+
*/
|
|
8
|
+
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
9
|
+
mnemo: { readOnlySpecialist: true },
|
|
10
|
+
scout: { readOnlySpecialist: true },
|
|
11
|
+
planner: { readOnlySpecialist: true },
|
|
12
|
+
reviewer: { readOnlySpecialist: true },
|
|
13
|
+
security: { readOnlySpecialist: true },
|
|
14
|
+
tester: { readOnlySpecialist: true },
|
|
15
|
+
};
|
|
16
|
+
export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set(Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
|
|
17
|
+
.filter(([, metadata]) => metadata.readOnlySpecialist)
|
|
18
|
+
.map(([agentId]) => agentId));
|
|
19
|
+
export function isReadOnlyPlanningDispatchAgent(agentId) {
|
|
20
|
+
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId];
|
|
21
|
+
return metadata?.readOnlySpecialist === true;
|
|
22
|
+
}
|
|
23
|
+
export function allowedPlanningDispatchAgentsList() {
|
|
24
|
+
return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
|
|
25
|
+
}
|
|
@@ -61,6 +61,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
|
|
|
61
61
|
"git",
|
|
62
62
|
"post_unit_hooks",
|
|
63
63
|
"pre_dispatch_hooks",
|
|
64
|
+
"planning_subagents",
|
|
64
65
|
"dynamic_routing",
|
|
65
66
|
"disabled_model_providers",
|
|
66
67
|
"uok",
|
|
@@ -134,6 +135,10 @@ export const KNOWN_UNIT_LABELS = [
|
|
|
134
135
|
"workflow-preferences", "discuss-project", "discuss-requirements",
|
|
135
136
|
"research-decision", "research-project",
|
|
136
137
|
];
|
|
138
|
+
export const CONFIGURABLE_PLANNING_SUBAGENT_UNITS = [
|
|
139
|
+
"plan-milestone",
|
|
140
|
+
"plan-slice",
|
|
141
|
+
];
|
|
137
142
|
export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
138
143
|
/** The nine model-routing phase buckets. */
|
|
139
144
|
export const GSD_MODEL_PHASE_KEYS = [
|