gsd-pi 2.39.0 → 2.40.0-dev.4a93031

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.
Files changed (133) hide show
  1. package/dist/resource-loader.js +66 -2
  2. package/dist/resources/extensions/async-jobs/index.js +10 -0
  3. package/dist/resources/extensions/get-secrets-from-user.js +1 -1
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  5. package/dist/resources/extensions/gsd/auto-loop.js +761 -673
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
  8. package/dist/resources/extensions/gsd/auto-start.js +6 -1
  9. package/dist/resources/extensions/gsd/auto.js +6 -4
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  12. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  13. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  16. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  18. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  19. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  20. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  21. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  23. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  24. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  25. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  26. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  27. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +8 -1190
  29. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  30. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  31. package/dist/resources/extensions/gsd/doctor.js +32 -2
  32. package/dist/resources/extensions/gsd/export-html.js +46 -0
  33. package/dist/resources/extensions/gsd/files.js +1 -1
  34. package/dist/resources/extensions/gsd/health-widget.js +1 -1
  35. package/dist/resources/extensions/gsd/index.js +4 -1115
  36. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  37. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  38. package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
  39. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  40. package/dist/welcome-screen.d.ts +3 -2
  41. package/dist/welcome-screen.js +66 -22
  42. package/package.json +1 -1
  43. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  44. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  46. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  48. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  49. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  50. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  51. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/skills.js +2 -1
  53. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  55. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  57. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  59. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  60. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  61. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  62. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  63. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  64. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  65. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  66. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  67. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  68. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  69. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  70. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  71. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  73. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  75. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  77. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  78. package/packages/pi-coding-agent/package.json +1 -1
  79. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  80. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  81. package/packages/pi-coding-agent/src/core/skills.ts +2 -1
  82. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  83. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  84. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  85. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  86. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  87. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  88. package/pkg/package.json +1 -1
  89. package/src/resources/extensions/async-jobs/index.ts +11 -0
  90. package/src/resources/extensions/get-secrets-from-user.ts +1 -1
  91. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  92. package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
  93. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
  94. package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
  95. package/src/resources/extensions/gsd/auto-start.ts +6 -1
  96. package/src/resources/extensions/gsd/auto.ts +13 -10
  97. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  98. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  99. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  100. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  101. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  102. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  103. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  104. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  105. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  106. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  107. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  108. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  109. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  110. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  111. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  112. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  113. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  114. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  115. package/src/resources/extensions/gsd/commands.ts +10 -1329
  116. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  117. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  118. package/src/resources/extensions/gsd/doctor.ts +47 -3
  119. package/src/resources/extensions/gsd/export-html.ts +51 -0
  120. package/src/resources/extensions/gsd/files.ts +1 -1
  121. package/src/resources/extensions/gsd/health-widget.ts +2 -1
  122. package/src/resources/extensions/gsd/index.ts +12 -1314
  123. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  124. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  125. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
  126. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  127. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  128. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
  129. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  130. package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
  131. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  132. /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
  133. /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
