ultimate-pi 0.17.0 → 0.18.1

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 (137) hide show
  1. package/.agents/skills/harness-context/SKILL.md +13 -6
  2. package/.agents/skills/harness-debate-plan/SKILL.md +37 -20
  3. package/.agents/skills/harness-decisions/SKILL.md +1 -1
  4. package/.agents/skills/harness-eval/SKILL.md +6 -21
  5. package/.agents/skills/harness-governor/SKILL.md +4 -3
  6. package/.agents/skills/harness-orchestration/SKILL.md +41 -53
  7. package/.agents/skills/harness-plan/SKILL.md +23 -12
  8. package/.agents/skills/harness-review/SKILL.md +52 -0
  9. package/.agents/skills/harness-sentrux-setup/SKILL.md +16 -3
  10. package/.agents/skills/harness-steer/SKILL.md +14 -0
  11. package/.agents/skills/sentrux/SKILL.md +9 -9
  12. package/.pi/agents/harness/planning/decompose.md +7 -4
  13. package/.pi/agents/harness/planning/hypothesis-validator.md +2 -0
  14. package/.pi/agents/harness/planning/hypothesis.md +3 -1
  15. package/.pi/agents/harness/planning/plan-adversary.md +2 -0
  16. package/.pi/agents/harness/planning/plan-evaluator.md +2 -0
  17. package/.pi/agents/harness/planning/plan-synthesizer.md +25 -0
  18. package/.pi/agents/harness/planning/planning-context.md +48 -0
  19. package/.pi/agents/harness/planning/review-integrator.md +2 -0
  20. package/.pi/agents/harness/planning/sprint-contract-auditor.md +2 -0
  21. package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +3 -10
  22. package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +3 -12
  23. package/.pi/agents/harness/running/executor.md +45 -0
  24. package/.pi/agents/harness/sentrux-steward.md +51 -0
  25. package/.pi/extensions/00-harness-project-control.ts +133 -0
  26. package/.pi/extensions/00-posthog-network-bootstrap.ts +11 -0
  27. package/.pi/extensions/budget-guard.ts +2 -0
  28. package/.pi/extensions/debate-orchestrator.ts +2 -0
  29. package/.pi/extensions/harness-ask-user.ts +2 -2
  30. package/.pi/extensions/harness-debate-tools.ts +2 -2
  31. package/.pi/extensions/harness-live-widget.ts +60 -3
  32. package/.pi/extensions/harness-plan-approval.ts +64 -58
  33. package/.pi/extensions/harness-run-context.ts +715 -90
  34. package/.pi/extensions/harness-subagent-submit.ts +46 -12
  35. package/.pi/extensions/harness-subagents.ts +2 -2
  36. package/.pi/extensions/harness-telemetry.ts +2 -0
  37. package/.pi/extensions/harness-web-tools.ts +2 -2
  38. package/.pi/extensions/lib/extension-load-guard.ts +10 -0
  39. package/.pi/extensions/lib/harness-artifact-gate.ts +172 -0
  40. package/.pi/extensions/lib/harness-posthog.ts +9 -5
  41. package/.pi/extensions/lib/harness-spawn-topology.ts +165 -0
  42. package/.pi/extensions/lib/harness-subagent-auth.ts +1 -2
  43. package/.pi/extensions/lib/harness-subagent-policy.ts +28 -24
  44. package/.pi/extensions/lib/harness-subagent-precheck.ts +36 -10
  45. package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +66 -2
  46. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +22 -22
  47. package/.pi/extensions/lib/harness-subagents-bridge.ts +7 -29
  48. package/.pi/extensions/lib/harness-subprocess-bootstrap.ts +73 -0
  49. package/.pi/extensions/lib/plan-approval/create-plan.ts +2 -3
  50. package/.pi/extensions/lib/plan-approval/resolve-disk.ts +102 -0
  51. package/.pi/extensions/lib/plan-approval/schema.ts +22 -8
  52. package/.pi/extensions/lib/plan-approval/types.ts +1 -1
  53. package/.pi/extensions/lib/plan-approval/validate.ts +2 -2
  54. package/.pi/extensions/lib/plan-approval-readiness.ts +192 -0
  55. package/.pi/extensions/lib/plan-debate-eligibility.ts +12 -5
  56. package/.pi/extensions/lib/plan-debate-gate.ts +22 -1
  57. package/.pi/extensions/lib/plan-debate-lanes.ts +32 -2
  58. package/.pi/extensions/lib/plan-review-gate.ts +8 -0
  59. package/.pi/extensions/lib/posthog-client.ts +76 -0
  60. package/.pi/extensions/lib/spawn-policy.ts +3 -3
  61. package/.pi/extensions/observation-bus.ts +2 -0
  62. package/.pi/extensions/policy-gate.ts +26 -19
  63. package/.pi/extensions/review-integrity.ts +91 -10
  64. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  65. package/.pi/extensions/test-diff-integrity.ts +1 -0
  66. package/.pi/extensions/trace-recorder.ts +2 -0
  67. package/.pi/harness/agents.manifest.json +37 -37
  68. package/.pi/harness/corpus/cron.example +8 -0
  69. package/.pi/harness/corpus/graphify-kb-updater.config.json +214 -0
  70. package/.pi/harness/corpus/systemd/graphify-kb-updater.env.template +4 -0
  71. package/.pi/harness/corpus/systemd/graphify-kb-updater.service +17 -0
  72. package/.pi/harness/corpus/systemd/graphify-kb-updater.timer +11 -0
  73. package/.pi/harness/docs/adrs/0001-harness-constitution.md +2 -1
  74. package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +8 -6
  75. package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +6 -1
  76. package/.pi/harness/docs/adrs/0031-harness-run-context.md +1 -1
  77. package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +7 -0
  78. package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +3 -3
  79. package/.pi/harness/docs/adrs/0036-implementation-research-and-selective-debate.md +8 -5
  80. package/.pi/harness/docs/adrs/0039-harness-post-run-review-gate.md +47 -0
  81. package/.pi/harness/docs/adrs/0040-practice-grounded-orchestration.md +40 -0
  82. package/.pi/harness/docs/adrs/0041-intelligent-planning-reconnaissance.md +39 -0
  83. package/.pi/harness/docs/adrs/0042-agent-native-orchestration.md +35 -0
  84. package/.pi/harness/docs/adrs/0043-path-first-harness-tools.md +38 -0
  85. package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +37 -0
  86. package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
  87. package/.pi/harness/docs/adrs/README.md +11 -0
  88. package/.pi/harness/docs/graphify-kb-updater-runbook.md +163 -0
  89. package/.pi/harness/docs/practice-map.md +110 -0
  90. package/.pi/harness/env.harness.template +5 -3
  91. package/.pi/harness/evals/smoke/sentrux-stub.json +1 -1
  92. package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +5 -2
  93. package/.pi/harness/specs/README.md +1 -1
  94. package/.pi/harness/specs/harness-run-context.schema.json +11 -0
  95. package/.pi/harness/specs/harness-spawn-context.schema.json +15 -1
  96. package/.pi/harness/specs/plan-execution-plan.schema.json +39 -1
  97. package/.pi/harness/specs/plan-packet.schema.json +4 -0
  98. package/.pi/harness/specs/plan-phase-status.schema.json +17 -0
  99. package/.pi/harness/specs/plan-phase-waiver.schema.json +25 -0
  100. package/.pi/harness/specs/plan-planning-context.schema.json +50 -0
  101. package/.pi/harness/specs/repair-brief.schema.json +45 -0
  102. package/.pi/harness/specs/review-outcome.schema.json +46 -0
  103. package/.pi/harness/specs/sentrux-manifest-proposal.schema.json +80 -0
  104. package/.pi/harness/specs/sentrux-signal.schema.json +43 -0
  105. package/.pi/harness/specs/steer-state.schema.json +20 -0
  106. package/.pi/lib/harness-context-mode-policy.ts +256 -0
  107. package/.pi/lib/harness-project-config.ts +91 -0
  108. package/.pi/lib/harness-repair-brief.ts +145 -0
  109. package/.pi/lib/harness-run-context.ts +591 -32
  110. package/.pi/lib/harness-ui-state.ts +114 -21
  111. package/.pi/prompts/harness-auto.md +10 -10
  112. package/.pi/prompts/harness-critic.md +3 -30
  113. package/.pi/prompts/harness-eval.md +4 -37
  114. package/.pi/prompts/harness-plan.md +116 -54
  115. package/.pi/prompts/harness-review.md +150 -15
  116. package/.pi/prompts/harness-run.md +62 -10
  117. package/.pi/prompts/harness-sentrux-steward.md +55 -0
  118. package/.pi/prompts/harness-setup.md +5 -4
  119. package/.pi/prompts/harness-steer.md +30 -0
  120. package/.pi/scripts/README.md +1 -0
  121. package/.pi/scripts/graphify-kb-updater.mjs +398 -0
  122. package/.pi/scripts/harness-agents-manifest.mjs +1 -1
  123. package/.pi/scripts/harness-project-toggle.mjs +129 -0
  124. package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
  125. package/.pi/scripts/harness-verify.mjs +22 -6
  126. package/.pi/scripts/harness-web-policy-guard.mjs +68 -0
  127. package/.pi/scripts/validate-plan-dag.mjs +3 -3
  128. package/AGENTS.md +1 -0
  129. package/CHANGELOG.md +23 -0
  130. package/README.md +94 -58
  131. package/package.json +5 -4
  132. package/.pi/agents/harness/executor.md +0 -47
  133. package/.pi/agents/harness/planning/scout-graphify.md +0 -37
  134. package/.pi/agents/harness/planning/scout-semantic.md +0 -39
  135. package/.pi/agents/harness/planning/scout-structure.md +0 -35
  136. package/.pi/prompts/git-sync.md +0 -124
  137. /package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -0
