gsd-pi 2.37.0 → 2.37.1-dev.3bbb0a9

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 (103) hide show
  1. package/README.md +21 -20
  2. package/dist/onboarding.js +1 -0
  3. package/dist/resources/extensions/cmux/package.json +7 -0
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +67 -1
  5. package/dist/resources/extensions/gsd/auto-loop.js +18 -4
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
  7. package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
  8. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  9. package/dist/resources/extensions/gsd/auto.js +42 -5
  10. package/dist/resources/extensions/gsd/commands.js +80 -33
  11. package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
  12. package/dist/resources/extensions/gsd/files.js +41 -0
  13. package/dist/resources/extensions/gsd/git-service.js +9 -1
  14. package/dist/resources/extensions/gsd/history.js +2 -1
  15. package/dist/resources/extensions/gsd/metrics.js +4 -2
  16. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  17. package/dist/resources/extensions/gsd/preferences-types.js +2 -1
  18. package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
  19. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  20. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
  21. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  22. package/dist/resources/extensions/gsd/session-lock.js +26 -6
  23. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  24. package/dist/resources/extensions/shared/format-utils.js +5 -41
  25. package/dist/resources/extensions/shared/layout-utils.js +46 -0
  26. package/dist/resources/extensions/shared/mod.js +2 -1
  27. package/package.json +2 -1
  28. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  29. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  30. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  31. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  32. package/packages/pi-ai/dist/models.generated.js +172 -0
  33. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  34. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  35. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  36. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  37. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  38. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  39. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  40. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  41. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  42. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  43. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  45. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  46. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  47. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  48. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  49. package/packages/pi-ai/dist/types.d.ts +2 -2
  50. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  51. package/packages/pi-ai/dist/types.js.map +1 -1
  52. package/packages/pi-ai/package.json +1 -0
  53. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  54. package/packages/pi-ai/src/models.generated.ts +172 -0
  55. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  56. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  57. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  58. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  59. package/packages/pi-ai/src/types.ts +2 -0
  60. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
  62. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  65. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  66. package/packages/pi-coding-agent/package.json +1 -1
  67. package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
  68. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  69. package/pkg/package.json +1 -1
  70. package/src/resources/extensions/cmux/package.json +7 -0
  71. package/src/resources/extensions/gsd/auto-dispatch.ts +93 -0
  72. package/src/resources/extensions/gsd/auto-loop.ts +24 -6
  73. package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
  74. package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
  75. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  76. package/src/resources/extensions/gsd/auto.ts +56 -5
  77. package/src/resources/extensions/gsd/commands.ts +85 -31
  78. package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
  79. package/src/resources/extensions/gsd/files.ts +45 -0
  80. package/src/resources/extensions/gsd/git-service.ts +12 -1
  81. package/src/resources/extensions/gsd/history.ts +2 -1
  82. package/src/resources/extensions/gsd/metrics.ts +4 -2
  83. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  84. package/src/resources/extensions/gsd/preferences-types.ts +5 -1
  85. package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
  86. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  87. package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
  88. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  89. package/src/resources/extensions/gsd/session-lock.ts +41 -6
  90. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  91. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +37 -1
  92. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
  93. package/src/resources/extensions/gsd/tests/cmux.test.ts +25 -1
  94. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
  95. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  96. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  97. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  98. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
  99. package/src/resources/extensions/gsd/types.ts +43 -0
  100. package/src/resources/extensions/shared/format-utils.ts +5 -44
  101. package/src/resources/extensions/shared/layout-utils.ts +49 -0
  102. package/src/resources/extensions/shared/mod.ts +7 -4
  103. package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
@@ -12,7 +12,7 @@ import { deriveState } from "./state.js";
12
12
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
13
13
  import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
14
14
  import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
15
- import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote } from "./auto.js";
15
+ import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
16
16
  import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
17
17
  import { resolveProjectRoot } from "./worktree.js";
18
18
  import { assertSafeDirectory } from "./validate-directory.js";
@@ -36,8 +36,8 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
36
36
  import { runEnvironmentChecks } from "./doctor-environment.js";
37
37
  import { handleLogs } from "./commands-logs.js";
38
38
  import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
39
- import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
40
39
  import { handleCmux } from "./commands-cmux.js";
40
+ import { showNextAction } from "../shared/mod.js";
41
41
  /** Resolve the effective project root, accounting for worktree paths. */