@@ -0,0 +1,156 @@
1
+ import { join } from "node:path";
2
+ import { isToolCallEventType } from "@gsd/pi-coding-agent";
3
+ import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
4
+ import { buildBeforeAgentStartResult } from "./system-context.js";
5
+ import { handleAgentEnd } from "./agent-end-recovery.js";
6
+ import { isDepthVerified, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite } from "./write-gate.js";
7
+ import { getDiscussionMilestoneId } from "../guided-flow.js";
8
+ import { loadToolApiKeys } from "../commands-config.js";
9
+ import { loadFile, saveFile, formatContinue } from "../files.js";
10
+ import { deriveState } from "../state.js";
11
+ import { getAutoDashboardData, isAutoActive, isAutoPaused, markToolEnd, markToolStart } from "../auto.js";
12
+ import { isParallelActive, shutdownParallel } from "../parallel-orchestrator.js";
13
+ import { saveActivityLog } from "../activity-log.js";
14
+ import { maybeRenderGsdHeader } from "./register-shortcuts.js";
15
+ export function registerHooks(pi) {
16
+ pi.on("session_start", async (_event, ctx) => {
17
+ resetWriteGateState();
18
+ maybeRenderGsdHeader(ctx);
19
+ loadToolApiKeys();
20
+ try {
21
+ const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([
22
+ import("../../remote-questions/config.js"),
23
+ import("../../remote-questions/status.js"),
24
+ ]);
25
+ const status = getRemoteConfigStatus();
26
+ const latest = getLatestPromptSummary();
27
+ if (!status.includes("not configured")) {
28
+ const suffix = latest ? `\nLast remote prompt: ${latest.id} (${latest.status})` : "";
29
+ ctx.ui.notify(`${status}${suffix}`, status.includes("disabled") ? "warning" : "info");
30
+ }
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ });
36
+ pi.on("before_agent_start", async (event, ctx) => {
37
+ return buildBeforeAgentStartResult(event, ctx);
38
+ });
39
+ pi.on("agent_end", async (event, ctx) => {
40
+ await handleAgentEnd(pi, event, ctx);
41
+ });
42
+ pi.on("session_before_compact", async () => {
43
+ if (isAutoActive() || isAutoPaused()) {
44
+ return { cancel: true };
45
+ }
46
+ const basePath = process.cwd();
47
+ const state = await deriveState(basePath);
48
+ if (!state.activeMilestone || !state.activeSlice || !state.activeTask)
49
+ return;
50
+ if (state.phase !== "executing")
51
+ return;
52
+ const sliceDir = resolveSlicePath(basePath, state.activeMilestone.id, state.activeSlice.id);
53
+ if (!sliceDir)
54
+ return;
55
+ const existingFile = resolveSliceFile(basePath, state.activeMilestone.id, state.activeSlice.id, "CONTINUE");
56
+ if (existingFile && await loadFile(existingFile))
57
+ return;
58
+ const legacyContinue = join(sliceDir, "continue.md");
59
+ if (await loadFile(legacyContinue))
60
+ return;
61
+ const continuePath = join(sliceDir, `${state.activeSlice.id}-CONTINUE.md`);
62
+ await saveFile(continuePath, formatContinue({
63
+ frontmatter: {
64
+ milestone: state.activeMilestone.id,
65
+ slice: state.activeSlice.id,
66
+ task: state.activeTask.id,
67
+ step: 0,
68
+ totalSteps: 0,
69
+ status: "compacted",
70
+ savedAt: new Date().toISOString(),
71
+ },
72
+ completedWork: `Task ${state.activeTask.id} (${state.activeTask.title}) was in progress when compaction occurred.`,
73
+ remainingWork: "Check the task plan for remaining steps.",
74
+ decisions: "Check task summary files for prior decisions.",
75
+ context: "Session was auto-compacted by Pi. Resume with /gsd.",
76
+ nextAction: `Resume task ${state.activeTask.id}: ${state.activeTask.title}.`,
77
+ }));
78
+ });
79
+ pi.on("session_shutdown", async (_event, ctx) => {
80
+ if (isParallelActive()) {
81
+ try {
82
+ await shutdownParallel(process.cwd());
83
+ }
84
+ catch {
85
+ // best-effort
86
+ }
87
+ }
88
+ if (!isAutoActive() && !isAutoPaused())
89
+ return;
90
+ const dash = getAutoDashboardData();
91
+ if (dash.currentUnit) {
92
+ saveActivityLog(ctx, dash.basePath, dash.currentUnit.type, dash.currentUnit.id);
93
+ }
94
+ });
95
+ pi.on("tool_call", async (event) => {
96
+ if (!isToolCallEventType("write", event))
97
+ return;
98
+ const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(), isDepthVerified(), isQueuePhaseActive());
99
+ if (result.block)
100
+ return result;
101
+ });
102
+ pi.on("tool_result", async (event) => {
103
+ if (event.toolName !== "ask_user_questions")
104
+ return;
105
+ const milestoneId = getDiscussionMilestoneId();
106
+ if (!milestoneId)
107
+ return;
108
+ const details = event.details;
109
+ if (details?.cancelled || !details?.response)
110
+ return;
111
+ const questions = event.input?.questions ?? [];
112
+ for (const question of questions) {
113
+ if (typeof question.id === "string" && question.id.includes("depth_verification")) {
114
+ markDepthVerified();
115
+ break;
116
+ }
117
+ }
118
+ const basePath = process.cwd();
119
+ const milestoneDir = resolveMilestonePath(basePath, milestoneId);
120
+ if (!milestoneDir)
121
+ return;
122
+ const discussionPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "DISCUSSION"));
123
+ const timestamp = new Date().toISOString();
124
+ const lines = [`## Exchange — ${timestamp}`, ""];
125
+ for (const question of questions) {
126
+ lines.push(`### ${question.header ?? "Question"}`, "", question.question ?? "");
127
+ if (Array.isArray(question.options)) {
128
+ lines.push("");
129
+ for (const opt of question.options) {
130
+ lines.push(`- **${opt.label}** — ${opt.description ?? ""}`);
131
+ }
132
+ }
133
+ const answer = details.response?.answers?.[question.id];
134
+ if (answer) {
135
+ lines.push("");
136
+ const selected = Array.isArray(answer.selected) ? answer.selected.join(", ") : answer.selected;
137
+ lines.push(`**Selected:** ${selected}`);
138
+ if (answer.notes) {
139
+ lines.push(`**Notes:** ${answer.notes}`);
140
+ }
141
+ }
142
+ lines.push("");
143
+ }
144
+ lines.push("---", "");
145
+ const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
146
+ await saveFile(discussionPath, existing + lines.join("\n"));
147
+ });
148
+ pi.on("tool_execution_start", async (event) => {
149
+ if (!isAutoActive())
150
+ return;
151
+ markToolStart(event.toolCallId);
152
+ });
153
+ pi.on("tool_execution_end", async (event) => {
154
+ markToolEnd(event.toolCallId);
155
+ });
156
+ }
@@ -0,0 +1,46 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { Key, Text } from "@gsd/pi-tui";
4
+ import { GSDDashboardOverlay } from "../dashboard-overlay.js";
5
+ import { shortcutDesc } from "../../shared/mod.js";
6
+ export const GSD_LOGO_LINES = [
7
+ " ██████╗ ███████╗██████╗ ",
8
+ " ██╔════╝ ██╔════╝██╔══██╗",
9
+ " ██║ ███╗███████╗██║ ██║",
10
+ " ██║ ██║╚════██║██║ ██║",
11
+ " ╚██████╔╝███████║██████╔╝",
12
+ " ╚═════╝ ╚══════╝╚═════╝ ",
13
+ ];
14
+ export function registerShortcuts(pi) {
15
+ pi.registerShortcut(Key.ctrlAlt("g"), {
16
+ description: shortcutDesc("Open GSD dashboard", "/gsd status"),
17
+ handler: async (ctx) => {
18
+ if (!existsSync(join(process.cwd(), ".gsd"))) {
19
+ ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
20
+ return;
21
+ }
22
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
23
+ overlay: true,
24
+ overlayOptions: {
25
+ width: "90%",
26
+ minWidth: 80,
27
+ maxHeight: "92%",
28
+ anchor: "center",
29
+ },
30
+ });
31
+ },
32
+ });
33
+ }
34
+ export function maybeRenderGsdHeader(ctx) {
35
+ try {
36
+ const theme = ctx.ui.theme;
37
+ const version = process.env.GSD_VERSION || "0.0.0";
38
+ const logoText = GSD_LOGO_LINES.map((line) => theme.fg("accent", line)).join("\n");
39
+ const titleLine = ` ${theme.bold("Get Shit Done")} ${theme.fg("dim", `v${version}`)}`;
40
+ const headerContent = `${logoText}\n${titleLine}`;
41
+ ctx.ui.setHeader((_ui, _theme) => new Text(headerContent, 1, 0));
42
+ }
43
+ catch {
44
+ // no TUI
45
+ }
46
+ }
@@ -0,0 +1,300 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { debugTime } from "../debug-logger.js";
5
+ import { loadPrompt } from "../prompt-loader.js";
6
+ import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffectiveGSDPreferences } from "../preferences.js";
7
+ import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
8
+ import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
9
+ import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
10
+ import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-command.js";
11
+ import { deriveState } from "../state.js";
12
+ import { formatOverridesSection, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
13
+ import { toPosixPath } from "../../shared/mod.js";
14
+ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.js";
15
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
16
+ function warnDeprecatedAgentInstructions() {
17
+ const paths = [
18
+ join(gsdHome, "agent-instructions.md"),
19
+ join(process.cwd(), ".gsd", "agent-instructions.md"),
20
+ ];
21
+ for (const path of paths) {
22
+ if (existsSync(path)) {
23
+ console.warn(`[GSD] DEPRECATED: ${path} is no longer loaded. ` +
24
+ `Migrate your instructions to AGENTS.md (or CLAUDE.md) in the same directory. ` +
25
+ `See https://github.com/gsd-build/GSD-2/issues/1492`);
26
+ }
27
+ }
28
+ }
29
+ export async function buildBeforeAgentStartResult(event, ctx) {
30
+ if (!existsSync(join(process.cwd(), ".gsd")))
31
+ return undefined;
32
+ const stopContextTimer = debugTime("context-inject");
33
+ const systemContent = loadPrompt("system");
34
+ const loadedPreferences = loadEffectiveGSDPreferences();
35
+ if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
36
+ markCmuxPromptShown();
37
+ ctx.ui.notify("cmux detected. Run /gsd cmux on to enable sidebar metadata, notifications, and visual subagent splits for this project.", "info");
38
+ }
39
+ let preferenceBlock = "";
40
+ if (loadedPreferences) {
41
+ const cwd = process.cwd();
42
+ const report = resolveAllSkillReferences(loadedPreferences.preferences, cwd);
43
+ preferenceBlock = `\n\n${renderPreferencesForSystemPrompt(loadedPreferences.preferences, report.resolutions)}`;
44
+ if (report.warnings.length > 0) {
45
+ ctx.ui.notify(`GSD skill preferences: ${report.warnings.length} unresolved skill${report.warnings.length === 1 ? "" : "s"}: ${report.warnings.join(", ")}`, "warning");
46
+ }
47
+ }
48
+ let knowledgeBlock = "";
49
+ const knowledgePath = resolveGsdRootFile(process.cwd(), "KNOWLEDGE");
50
+ if (existsSync(knowledgePath)) {
51
+ try {
52
+ const content = readFileSync(knowledgePath, "utf-8").trim();
53
+ if (content) {
54
+ knowledgeBlock = `\n\n[PROJECT KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${content}`;
55
+ }
56
+ }
57
+ catch {
58
+ // skip
59
+ }
60
+ }
61
+ let memoryBlock = "";
62
+ try {
63
+ const { formatMemoriesForPrompt, getActiveMemoriesRanked } = await import("../memory-store.js");
64
+ const memories = getActiveMemoriesRanked(30);
65
+ if (memories.length > 0) {
66
+ const formatted = formatMemoriesForPrompt(memories, 2000);
67
+ if (formatted) {
68
+ memoryBlock = `\n\n${formatted}`;
69
+ }
70
+ }
71
+ }
72
+ catch {
73
+ // non-fatal
74
+ }
75
+ let newSkillsBlock = "";
76
+ if (hasSkillSnapshot()) {
77
+ const newSkills = detectNewSkills();
78
+ if (newSkills.length > 0) {
79
+ newSkillsBlock = formatSkillsXml(newSkills);
80
+ }
81
+ }
82
+ warnDeprecatedAgentInstructions();
83
+ const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
84
+ const worktreeBlock = buildWorktreeContextBlock();
85
+ const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
86
+ stopContextTimer({
87
+ systemPromptSize: fullSystem.length,
88
+ injectionSize: injection?.length ?? 0,
89
+ hasPreferences: preferenceBlock.length > 0,
90
+ hasNewSkills: newSkillsBlock.length > 0,
91
+ });
92
+ return {
93
+ systemPrompt: fullSystem,
94
+ ...(injection
95
+ ? {
96
+ message: {
97
+ customType: "gsd-guided-context",
98
+ content: injection,
99
+ display: false,
100
+ },
101
+ }
102
+ : {}),
103
+ };
104
+ }
105
+ function buildWorktreeContextBlock() {
106
+ const worktreeName = getActiveWorktreeName();
107
+ const worktreeMainCwd = getWorktreeOriginalCwd();
108
+ const autoWorktree = getActiveAutoWorktreeContext();
109
+ if (worktreeName && worktreeMainCwd) {
110
+ return [
111
+ "",
112
+ "",
113
+ "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
114
+ `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
115
+ `The actual current working directory is: ${toPosixPath(process.cwd())}`,
116
+ "",
117
+ `You are working inside a GSD worktree.`,
118
+ `- Worktree name: ${worktreeName}`,
119
+ `- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
120
+ `- Main project: ${toPosixPath(worktreeMainCwd)}`,
121
+ `- Branch: worktree/${worktreeName}`,
122
+ "",
123
+ "All file operations, bash commands, and GSD state resolve against the worktree path above.",
124
+ "Use /worktree merge to merge changes back. Use /worktree return to switch back to the main tree.",
125
+ ].join("\n");
126
+ }
127
+ if (autoWorktree) {
128
+ return [
129
+ "",
130
+ "",
131
+ "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
132
+ `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
133
+ `The actual current working directory is: ${toPosixPath(process.cwd())}`,
134
+ "",
135
+ "You are working inside a GSD auto-worktree.",
136
+ `- Milestone worktree: ${autoWorktree.worktreeName}`,
137
+ `- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
138
+ `- Main project: ${toPosixPath(autoWorktree.originalBase)}`,
139
+ `- Branch: ${autoWorktree.branch}`,
140
+ "",
141
+ "All file operations, bash commands, and GSD state resolve against the worktree path above.",
142
+ "Write every .gsd artifact in the worktree path above, never in the main project tree.",
143
+ ].join("\n");
144
+ }
145
+ return "";
146
+ }
147
+ async function buildGuidedExecuteContextInjection(prompt, basePath) {
148
+ const executeMatch = prompt.match(/Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
149
+ if (executeMatch) {
150
+ const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
151
+ return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, taskId, taskTitle);
152
+ }
153
+ const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
154
+ if (resumeMatch) {
155
+ const [, sliceId, milestoneId] = resumeMatch;
156
+ const state = await deriveState(basePath);
157
+ if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
158
+ return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+ async function buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, taskId, taskTitle) {
164
+ const taskPlanPath = resolveTaskFile(basePath, milestoneId, sliceId, taskId, "PLAN");
165
+ const taskPlanRelPath = relTaskFile(basePath, milestoneId, sliceId, taskId, "PLAN");
166
+ const taskPlanContent = taskPlanPath ? await loadFile(taskPlanPath) : null;
167
+ const taskPlanInline = taskPlanContent
168
+ ? ["## Inlined Task Plan (authoritative local execution contract)", `Source: \`${taskPlanRelPath}\``, "", taskPlanContent.trim()].join("\n")
169
+ : ["## Inlined Task Plan (authoritative local execution contract)", `Task plan not found at dispatch time. Read \`${taskPlanRelPath}\` before executing.`].join("\n");
170
+ const slicePlanPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
171
+ const slicePlanRelPath = relSliceFile(basePath, milestoneId, sliceId, "PLAN");
172
+ const slicePlanContent = slicePlanPath ? await loadFile(slicePlanPath) : null;
173
+ const slicePlanExcerpt = extractSliceExecutionExcerpt(slicePlanContent, slicePlanRelPath);
174
+ const priorTaskLines = await buildCarryForwardLines(basePath, milestoneId, sliceId, taskId);
175
+ const resumeSection = await buildResumeSection(basePath, milestoneId, sliceId);
176
+ const activeOverrides = await loadActiveOverrides(basePath);
177
+ const overridesSection = formatOverridesSection(activeOverrides);
178
+ return [
179
+ "[GSD Guided Execute Context]",
180
+ "Use this injected context as startup context for guided task execution. Treat the inlined task plan as the authoritative local execution contract. Use source artifacts to verify details and run checks.",
181
+ overridesSection, "",
182
+ "",
183
+ resumeSection,
184
+ "",
185
+ "## Carry-Forward Context",
186
+ ...priorTaskLines,
187
+ "",
188
+ taskPlanInline,
189
+ "",
190
+ slicePlanExcerpt,
191
+ "",
192
+ "## Backing Source Artifacts",
193
+ `- Slice plan: \`${slicePlanRelPath}\``,
194
+ `- Task plan source: \`${taskPlanRelPath}\``,
195
+ ].join("\n");
196
+ }
197
+ async function buildCarryForwardLines(basePath, milestoneId, sliceId, taskId) {
198
+ const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId);
199
+ if (!tasksDir)
200
+ return ["- No prior task summaries in this slice."];
201
+ const currentNum = parseInt(taskId.replace(/^T/, ""), 10);
202
+ const sliceRel = relSlicePath(basePath, milestoneId, sliceId);
203
+ const summaryFiles = resolveTaskFiles(tasksDir, "SUMMARY")
204
+ .filter((file) => parseInt(file.replace(/^T/, ""), 10) < currentNum)
205
+ .sort();
206
+ if (summaryFiles.length === 0)
207
+ return ["- No prior task summaries in this slice."];
208
+ return Promise.all(summaryFiles.map(async (file) => {
209
+ const absPath = join(tasksDir, file);
210
+ const content = await loadFile(absPath);
211
+ const relPath = `${sliceRel}/tasks/${file}`;
212
+ if (!content)
213
+ return `- \`${relPath}\``;
214
+ const summary = parseSummary(content);
215
+ const provided = summary.frontmatter.provides.slice(0, 2).join("; ");
216
+ const decisions = summary.frontmatter.key_decisions.slice(0, 2).join("; ");
217
+ const patterns = summary.frontmatter.patterns_established.slice(0, 2).join("; ");
218
+ const diagnostics = extractMarkdownSection(content, "Diagnostics");
219
+ const parts = [summary.title || relPath];
220
+ if (summary.oneLiner)
221
+ parts.push(summary.oneLiner);
222
+ if (provided)
223
+ parts.push(`provides: ${provided}`);
224
+ if (decisions)
225
+ parts.push(`decisions: ${decisions}`);
226
+ if (patterns)
227
+ parts.push(`patterns: ${patterns}`);
228
+ if (diagnostics)
229
+ parts.push(`diagnostics: ${oneLine(diagnostics)}`);
230
+ return `- \`${relPath}\` — ${parts.join(" | ")}`;
231
+ }));
232
+ }
233
+ async function buildResumeSection(basePath, milestoneId, sliceId) {
234
+ const continueFile = resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE");
235
+ const legacyDir = resolveSlicePath(basePath, milestoneId, sliceId);
236
+ const legacyPath = legacyDir ? join(legacyDir, "continue.md") : null;
237
+ const continueContent = continueFile ? await loadFile(continueFile) : null;
238
+ const legacyContent = !continueContent && legacyPath ? await loadFile(legacyPath) : null;
239
+ const resolvedContent = continueContent ?? legacyContent;
240
+ const resolvedRelPath = continueContent
241
+ ? relSliceFile(basePath, milestoneId, sliceId, "CONTINUE")
242
+ : (legacyPath ? `${relSlicePath(basePath, milestoneId, sliceId)}/continue.md` : null);
243
+ if (!resolvedContent || !resolvedRelPath) {
244
+ return ["## Resume State", "- No continue file present. Start from the top of the task plan."].join("\n");
245
+ }
246
+ const cont = parseContinue(resolvedContent);
247
+ const lines = [
248
+ "## Resume State",
249
+ `Source: \`${resolvedRelPath}\``,
250
+ `- Status: ${cont.frontmatter.status || "in_progress"}`,
251
+ ];
252
+ if (cont.frontmatter.step && cont.frontmatter.totalSteps) {
253
+ lines.push(`- Progress: step ${cont.frontmatter.step} of ${cont.frontmatter.totalSteps}`);
254
+ }
255
+ if (cont.completedWork)
256
+ lines.push(`- Completed: ${oneLine(cont.completedWork)}`);
257
+ if (cont.remainingWork)
258
+ lines.push(`- Remaining: ${oneLine(cont.remainingWork)}`);
259
+ if (cont.decisions)
260
+ lines.push(`- Decisions: ${oneLine(cont.decisions)}`);
261
+ if (cont.nextAction)
262
+ lines.push(`- Next action: ${oneLine(cont.nextAction)}`);
263
+ return lines.join("\n");
264
+ }
265
+ function extractSliceExecutionExcerpt(content, relPath) {
266
+ if (!content) {
267
+ return ["## Slice Plan Excerpt", `Slice plan not found at dispatch time. Read \`${relPath}\` before running slice-level verification.`].join("\n");
268
+ }
269
+ const lines = content.split("\n");
270
+ const goalLine = lines.find((line) => line.startsWith("**Goal:**"))?.trim();
271
+ const demoLine = lines.find((line) => line.startsWith("**Demo:**"))?.trim();
272
+ const verification = extractMarkdownSection(content, "Verification");
273
+ const observability = extractMarkdownSection(content, "Observability / Diagnostics");
274
+ const parts = ["## Slice Plan Excerpt", `Source: \`${relPath}\``];
275
+ if (goalLine)
276
+ parts.push(goalLine);
277
+ if (demoLine)
278
+ parts.push(demoLine);
279
+ if (verification)
280
+ parts.push("", "### Slice Verification", verification.trim());
281
+ if (observability)
282
+ parts.push("", "### Slice Observability / Diagnostics", observability.trim());
283
+ return parts.join("\n");
284
+ }
285
+ function extractMarkdownSection(content, heading) {
286
+ const match = new RegExp(`^## ${escapeRegExp(heading)}\\s*$`, "m").exec(content);
287
+ if (!match)
288
+ return null;
289
+ const start = match.index + match[0].length;
290
+ const rest = content.slice(start);
291
+ const nextHeading = rest.match(/^##\s+/m);
292
+ const end = nextHeading?.index ?? rest.length;
293
+ return rest.slice(0, end).trim();
294
+ }
295
+ function escapeRegExp(value) {
296
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
297
+ }
298
+ function oneLine(text) {
299
+ return text.replace(/\s+/g, " ").trim();
300
+ }
@@ -0,0 +1,38 @@
1
+ const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
2
+ let depthVerificationDone = false;
3
+ let activeQueuePhase = false;
4
+ export function isDepthVerified() {
5
+ return depthVerificationDone;
6
+ }
7
+ export function isQueuePhaseActive() {
8
+ return activeQueuePhase;
9
+ }
10
+ export function setQueuePhaseActive(active) {
11
+ activeQueuePhase = active;
12
+ }
13
+ export function resetWriteGateState() {
14
+ depthVerificationDone = false;
15
+ }
16
+ export function clearDiscussionFlowState() {
17
+ depthVerificationDone = false;
18
+ activeQueuePhase = false;
19
+ }
20
+ export function markDepthVerified() {
21
+ depthVerificationDone = true;
22
+ }
23
+ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, depthVerified, queuePhaseActive) {
24
+ if (toolName !== "write")
25
+ return { block: false };
26
+ const inDiscussion = milestoneId !== null;
27
+ const inQueue = queuePhaseActive ?? false;
28
+ if (!inDiscussion && !inQueue)
29
+ return { block: false };
30
+ if (!MILESTONE_CONTEXT_RE.test(inputPath))
31
+ return { block: false };
32
+ if (depthVerified)
33
+ return { block: false };
34
+ return {
35
+ block: true,
36
+ reason: `Blocked: Cannot write to milestone CONTEXT.md during discussion phase without depth verification. Call ask_user_questions with question id "depth_verification" first to confirm discussion depth before writing context.`,
37
+ };
38
+ }