@@ -6,8 +6,11 @@ import {
6
6
  type AgentConfig,
7
7
  agentAllowsMutatingTools,
8
8
  } from "../../../vendor/pi-subagents/src/agents.js";
9
- import type { HarnessPhase } from "../../lib/harness-run-context.js";
10
- import { inferHarnessPhase } from "../../lib/harness-run-context.js";
9
+ import {
10
+ type HarnessPhase,
11
+ inferHarnessPhase,
12
+ } from "../../lib/harness-run-context.js";
13
+ import { validateHarnessSpawnTopology } from "./harness-spawn-topology.js";
11
14
  import { classifyHarnessAgent } from "./harness-subagent-policy.js";
12
15
 
13
16
  export interface SubagentTaskRef {
@@ -19,6 +22,11 @@ export interface PrecheckResult {
19
22
  message?: string;
20
23
  }
21
24
 
25
+ export interface PrecheckOptions {
26
+ projectRoot?: string;
27
+ runId?: string | null;
28
+ }
29
+
22
30
  function collectAgents(params: {
23
31
  agent?: string;
24
32
  tasks?: SubagentTaskRef[];
@@ -40,7 +48,7 @@ function resolveAgent(
40
48
  return agents.find((a) => a.name === name);
41
49
  }
42
50
 
43
- export function precheckHarnessSubagentSpawn(
51
+ export async function precheckHarnessSubagentSpawn(
44
52
  params: {
45
53
  agent?: string;
46
54
  tasks?: SubagentTaskRef[];
@@ -49,13 +57,14 @@ export function precheckHarnessSubagentSpawn(
49
57
  },
50
58
  agents: AgentConfig[],
51
59
  phase: HarnessPhase,
52
- ): PrecheckResult {
60
+ opts?: PrecheckOptions,
61
+ ): Promise<PrecheckResult> {
53
62
  const names = collectAgents(params);
54
63
  const mutating = names.filter((n) => {
55
64
  const cfg = resolveAgent(agents, n);
56
65
  return cfg
57
66
  ? agentAllowsMutatingTools(cfg)
58
- : n.startsWith("harness/executor");
67
+ : n.startsWith("harness/running/");
59
68
  });
60
69
 
61
70
  if (phase === "plan" && mutating.length > 0) {
@@ -67,7 +76,17 @@ export function precheckHarnessSubagentSpawn(
67
76
  };
68
77
  }
69
78
 
70
- if ((params.tasks?.length ?? 0) > 1 && mutating.length > 1) {
79
+ const parallelEvalAdversary =
80
+ (params.tasks?.length ?? 0) === 2 &&
81
+ params.tasks?.some((t) => t.agent === "harness/reviewing/evaluator") &&
82
+ params.tasks?.some((t) => t.agent === "harness/reviewing/adversary") &&
83
+ phase === "evaluate";
84
+
85
+ if (
86
+ (params.tasks?.length ?? 0) > 1 &&
87
+ mutating.length > 1 &&
88
+ !parallelEvalAdversary
89
+ ) {
71
90
  return {
72
91
  ok: false,
73
92
  message:
@@ -76,12 +95,19 @@ export function precheckHarnessSubagentSpawn(
76
95
  };
77
96
  }
78
97
 
98
+ const parallelTaskCount = params.tasks?.length ?? (params.agent ? 1 : 0);
99
+ const topology = await validateHarnessSpawnTopology(names, phase, {
100
+ parallelTaskCount,
101
+ projectRoot: opts?.projectRoot,
102
+ runId: opts?.runId,
103
+ });
104
+ if (!topology.ok) {
105
+ return topology;
106
+ }
107
+
79
108
  for (const name of names) {
80
109
  if (!name.startsWith("harness/")) continue;
81
- const kind = classifyHarnessAgent(name);
82
- if (kind === "planner" && phase !== "plan") {
83
- // allowed — planning agents can run in plan only ideally
84
- }
110
+ classifyHarnessAgent(name);
85
111
  }
86
112
 
87
113
  return { ok: true };
@@ -2,8 +2,8 @@
2
2
  * Shared write pipeline for harness subagent submit tools.
3
3
  */
4
4
 
5
- import { mkdir } from "node:fs/promises";
6
- import { dirname, join } from "node:path";
5
+ import { mkdir, readFile } from "node:fs/promises";
6
+ import { dirname, join, resolve } from "node:path";
7
7
  import { validateAgainstHarnessSchema } from "../../lib/harness-schema-validate.js";
8
8
  import { resolveGuardedRunDir } from "../../lib/harness-subagent-submit-path.js";
9
9
  import { writeYamlFile } from "../../lib/harness-yaml.js";
@@ -24,6 +24,54 @@ export interface SubmitPipelineResult {
24
24
  human_required?: boolean;
25
25
  }
26
26
 
27
+ export async function loadSubmitDocument(opts: {
28
+ projectRoot: string;
29
+ runDir: string;
30
+ document?: Record<string, unknown>;
31
+ source_path?: string;
32
+ }): Promise<
33
+ | { ok: true; document: Record<string, unknown> }
34
+ | { ok: false; validation_errors: string[] }
35
+ > {
36
+ if (opts.document && typeof opts.document === "object") {
37
+ return { ok: true, document: opts.document };
38
+ }
39
+ const rel = opts.source_path?.trim();
40
+ if (!rel) {
41
+ return {
42
+ ok: false,
43
+ validation_errors: ["submit_* requires document or source_path"],
44
+ };
45
+ }
46
+ const abs = resolve(opts.runDir, rel.replace(/^\//, ""));
47
+ if (!abs.startsWith(resolve(opts.runDir))) {
48
+ return {
49
+ ok: false,
50
+ validation_errors: [
51
+ "source_path must stay under the active run directory",
52
+ ],
53
+ };
54
+ }
55
+ try {
56
+ const raw = await readFile(abs, "utf-8");
57
+ const { parse } = await import("yaml");
58
+ const doc = parse(raw) as Record<string, unknown>;
59
+ if (!doc || typeof doc !== "object") {
60
+ return {
61
+ ok: false,
62
+ validation_errors: ["source_path did not parse to an object"],
63
+ };
64
+ }
65
+ return { ok: true, document: doc };
66
+ } catch (e) {
67
+ const msg = e instanceof Error ? e.message : String(e);
68
+ return {
69
+ ok: false,
70
+ validation_errors: [`source_path read failed: ${msg}`],
71
+ };
72
+ }
73
+ }
74
+
27
75
  export async function executeSubmitPipeline(opts: {
28
76
  projectRoot: string;
29
77
  specsDir: string;
@@ -56,6 +104,22 @@ export async function executeSubmitPipeline(opts: {
56
104
  await mkdir(dirname(absPath), { recursive: true });
57
105
  await writeYamlFile(absPath, opts.document);
58
106
 
107
+ if (opts.spec.toolName === "submit_executor_handoff") {
108
+ const rollback = opts.document.rollback_refs;
109
+ if (rollback && typeof rollback === "object" && !Array.isArray(rollback)) {
110
+ const rollbackPath = join(
111
+ runResolved.runDir,
112
+ "artifacts",
113
+ "executor-rollback.yaml",
114
+ );
115
+ await mkdir(dirname(rollbackPath), { recursive: true });
116
+ await writeYamlFile(rollbackPath, {
117
+ schema_version: "1.0.0",
118
+ ...(rollback as Record<string, unknown>),
119
+ });
120
+ }
121
+ }
122
+
59
123
  let laneResult: ApplyDebateLaneResult | undefined;
60
124
  if (opts.spec.debateLane) {
61
125
  laneResult = await applyDebateLaneFromDoc({
@@ -23,32 +23,23 @@ function roundPath(prefix: string, doc: Record<string, unknown>): string {
23
23
 
24
24
  export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
25
25
  {
26
- toolName: "submit_scout_findings",
27
- agents: [
28
- "harness/planning/scout-graphify",
29
- "harness/planning/scout-structure",
30
- "harness/planning/scout-semantic",
31
- ],
32
- schemaFile: "plan-scout-findings.schema.json",
33
- artifactPath: (doc) => {
34
- const lane =
35
- typeof doc.lane === "string"
36
- ? doc.lane
37
- : typeof doc.scout_lane === "string"
38
- ? doc.scout_lane
39
- : "graphify";
40
- return `artifacts/scout-${lane}.yaml`;
41
- },
26
+ toolName: "submit_planning_context",
27
+ agents: ["harness/planning/planning-context"],
28
+ schemaFile: "plan-planning-context.schema.json",
29
+ artifactPath: "artifacts/planning-context.yaml",
42
30
  },
43
31
  {
44
32
  toolName: "submit_decomposition_brief",
45
- agents: ["harness/planning/decompose"],
33
+ agents: ["harness/planning/decompose", "harness/planning/plan-synthesizer"],
46
34
  schemaFile: "plan-decomposition-brief.schema.json",
47
35
  artifactPath: "artifacts/decomposition.yaml",
48
36
  },
49
37
  {
50
38
  toolName: "submit_hypothesis_brief",
51
- agents: ["harness/planning/hypothesis"],
39
+ agents: [
40
+ "harness/planning/hypothesis",
41
+ "harness/planning/plan-synthesizer",
42
+ ],
52
43
  schemaFile: "plan-hypothesis-brief.schema.json",
53
44
  artifactPath: "artifacts/hypothesis.yaml",
54
45
  },
@@ -66,7 +57,10 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
66
57
  },
67
58
  {
68
59
  toolName: "submit_execution_plan_brief",
69
- agents: ["harness/planning/execution-plan-author"],
60
+ agents: [
61
+ "harness/planning/execution-plan-author",
62
+ "harness/planning/plan-synthesizer",
63
+ ],
70
64
  schemaFile: "plan-execution-plan-brief.schema.json",
71
65
  artifactPath: "artifacts/execution-plan-draft.yaml",
72
66
  },
@@ -106,19 +100,19 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
106
100
  },
107
101
  {
108
102
  toolName: "submit_executor_handoff",
109
- agents: ["harness/executor"],
103
+ agents: ["harness/running/executor"],
110
104
  schemaFile: "harness-executor-handoff.schema.json",
111
105
  artifactPath: "handoff/executor-summary.yaml",
112
106
  },
113
107
  {
114
108
  toolName: "submit_eval_verdict",
115
- agents: ["harness/evaluator"],
109
+ agents: ["harness/reviewing/evaluator"],
116
110
  schemaFile: "eval-verdict.schema.json",
117
111
  artifactPath: "artifacts/eval-verdict.yaml",
118
112
  },
119
113
  {
120
114
  toolName: "submit_adversary_report",
121
- agents: ["harness/adversary"],
115
+ agents: ["harness/reviewing/adversary"],
122
116
  schemaFile: "adversary-report.schema.json",
123
117
  artifactPath: "artifacts/adversary-report.yaml",
124
118
  },
@@ -129,6 +123,12 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
129
123
  artifactPath: "artifacts/human-required.yaml",
130
124
  humanRequired: true,
131
125
  },
126
+ {
127
+ toolName: "submit_sentrux_manifest_proposal",
128
+ agents: ["harness/sentrux-steward"],
129
+ schemaFile: "sentrux-manifest-proposal.schema.json",
130
+ artifactPath: "artifacts/sentrux-manifest-proposal.yaml",
131
+ },
132
132
  ] as const;
133
133
 
134
134
  export const SUBMIT_TOOLS_BY_AGENT: Readonly<
@@ -118,32 +118,6 @@ export function createHarnessSubagentsExtension(
118
118
  resolveSubprocessEnv: (task, agent) => {
119
119
  if (!agent.name.startsWith("harness/")) return undefined;
120
120
  const ctx = parseSpawnContextFromTask(task);
121
- // #region agent log
122
- fetch(
123
- "http://127.0.0.1:7928/ingest/a5d40896-34cb-4f12-97db-df7ada0b22f0",
124
- {
125
- method: "POST",
126
- headers: {
127
- "Content-Type": "application/json",
128
- "X-Debug-Session-Id": "2ca12b",
129
- },
130
- body: JSON.stringify({
131
- sessionId: "2ca12b",
132
- hypothesisId: "H1",
133
- location: "harness-subagents-bridge.ts:resolveSubprocessEnv",
134
- message: "parsed spawn context for subprocess env",
135
- data: {
136
- agent: agent.name,
137
- hasCtx: Boolean(ctx?.run_id),
138
- run_id: ctx?.run_id ?? null,
139
- run_dir: ctx?.run_dir ?? null,
140
- taskPrefix: task.slice(0, 160),
141
- },
142
- timestamp: Date.now(),
143
- }),
144
- },
145
- ).catch(() => {});
146
- // #endregion
147
121
  if (!ctx?.run_id) return undefined;
148
122
  return {
149
123
  HARNESS_RUN_ID: ctx.run_id,
@@ -168,11 +142,16 @@ export function createHarnessSubagentsExtension(
168
142
  return { ok: false, message: budget.message };
169
143
  }
170
144
  const entries = ctx.sessionManager.getEntries();
171
- const phase = inferPhaseForPrecheck(ctx.sessionManager.getEntries());
172
- const pre = precheckHarnessSubagentSpawn(
145
+ const runCtx = getLatestRunContext(entries);
146
+ const phase = inferPhaseForPrecheck(entries);
147
+ const pre = await precheckHarnessSubagentSpawn(
173
148
  params as Parameters<typeof precheckHarnessSubagentSpawn>[0],
174
149
  agents,
175
150
  phase,
151
+ {
152
+ projectRoot: ctx.cwd,
153
+ runId: runCtx?.run_id ?? null,
154
+ },
176
155
  );
177
156
  if (!pre.ok) {
178
157
  return { ok: false, message: pre.message };
@@ -185,7 +164,6 @@ export function createHarnessSubagentsExtension(
185
164
  return { ok: false, message: refreshMsg };
186
165
  }
187
166
  }
188
- const runCtx = getLatestRunContext(entries);
189
167
  const runId =
190
168
  runCtx?.run_id ??
191
169
  getRunIdFromSession(entries, lastSessionId) ??
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Seed harness-run-context + policy-gate session entries in subagent subprocesses.
3
+ */
4
+
5
+ import type {
6
+ ExtensionAPI,
7
+ ExtensionContext,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import {
10
+ getLatestRunContext,
11
+ type HarnessRunContext,
12
+ isHarnessSubprocess,
13
+ loadRunContextForSubprocess,
14
+ nowIso,
15
+ policyBootstrapFromRunContext,
16
+ } from "../../lib/harness-run-context.js";
17
+
18
+ type PolicyState = {
19
+ phase: "plan" | "execute" | "evaluate" | "adversary" | "merge";
20
+ approvedPlan: boolean;
21
+ planId: string | null;
22
+ budgetBypass: boolean;
23
+ aborted: boolean;
24
+ abortReason: string | null;
25
+ abortedAt: string | null;
26
+ updatedAt: string;
27
+ };
28
+
29
+ function defaultPolicyState(): PolicyState {
30
+ return {
31
+ phase: "plan",
32
+ approvedPlan: false,
33
+ planId: null,
34
+ budgetBypass: false,
35
+ aborted: false,
36
+ abortReason: null,
37
+ abortedAt: null,
38
+ updatedAt: nowIso(),
39
+ };
40
+ }
41
+
42
+ /** Append disk-backed run + policy entries when subprocess has no session context yet. */
43
+ export async function bootstrapHarnessSubprocessFromEnv(
44
+ pi: ExtensionAPI,
45
+ ctx: ExtensionContext,
46
+ ): Promise<HarnessRunContext | null> {
47
+ if (!isHarnessSubprocess()) return null;
48
+ const entries = ctx.sessionManager.getEntries();
49
+ if (getLatestRunContext(entries)) return getLatestRunContext(entries);
50
+
51
+ const projectRoot = ctx.cwd;
52
+ const sessionId = ctx.sessionManager.getSessionId();
53
+ const disk = await loadRunContextForSubprocess(projectRoot);
54
+ if (!disk?.plan_ready) return null;
55
+
56
+ const runCtx: HarnessRunContext = {
57
+ ...disk,
58
+ pi_session_id: sessionId,
59
+ };
60
+ pi.appendEntry("harness-run-context", runCtx);
61
+
62
+ const boot = policyBootstrapFromRunContext(runCtx);
63
+ const policy: PolicyState = {
64
+ ...defaultPolicyState(),
65
+ phase: boot.phase,
66
+ approvedPlan: boot.approvedPlan,
67
+ planId: boot.planId,
68
+ updatedAt: nowIso(),
69
+ };
70
+ pi.appendEntry("harness-policy-state", policy);
71
+
72
+ return runCtx;
73
+ }
@@ -12,12 +12,11 @@ import {
12
12
  import { writeYamlFile } from "../../../lib/harness-yaml.js";
13
13
  import { writePlanReviewMarkdown } from "./plan-review.js";
14
14
 
15
- export const CREATE_PLAN_SNIPPET =
16
- "create_plan({ plan_packet: { ...approved PlanPacket } })";
15
+ export const CREATE_PLAN_SNIPPET = "create_plan()";
17
16
 
18
17
  export const CREATE_PLAN_GUIDELINES = [
19
18
  "Call create_plan only after the user approves via approve_plan (Approve selection).",
20
- "Pass the same plan_packet you showed in approve_plan — path is resolved automatically.",
19
+ "Uses plan-packet.yaml on disk at plan_packet_path (path-first; no inline packet).",
21
20
  "Never use write or edit for plan-packet.yaml; create_plan is the only allowed plan write.",
22
21
  ];
23
22
 
@@ -0,0 +1,102 @@
1
+ import { join } from "node:path";
2
+ import {
3
+ canonicalPlanPath,
4
+ getLatestRunContext,
5
+ harnessRunsRoot,
6
+ type PlanPacketLike,
7
+ RESEARCH_BRIEF_BASENAME,
8
+ readPlanPacketFromPath,
9
+ validatePlanPacket,
10
+ } from "../../../lib/harness-run-context.js";
11
+ import { readYamlFile } from "../../../lib/harness-yaml.js";
12
+ import type { ApprovePlanParams, PlanResearchBrief } from "./types.js";
13
+
14
+ function isNonEmptyPacket(
15
+ packet: PlanPacketLike | null | undefined,
16
+ ): packet is PlanPacketLike {
17
+ return Boolean(
18
+ packet &&
19
+ typeof packet === "object" &&
20
+ Object.keys(packet).length > 0 &&
21
+ packet.plan_id,
22
+ );
23
+ }
24
+
25
+ export async function loadResearchBriefFromRun(
26
+ runId: string,
27
+ projectRoot: string,
28
+ ): Promise<PlanResearchBrief | undefined> {
29
+ try {
30
+ const path = join(
31
+ harnessRunsRoot(projectRoot),
32
+ runId,
33
+ RESEARCH_BRIEF_BASENAME,
34
+ );
35
+ return (await readYamlFile(
36
+ path,
37
+ RESEARCH_BRIEF_BASENAME,
38
+ )) as PlanResearchBrief;
39
+ } catch {
40
+ return undefined;
41
+ }
42
+ }
43
+
44
+ /** Path-first approve_plan: load packet + research brief from active run dir. */
45
+ export async function resolveApprovePlanParamsFromDisk(
46
+ params: ApprovePlanParams,
47
+ entries: unknown[],
48
+ projectRoot: string,
49
+ ): Promise<
50
+ | {
51
+ ok: true;
52
+ plan_packet: PlanPacketLike;
53
+ research_brief?: PlanResearchBrief;
54
+ }
55
+ | { ok: false; error: string }
56
+ > {
57
+ const inline = params.plan_packet;
58
+ if (isNonEmptyPacket(inline)) {
59
+ const validation = validatePlanPacket(inline);
60
+ if (!validation.valid) {
61
+ return {
62
+ ok: false,
63
+ error: `approve_plan: invalid plan_packet — ${validation.errors.join("; ")}`,
64
+ };
65
+ }
66
+ return {
67
+ ok: true,
68
+ plan_packet: inline,
69
+ research_brief: params.research_brief ?? undefined,
70
+ };
71
+ }
72
+
73
+ const runCtx = getLatestRunContext(entries);
74
+ if (!runCtx?.run_id) {
75
+ return {
76
+ ok: false,
77
+ error:
78
+ 'approve_plan: no active harness run. Run /harness-plan "<task>" first.',
79
+ };
80
+ }
81
+ const planPath =
82
+ runCtx.plan_packet_path ?? canonicalPlanPath(runCtx.run_id, projectRoot);
83
+ const packet = await readPlanPacketFromPath(planPath);
84
+ if (!isNonEmptyPacket(packet)) {
85
+ return {
86
+ ok: false,
87
+ error:
88
+ "approve_plan: plan_packet missing on disk. Write plan-packet.yaml draft before approve_plan.",
89
+ };
90
+ }
91
+ const validation = validatePlanPacket(packet);
92
+ if (!validation.valid) {
93
+ return {
94
+ ok: false,
95
+ error: `approve_plan: invalid plan_packet on disk — ${validation.errors.join("; ")}`,
96
+ };
97
+ }
98
+ const research_brief =
99
+ params.research_brief ??
100
+ (await loadResearchBriefFromRun(runCtx.run_id, projectRoot));
101
+ return { ok: true, plan_packet: packet, research_brief };
102
+ }
@@ -1,12 +1,14 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
 
3
3
  export const ApprovePlanParamsSchema = Type.Object({
4
- plan_packet: Type.Object(
5
- {},
6
- {
7
- description:
8
- "Full PlanPacket object (schema_version, plan_id, task_id, scope, assumptions, risk_level, acceptance_checks, rollback_plan).",
9
- },
4
+ plan_packet: Type.Optional(
5
+ Type.Object(
6
+ {},
7
+ {
8
+ description:
9
+ "Optional inline PlanPacket (deprecated). Default: read plan-packet.yaml from active run (ADR 0043).",
10
+ },
11
+ ),
10
12
  ),
11
13
  human_summary: Type.Optional(
12
14
  Type.String({
@@ -45,10 +47,22 @@ export const ApprovePlanParamsSchema = Type.Object({
45
47
  });
46
48
 
47
49
  export const PROMPT_SNIPPET =
48
- "approve_plan({ plan_packet: { ...PlanPacket fields... }, human_summary?: string, research_brief?: { decomposition, hypothesis, eval } })";
50
+ "approve_plan({ human_summary?: string }) loads plan-packet.yaml from active run";
49
51
 
50
52
  export const PROMPT_GUIDELINES = [
51
- "Call approve_plan once with the complete plan_packet when ready for user approval.",
53
+ "Call approve_plan once when plan-packet.yaml is on disk (path-first; do not embed full packet in tool args).",
52
54
  "Use ask_user only for clarification — not for final plan approval.",
53
55
  "On Request changes, revise the plan and call approve_plan again.",
54
56
  ];
57
+
58
+ export const CreatePlanParamsSchema = Type.Object({
59
+ plan_packet: Type.Optional(
60
+ Type.Object(
61
+ {},
62
+ {
63
+ description:
64
+ "Optional inline packet (deprecated). Default: read approved plan from plan_packet_path.",
65
+ },
66
+ ),
67
+ ),
68
+ });
@@ -22,7 +22,7 @@ export interface PlanResearchBrief {
22
22
  }
23
23
 
24
24
  export interface ApprovePlanParams {
25
- plan_packet: PlanPacketLike;
25
+ plan_packet?: PlanPacketLike;
26
26
  human_summary?: string;
27
27
  research_brief?: PlanResearchBrief | null;
28
28
  options?: Array<string | { title: string; description?: string }>;
@@ -15,8 +15,8 @@ export function validateApprovePlanParams(
15
15
  params: ApprovePlanParams,
16
16
  ): ValidatedApprovePlanParams | string {
17
17
  const packet = params.plan_packet;
18
- if (!packet || typeof packet !== "object") {
19
- return "approve_plan: plan_packet object is required.";
18
+ if (!packet || typeof packet !== "object" || !packet.plan_id) {
19
+ return "approve_plan: plan_packet must be resolved from disk before validate (use resolveApprovePlanParamsFromDisk).";
20
20
  }
21
21
  const validation = validatePlanPacket(packet as PlanPacketLike);
22
22
  if (!validation.valid) {