42
42
  export function projectRoot() {
43
43
  const cwd = process.cwd();
@@ -57,36 +57,81 @@ export function projectRoot() {
57
57
  return root;
58
58
  }
59
59
  /**
60
- * Check if another process holds the auto-mode session lock.
61
- * Returns the lock data if a remote session is alive, null otherwise.
60
+ * Guard against starting auto-mode when a remote session is already running.
61
+ * Returns true if the caller should proceed with startAuto, false if handled.
62
62
  */
63
- function getRemoteAutoSession(basePath) {
64
- const lockData = readSessionLockData(basePath);
65
- if (!lockData)
66
- return null;
67
- if (lockData.pid === process.pid)
68
- return null;
69
- if (!isSessionLockProcessAlive(lockData))
70
- return null;
71
- return { pid: lockData.pid };
72
- }
73
- /**
74
- * Show a steering menu when auto-mode is running in another process.
75
- * Returns true if a remote session was detected (caller should return early).
76
- */
77
- function notifyRemoteAutoActive(ctx, basePath) {
78
- const remote = getRemoteAutoSession(basePath);
79
- if (!remote)
63
+ async function guardRemoteSession(ctx, pi) {
64
+ // Local session already active — proceed (startAuto handles re-entrant calls)
65
+ if (isAutoActive() || isAutoPaused())
66
+ return true;
67
+ const remote = checkRemoteAutoSession(projectRoot());
68
+ if (!remote.running || !remote.pid)
69
+ return true;
70
+ const unitLabel = remote.unitType && remote.unitId
71
+ ? `${remote.unitType} (${remote.unitId})`
72
+ : "unknown unit";
73
+ const unitsMsg = remote.completedUnits != null
74
+ ? `${remote.completedUnits} units completed`
75
+ : "";
76
+ const choice = await showNextAction(ctx, {
77
+ title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
78
+ summary: [
79
+ `Currently executing: ${unitLabel}`,
80
+ ...(unitsMsg ? [unitsMsg] : []),
81
+ ...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
82
+ ],
83
+ actions: [
84
+ {
85
+ id: "status",
86
+ label: "View status",
87
+ description: "Show the current GSD progress dashboard.",
88
+ recommended: true,
89
+ },
90
+ {
91
+ id: "steer",
92
+ label: "Steer the session",
93
+ description: "Use /gsd steer <instruction> to redirect the running session.",
94
+ },
95
+ {
96
+ id: "stop",
97
+ label: "Stop remote session",
98
+ description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
99
+ },
100
+ {
101
+ id: "force",
102
+ label: "Force start (steal lock)",
103
+ description: "Start a new session, terminating the existing one.",
104
+ },
105
+ ],
106
+ notYetMessage: "Run /gsd when ready.",
107
+ });
108
+ if (choice === "status") {
109
+ await handleStatus(ctx);
110
+ return false;
111
+ }
112
+ if (choice === "steer") {
113
+ ctx.ui.notify("Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
114
+ "Example: /gsd steer Use Postgres instead of SQLite", "info");
115
+ return false;
116
+ }
117
+ if (choice === "stop") {
118
+ const result = stopAutoRemote(projectRoot());
119
+ if (result.found) {
120
+ ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
121
+ }
122
+ else if (result.error) {
123
+ ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
124
+ }
125
+ else {
126
+ ctx.ui.notify("Remote session is no longer running.", "info");
127
+ }
80
128
  return false;
81
- ctx.ui.notify(`Auto-mode is running in another process (PID ${remote.pid}).\n` +
82
- `Use these commands to interact with it:\n` +
83
- ` /gsd status check progress\n` +
84
- ` /gsd discuss — discuss architecture decisions\n` +
85
- ` /gsd queue queue the next milestone\n` +
86
- ` /gsd steer apply an override to active work\n` +
87
- ` /gsd capture — fire-and-forget thought\n` +
88
- ` /gsd stop — stop auto-mode`, "warning");
89
- return true;
129
+ }
130
+ if (choice === "force") {
131
+ return true; // Proceed startAuto will steal the lock
132
+ }
133
+ // "not_yet" or escape
134
+ return false;
90
135
  }
91
136
  export function registerGSDCommand(pi) {
92
137
  pi.registerCommand("gsd", {
@@ -542,12 +587,12 @@ export async function handleGSDCommand(args, ctx, pi) {
542
587
  await handleDryRun(ctx, projectRoot());
543
588
  return;
544
589
  }
545
- if (notifyRemoteAutoActive(ctx, projectRoot()))
546
- return;
547
590
  const verboseMode = trimmed.includes("--verbose");
548
591
  const debugMode = trimmed.includes("--debug");
549
592
  if (debugMode)
550
593
  enableDebug(projectRoot());
594
+ if (!(await guardRemoteSession(ctx, pi)))
595
+ return;
551
596
  await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
552
597
  return;
553
598
  }
@@ -556,6 +601,8 @@ export async function handleGSDCommand(args, ctx, pi) {
556
601
  const debugMode = trimmed.includes("--debug");
557
602
  if (debugMode)
558
603
  enableDebug(projectRoot());
604
+ if (!(await guardRemoteSession(ctx, pi)))
605
+ return;
559
606
  await startAuto(ctx, pi, projectRoot(), verboseMode);
560
607
  return;
561
608
  }
@@ -899,7 +946,7 @@ Examples:
899
946
  return;
900
947
  }
901
948
  if (trimmed === "") {
902
- if (notifyRemoteAutoActive(ctx, projectRoot()))
949
+ if (!(await guardRemoteSession(ctx, pi)))
903
950
  return;
904
951
  await startAuto(ctx, pi, projectRoot(), false, { step: true });
905
952
  return;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { existsSync } from "node:fs";
14
14
  import { AuthStorage } from "@gsd/pi-coding-agent";
15
+ import { getEnvApiKey } from "@gsd/pi-ai";
15
16
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
17
  import { getAuthPath, PROVIDER_REGISTRY } from "./key-manager.js";
17
18
  // ── Model → Provider ID mapping ───────────────────────────────────────────────
@@ -33,6 +34,7 @@ function modelToProviderId(model) {
33
34
  google: "google",
34
35
  anthropic: "anthropic",
35
36
  openai: "openai",
37
+ "github-copilot": "github-copilot",
36
38
  };
37
39
  if (prefixMap[prefix])
38
40
  return prefixMap[prefix];
@@ -108,13 +110,29 @@ function resolveKey(providerId) {
108
110
  // auth.json malformed — fall through to env check
109
111
  }
110
112
  }
111
- // Check environment variable
113
+ // Check environment variable using the authoritative env var resolution
114
+ // (handles multi-var lookups like ANTHROPIC_OAUTH_TOKEN || ANTHROPIC_API_KEY,
115
+ // COPILOT_GITHUB_TOKEN || GH_TOKEN || GITHUB_TOKEN, Vertex ADC, Bedrock, etc.)
116
+ if (getEnvApiKey(providerId)) {
117
+ return { found: true, source: "env", backedOff: false };
118
+ }
119
+ // Fall back to PROVIDER_REGISTRY env var for providers not covered by getEnvApiKey
120
+ // (e.g., search providers like Brave, Tavily; tool providers like Jina, Context7)
112
121
  if (info?.envVar && process.env[info.envVar]) {
113
122
  return { found: true, source: "env", backedOff: false };
114
123
  }
115
124
  return { found: false, source: "none", backedOff: false };
116
125
  }
117
126
  // ── Individual check groups ────────────────────────────────────────────────────
127
+ /**
128
+ * Providers that can serve models normally associated with another provider.
129
+ * Key = the provider whose models can be served, Value = alternative providers to check.
130
+ * e.g. GitHub Copilot subscriptions can access Claude and GPT models.
131
+ */
132
+ const PROVIDER_ROUTES = {
133
+ anthropic: ["github-copilot"],
134
+ openai: ["github-copilot"],
135
+ };
118
136
  function checkLlmProviders() {
119
137
  const required = collectConfiguredModelProviders();
120
138
  const results = [];
@@ -123,6 +141,22 @@ function checkLlmProviders() {
123
141
  const label = info?.label ?? providerId;
124
142
  const lookup = resolveKey(providerId);
125
143
  if (!lookup.found) {
144
+ // Check if a cross-provider can serve this provider's models
145
+ const routes = PROVIDER_ROUTES[providerId];
146
+ const routeProvider = routes?.find(routeId => resolveKey(routeId).found);
147
+ if (routeProvider) {
148
+ const routeInfo = PROVIDER_REGISTRY.find(p => p.id === routeProvider);
149
+ const routeLabel = routeInfo?.label ?? routeProvider;
150
+ results.push({
151
+ name: providerId,
152
+ label,
153
+ category: "llm",
154
+ status: "ok",
155
+ message: `${label} — available via ${routeLabel}`,
156
+ required: true,
157
+ });
158
+ continue;
159
+ }
126
160
  const envVar = info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
127
161
  results.push({
128
162
  name: providerId,
@@ -629,6 +629,47 @@ export function countMustHavesMentionedInSummary(mustHaves, summaryContent) {
629
629
  }
630
630
  return count;
631
631
  }
632
+ // ─── Task Plan IO Extractor ────────────────────────────────────────────────
633
+ /**
634
+ * Extract input and output file paths from a task plan's `## Inputs` and
635
+ * `## Expected Output` sections. Looks for backtick-wrapped file paths on
636
+ * each line (e.g. `` `src/foo.ts` ``).
637
+ *
638
+ * Returns empty arrays for missing/empty sections — callers should treat
639
+ * tasks with no IO as ambiguous (sequential fallback trigger).
640
+ */
641
+ export function parseTaskPlanIO(content) {
642
+ const backtickPathRegex = /`([^`]+)`/g;
643
+ function extractPaths(sectionText) {
644
+ if (!sectionText)
645
+ return [];
646
+ const paths = [];
647
+ for (const line of sectionText.split("\n")) {
648
+ const trimmed = line.trim();
649
+ if (!trimmed || trimmed.startsWith("#"))
650
+ continue;
651
+ let match;
652
+ backtickPathRegex.lastIndex = 0;
653
+ while ((match = backtickPathRegex.exec(trimmed)) !== null) {
654
+ const candidate = match[1];
655
+ // Filter out things that look like code tokens rather than file paths
656
+ // (e.g. `true`, `false`, `npm run test`). A file path has at least one
657
+ // dot or slash.
658
+ if (candidate.includes("/") || candidate.includes(".")) {
659
+ paths.push(candidate);
660
+ }
661
+ }
662
+ }
663
+ return paths;
664
+ }
665
+ const [, body] = splitFrontmatter(content);
666
+ const inputSection = extractSection(body, "Inputs");
667
+ const outputSection = extractSection(body, "Expected Output");
668
+ return {
669
+ inputFiles: extractPaths(inputSection),
670
+ outputFiles: extractPaths(outputSection),
671
+ };
672
+ }
632
673
  /**
633
674
  * Extract the UAT type from a UAT file's raw content.
634
675
  *
@@ -349,10 +349,18 @@ export class GitServiceImpl {
349
349
  }
350
350
  const wtName = detectWorktreeName(this.basePath);
351
351
  if (wtName) {
352
+ // Auto-mode worktrees use milestone/<MID> branches (wtName = milestone ID)
353
+ const milestoneBranch = `milestone/${wtName}`;
354
+ const currentBranch = nativeGetCurrentBranch(this.basePath);
355
+ // If we're on a milestone/<MID> branch, use it (auto-mode case)
356
+ if (currentBranch.startsWith("milestone/")) {
357
+ return currentBranch;
358
+ }
359
+ // Otherwise check for manual worktree branch (worktree/<name>)
352
360
  const wtBranch = `worktree/${wtName}`;
353
361
  if (nativeBranchExists(this.basePath, wtBranch))
354
362
  return wtBranch;
355
- return nativeGetCurrentBranch(this.basePath);
363
+ return currentBranch;
356
364
  }
357
365
  // Repo-level default detection: origin/HEAD → main → master → current branch.
358
366
  // Native path uses libgit2 (single call), fallback spawns multiple git processes.
@@ -1,6 +1,7 @@
1
1
  // GSD Extension — Session History View
2
2
  // Human-readable display of past auto-mode unit executions.
3
- import { formatDuration, padRight, truncateWithEllipsis } from "../shared/format-utils.js";
3
+ import { formatDuration, truncateWithEllipsis } from "../shared/format-utils.js";
4
+ import { padRight } from "../shared/layout-utils.js";
4
5
  import { getLedger, getProjectTotals, formatCost, formatTokenCount, aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk, } from "./metrics.js";
5
6
  /**
6
7
  * Show recent unit execution history with cost, tokens, and duration.
@@ -17,8 +17,10 @@ import { gsdRoot } from "./paths.js";
17
17
  import { getAndClearSkills } from "./skill-telemetry.js";
18
18
  import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
19
19
  import { parseUnitId } from "./unit-id.js";
20
- // Re-export from shared — canonical implementation lives in format-utils.
21
- export { formatTokenCount } from "../shared/mod.js";
20
+ // Re-export from shared — import directly from format-utils to avoid pulling
21
+ // in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
22
+ // outside jiti's alias resolution (e.g. dynamic import in auto-loop reports).
23
+ export { formatTokenCount } from "../shared/format-utils.js";
22
24
  export function classifyUnitPhase(unitType) {
23
25
  switch (unitType) {
24
26
  case "research-milestone":
@@ -209,6 +209,30 @@ export function validateTaskPlanContent(file, content) {
209
209
  }
210
210
  }
211
211
  }
212
+ // Rule: Inputs and Expected Output should contain backtick-wrapped file paths
213
+ const inputsSection = getSection(content, "Inputs", 2);
214
+ const outputSection = getSection(content, "Expected Output", 2);
215
+ const backtickPathPattern = /`[^`]*[./][^`]*`/;
216
+ if (outputSection === null || !backtickPathPattern.test(outputSection)) {
217
+ issues.push({
218
+ severity: "warning",
219
+ scope: "task-plan",
220
+ file,
221
+ ruleId: "missing_output_file_paths",
222
+ message: "Task plan `## Expected Output` is missing or has no backtick-wrapped file paths.",
223
+ suggestion: "List concrete output file paths in backticks (e.g. `src/types.ts`). These are machine-parsed to derive task dependencies.",
224
+ });
225
+ }
226
+ if (inputsSection !== null && inputsSection.trim().length > 0 && !backtickPathPattern.test(inputsSection)) {
227
+ issues.push({
228
+ severity: "info",
229
+ scope: "task-plan",
230
+ file,
231
+ ruleId: "missing_input_file_paths",
232
+ message: "Task plan `## Inputs` has content but no backtick-wrapped file paths.",
233
+ suggestion: "List input file paths in backticks (e.g. `src/config.json`). These are machine-parsed to derive task dependencies.",
234
+ });
235
+ }
212
236
  // ── Observability rules (gated by runtime relevance) ──
213
237
  const relevant = textSuggestsObservabilityRelevant(content);
214
238
  if (!relevant)
@@ -65,11 +65,12 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
65
65
  "compression_strategy",
66
66
  "context_selection",
67
67
  "widget_mode",
68
+ "reactive_execution",
68
69
  ]);
69
70
  /** Canonical list of all dispatch unit types. */
70
71
  export const KNOWN_UNIT_TYPES = [
71
72
  "research-milestone", "plan-milestone", "research-slice", "plan-slice",
72
- "execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
73
+ "execute-task", "reactive-execute", "complete-slice", "replan-slice", "reassess-roadmap",
73
74
  "run-uat", "complete-milestone",
74
75
  ];
75
76
  export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
@@ -500,6 +500,48 @@ export function validatePreferences(preferences) {
500
500
  validated.parallel = parallel;
501
501
  }
502
502
  }
503
+ // ─── Reactive Execution ─────────────────────────────────────────────────
504
+ if (preferences.reactive_execution !== undefined) {
505
+ if (typeof preferences.reactive_execution === "object" && preferences.reactive_execution !== null) {
506
+ const re = preferences.reactive_execution;
507
+ const validRe = {};
508
+ if (re.enabled !== undefined) {
509
+ if (typeof re.enabled === "boolean")
510
+ validRe.enabled = re.enabled;
511
+ else
512
+ errors.push("reactive_execution.enabled must be a boolean");
513
+ }
514
+ if (re.max_parallel !== undefined) {
515
+ const mp = typeof re.max_parallel === "number" ? re.max_parallel : Number(re.max_parallel);
516
+ if (Number.isFinite(mp) && mp >= 1 && mp <= 8) {
517
+ validRe.max_parallel = Math.floor(mp);
518
+ }
519
+ else {
520
+ errors.push("reactive_execution.max_parallel must be a number between 1 and 8");
521
+ }
522
+ }
523
+ if (re.isolation_mode !== undefined) {
524
+ if (re.isolation_mode === "same-tree") {
525
+ validRe.isolation_mode = "same-tree";
526
+ }
527
+ else {
528
+ errors.push('reactive_execution.isolation_mode must be "same-tree"');
529
+ }
530
+ }
531
+ const knownReKeys = new Set(["enabled", "max_parallel", "isolation_mode"]);
532
+ for (const key of Object.keys(re)) {
533
+ if (!knownReKeys.has(key)) {
534
+ warnings.push(`unknown reactive_execution key "${key}" — ignored`);
535
+ }
536
+ }
537
+ if (Object.keys(validRe).length > 0) {
538
+ validated.reactive_execution = validRe;
539
+ }
540
+ }
541
+ else {
542
+ errors.push("reactive_execution must be an object");
543
+ }
544
+ }
503
545
  // ─── Verification Preferences ───────────────────────────────────────────
504
546
  if (preferences.verification_commands !== undefined) {
505
547
  if (Array.isArray(preferences.verification_commands)) {
@@ -61,13 +61,14 @@ Then:
61
61
  - a concrete, action-oriented title
62
62
  - the inline task entry fields defined in the plan.md template (Why / Files / Do / Verify / Done when)
63
63
  - a matching task plan file with description, steps, must-haves, verification, inputs, and expected output
64
+ - **Inputs and Expected Output must list concrete backtick-wrapped file paths** (e.g. `` `src/types.ts` ``). These are machine-parsed to derive task dependencies — vague prose without paths breaks parallel execution. Every task must have at least one output file path.
64
65
  - Observability Impact section **only if the task touches runtime boundaries, async flows, or error paths** — omit it otherwise
65
66
  6. Write `{{outputPath}}`
66
67
  7. Write individual task plans in `{{slicePath}}/tasks/`: `T01-PLAN.md`, `T02-PLAN.md`, etc.
67
68
  8. **Self-audit the plan.** Walk through each check — if any fail, fix the plan files before moving on:
68
69
  - **Completion semantics:** If every task were completed exactly as written, the slice goal/demo should actually be true.
69
70
  - **Requirement coverage:** Every must-have in the slice maps to at least one task. No must-have is orphaned. If `REQUIREMENTS.md` exists, every Active requirement this slice owns maps to at least one task.
70
- - **Task completeness:** Every task has steps, must-haves, verification, inputs, and expected output — none are blank or vague.
71
+ - **Task completeness:** Every task has steps, must-haves, verification, inputs, and expected output — none are blank or vague. Inputs and Expected Output list backtick-wrapped file paths, not prose descriptions.
71
72
  - **Dependency correctness:** Task ordering is consistent. No task references work from a later task.
72
73
  - **Key links planned:** For every pair of artifacts that must connect, there is an explicit step that wires them.
73
74
  - **Scope sanity:** Target 2–5 steps and 3–8 files per task. 10+ steps or 12+ files — must split. Each task must be completable in a single fresh context window.
@@ -0,0 +1,41 @@
1
+ # Reactive Task Execution — Parallel Dispatch
2
+
3
+ **Working directory:** `{{workingDirectory}}`
4
+ **Milestone:** {{milestoneId}} — {{milestoneTitle}}
5
+ **Slice:** {{sliceId}} — {{sliceTitle}}
6
+
7
+ ## Mission
8
+
9
+ You are executing **multiple tasks in parallel** for this slice. The task graph below shows which tasks are ready for simultaneous execution based on their input/output dependencies.
10
+
11
+ **Critical rule:** Use the `subagent` tool in **parallel mode** to dispatch all ready tasks simultaneously. Each subagent gets a self-contained execute-task prompt. After all subagents return, verify each task's outputs and write summaries.
12
+
13
+ ## Task Dependency Graph
14
+
15
+ {{graphContext}}
16
+
17
+ ## Ready Tasks for Parallel Dispatch
18
+
19
+ {{readyTaskCount}} tasks are ready for parallel execution:
20
+
21
+ {{readyTaskList}}
22
+
23
+ ## Execution Protocol
24
+
25
+ 1. **Dispatch all ready tasks** using `subagent` in parallel mode. Each subagent prompt is provided below.
26
+ 2. **Wait for all subagents** to complete.
27
+ 3. **Verify each task's outputs** — check that expected files were created/modified and that verification commands pass.
28
+ 4. **Write task summaries** for each completed task using the task-summary template.
29
+ 5. **Mark completed tasks** as done in the slice plan (checkbox `[x]`).
30
+ 6. **Commit** all changes with a clear message covering the parallel batch.
31
+
32
+ If any subagent fails:
33
+ - Write a summary for the failed task with `blocker_discovered: true`
34
+ - Continue marking the successful tasks as done
35
+ - The orchestrator will handle re-dispatch on the next iteration
36
+
37
+ ## Subagent Prompts
38
+
39
+ {{subagentPrompts}}
40
+
41
+ {{inlinedTemplates}}