ultimate-pi 0.17.0 → 0.18.0

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 (110) 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-eval/SKILL.md +6 -21
  4. package/.agents/skills/harness-governor/SKILL.md +4 -3
  5. package/.agents/skills/harness-orchestration/SKILL.md +39 -51
  6. package/.agents/skills/harness-plan/SKILL.md +23 -12
  7. package/.agents/skills/harness-review/SKILL.md +52 -0
  8. package/.agents/skills/harness-sentrux-setup/SKILL.md +13 -1
  9. package/.agents/skills/harness-steer/SKILL.md +14 -0
  10. package/.pi/agents/harness/adversary.md +3 -10
  11. package/.pi/agents/harness/evaluator.md +3 -12
  12. package/.pi/agents/harness/executor.md +12 -14
  13. package/.pi/agents/harness/planning/decompose.md +7 -4
  14. package/.pi/agents/harness/planning/hypothesis-validator.md +2 -0
  15. package/.pi/agents/harness/planning/hypothesis.md +3 -1
  16. package/.pi/agents/harness/planning/plan-adversary.md +2 -0
  17. package/.pi/agents/harness/planning/plan-evaluator.md +2 -0
  18. package/.pi/agents/harness/planning/plan-synthesizer.md +25 -0
  19. package/.pi/agents/harness/planning/planning-context.md +48 -0
  20. package/.pi/agents/harness/planning/review-integrator.md +2 -0
  21. package/.pi/agents/harness/planning/scout-graphify.md +3 -1
  22. package/.pi/agents/harness/planning/scout-semantic.md +3 -1
  23. package/.pi/agents/harness/planning/scout-structure.md +3 -1
  24. package/.pi/agents/harness/planning/sprint-contract-auditor.md +2 -0
  25. package/.pi/agents/harness/sentrux-steward.md +51 -0
  26. package/.pi/extensions/00-posthog-network-bootstrap.ts +11 -0
  27. package/.pi/extensions/harness-live-widget.ts +27 -1
  28. package/.pi/extensions/harness-plan-approval.ts +62 -56
  29. package/.pi/extensions/harness-run-context.ts +541 -84
  30. package/.pi/extensions/harness-subagent-submit.ts +43 -10
  31. package/.pi/extensions/lib/harness-artifact-gate.ts +182 -0
  32. package/.pi/extensions/lib/harness-posthog.ts +9 -5
  33. package/.pi/extensions/lib/harness-spawn-topology.ts +188 -0
  34. package/.pi/extensions/lib/harness-subagent-auth.ts +1 -0
  35. package/.pi/extensions/lib/harness-subagent-policy.ts +23 -19
  36. package/.pi/extensions/lib/harness-subagent-precheck.ts +35 -9
  37. package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +66 -2
  38. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +21 -3
  39. package/.pi/extensions/lib/harness-subagents-bridge.ts +7 -29
  40. package/.pi/extensions/lib/harness-subprocess-bootstrap.ts +73 -0
  41. package/.pi/extensions/lib/plan-approval/create-plan.ts +2 -3
  42. package/.pi/extensions/lib/plan-approval/resolve-disk.ts +102 -0
  43. package/.pi/extensions/lib/plan-approval/schema.ts +22 -8
  44. package/.pi/extensions/lib/plan-approval/types.ts +1 -1
  45. package/.pi/extensions/lib/plan-approval/validate.ts +2 -2
  46. package/.pi/extensions/lib/plan-approval-readiness.ts +241 -0
  47. package/.pi/extensions/lib/plan-debate-eligibility.ts +12 -5
  48. package/.pi/extensions/lib/plan-debate-gate.ts +22 -1
  49. package/.pi/extensions/lib/plan-debate-lanes.ts +32 -2
  50. package/.pi/extensions/lib/plan-review-gate.ts +8 -0
  51. package/.pi/extensions/lib/posthog-client.ts +76 -0
  52. package/.pi/extensions/policy-gate.ts +24 -19
  53. package/.pi/harness/agents.manifest.json +24 -16
  54. package/.pi/harness/corpus/cron.example +8 -0
  55. package/.pi/harness/corpus/graphify-kb-updater.config.json +159 -0
  56. package/.pi/harness/corpus/systemd/graphify-kb-updater.env.template +4 -0
  57. package/.pi/harness/corpus/systemd/graphify-kb-updater.service +17 -0
  58. package/.pi/harness/corpus/systemd/graphify-kb-updater.timer +11 -0
  59. package/.pi/harness/docs/adrs/0001-harness-constitution.md +2 -1
  60. package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +7 -6
  61. package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +6 -1
  62. package/.pi/harness/docs/adrs/0031-harness-run-context.md +1 -1
  63. package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +7 -0
  64. package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +3 -3
  65. package/.pi/harness/docs/adrs/0036-implementation-research-and-selective-debate.md +8 -5
  66. package/.pi/harness/docs/adrs/0039-harness-post-run-review-gate.md +47 -0
  67. package/.pi/harness/docs/adrs/0040-practice-grounded-orchestration.md +40 -0
  68. package/.pi/harness/docs/adrs/0041-intelligent-planning-reconnaissance.md +39 -0
  69. package/.pi/harness/docs/adrs/0042-agent-native-orchestration.md +35 -0
  70. package/.pi/harness/docs/adrs/0043-path-first-harness-tools.md +38 -0
  71. package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +36 -0
  72. package/.pi/harness/docs/adrs/README.md +10 -0
  73. package/.pi/harness/docs/graphify-kb-updater-runbook.md +157 -0
  74. package/.pi/harness/docs/practice-map.md +110 -0
  75. package/.pi/harness/env.harness.template +5 -3
  76. package/.pi/harness/evals/smoke/sentrux-stub.json +1 -1
  77. package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +5 -2
  78. package/.pi/harness/specs/README.md +1 -1
  79. package/.pi/harness/specs/harness-run-context.schema.json +11 -0
  80. package/.pi/harness/specs/harness-spawn-context.schema.json +14 -0
  81. package/.pi/harness/specs/plan-execution-plan.schema.json +39 -1
  82. package/.pi/harness/specs/plan-packet.schema.json +4 -0
  83. package/.pi/harness/specs/plan-phase-status.schema.json +17 -0
  84. package/.pi/harness/specs/plan-phase-waiver.schema.json +25 -0
  85. package/.pi/harness/specs/plan-planning-context.schema.json +50 -0
  86. package/.pi/harness/specs/repair-brief.schema.json +45 -0
  87. package/.pi/harness/specs/review-outcome.schema.json +46 -0
  88. package/.pi/harness/specs/sentrux-manifest-proposal.schema.json +80 -0
  89. package/.pi/harness/specs/sentrux-signal.schema.json +43 -0
  90. package/.pi/harness/specs/steer-state.schema.json +20 -0
  91. package/.pi/lib/harness-context-mode-policy.ts +256 -0
  92. package/.pi/lib/harness-repair-brief.ts +145 -0
  93. package/.pi/lib/harness-run-context.ts +591 -32
  94. package/.pi/lib/harness-ui-state.ts +87 -9
  95. package/.pi/prompts/harness-auto.md +9 -9
  96. package/.pi/prompts/harness-critic.md +3 -30
  97. package/.pi/prompts/harness-eval.md +4 -37
  98. package/.pi/prompts/harness-plan.md +118 -54
  99. package/.pi/prompts/harness-review.md +150 -15
  100. package/.pi/prompts/harness-run.md +62 -10
  101. package/.pi/prompts/harness-sentrux-steward.md +55 -0
  102. package/.pi/prompts/harness-steer.md +30 -0
  103. package/.pi/scripts/graphify-kb-updater.mjs +358 -0
  104. package/.pi/scripts/harness-verify.mjs +22 -6
  105. package/.pi/scripts/harness-web-policy-guard.mjs +68 -0
  106. package/.pi/scripts/validate-plan-dag.mjs +3 -3
  107. package/AGENTS.md +1 -0
  108. package/CHANGELOG.md +11 -0
  109. package/package.json +5 -4
  110. package/.pi/prompts/git-sync.md +0 -124
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: harness/planning/plan-synthesizer
3
+ description: Lake-first plan synthesis for low/med risk — problem framing, hypothesis, and execution_plan draft in one pass.
4
+ ---
5
+
6
+ # Plan synthesizer
7
+
8
+ You produce **lake-sized** outcomes (ADR 0042), not ticket-granularity WBS. Read `artifacts/planning-context.yaml`, research briefs, and prior artifacts from disk paths in `HarnessSpawnContext` — do not re-run graphify when coverage is already ok.
9
+
10
+ ## Outputs (all required on disk)
11
+
12
+ 1. **`submit_decomposition_brief`** → `artifacts/decomposition.yaml` — `core_tension`, `lakes[]` (outcome, scope boundary, verification intent), not a deep task tree.
13
+ 2. **`submit_hypothesis_brief`** → `artifacts/hypothesis.yaml` — falsifiable claim grounded in decomposition.
14
+ 3. **`submit_execution_plan_brief`** → `artifacts/execution-plan-draft.yaml` — lake-first `execution_plan` with `work_items` (each with `lake_id`, rich `description`, optional `context_bundle_path`), `executor_strategy` (`single_pass` for low, `per_lake` for med unless user dictates otherwise).
15
+
16
+ ## Rules
17
+
18
+ - Use **`submit_*({ source_path })`** when drafts exist on disk (ADR 0043); otherwise `document`.
19
+ - Do not spawn subprocesses; you are the subprocess.
20
+ - Match schemas under `.pi/harness/specs/`.
21
+ - Parent runs `validate-plan-dag.mjs` after merge into `plan-packet.yaml`.
22
+
23
+ ## High risk
24
+
25
+ If `--risk high` or material fork, stop and tell parent to use sequential `decompose` → `hypothesis` → `execution-plan-author` instead.
@@ -0,0 +1,48 @@
1
+ ---
2
+ description: Plan-phase optional reconnaissance subagent — graphify, sg, ccc (read-only). Prefer parent tool use.
3
+ tools: read, bash, ls, submit_planning_context
4
+ disallowed_tools: write, edit, ask_user, approve_plan, create_plan, subagent, grep, find
5
+ extensions: false
6
+ thinking: low
7
+ max_turns: 12
8
+ ---
9
+
10
+ You are the **Harness planning-context gatherer** (optional Phase 1 subprocess).
11
+
12
+ ## When to use
13
+
14
+ The **parent orchestrator** normally compiles `artifacts/planning-context.yaml` using tools directly. Spawn this agent only when reconnaissance is large enough to need a clean subprocess or context isolation.
15
+
16
+ ## Mission
17
+
18
+ Compile merged reconnaissance for the task in `HarnessSpawnContext`. You do **not** build the PlanPacket, approve plans, or mutate anything.
19
+
20
+ Use the repo tool hierarchy intelligently — pick tools that answer the task, not every tool by rote:
21
+
22
+ 1. **Architecture / relationships:** `graphify-out/GRAPH_REPORT.md`, then `graphify query`, `graphify explain`, `graphify path` (read-only).
23
+ 2. **Structure / symbols:** `sg -p '…'` — do not use `find` or `grep` for code search.
24
+ 3. **Semantic implementation:** `ccc search` (2–3 focused queries). The harness runs incremental `ccc index` before spawns — do **not** run `ccc index` or `ccc search --refresh`.
25
+
26
+ Skip lanes that add no value for this task. Record skipped lanes in `coverage.<lane>.status: skipped`.
27
+
28
+ ## Spawn context
29
+
30
+ Read `HarnessSpawnContext` (`task_summary`, `mode`, `plan_packet_path`, `risk_level`, `quick`). For `mode: revise`, read the existing plan first and focus on delta/risk areas.
31
+
32
+ When `quick: true`, you may set `coverage.semantic.status: skipped`.
33
+
34
+ ## Bash guardrails
35
+
36
+ Read-only only: no `graphify update`, installs, redirects (`>`, `>>`), or file creation.
37
+
38
+ ## Output
39
+
40
+ Before ending, call `submit_planning_context` exactly once with a full `PlanPlanningContext` document:
41
+
42
+ - `schema_version: "1.0.0"`
43
+ - `status`: `ok` | `partial` | `failed`
44
+ - `summary`: one paragraph
45
+ - `coverage`: `architecture`, `structure`, and `semantic` (each with `status`, `tools_used`, `summary`, `key_paths` as applicable)
46
+ - `findings`, `evidence_refs`, `open_questions`
47
+
48
+ Do not paste the artifact as prose — the tool write is the deliverable.
@@ -7,6 +7,8 @@ thinking: medium
7
7
  max_turns: 12
8
8
  ---
9
9
 
10
+ **Inspection role:** Recorder / integration PM (round synthesis). Parent is chair. See `.pi/harness/docs/practice-map.md`.
11
+
10
12
  ## Your task
11
13
 
12
14
  Synthesize evaluator, adversary, sprint audit, and (R1) hypothesis-validator lanes into one Review Gate round draft. Decide `review_gate_ready` from evidence, not optimism.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Plan-phase scout — graphify graph and wiki navigation (read-only).
2
+ description: "[DEPRECATEDADR 0041] Legacy graphify-only scout. Prefer parent tools + planning-context.yaml."
3
3
  tools: read, bash, ls, submit_scout_findings
4
4
  disallowed_tools: write, edit, ask_user, approve_plan, create_plan, subagent, grep, find
5
5
  extensions: false
@@ -7,6 +7,8 @@ thinking: low
7
7
  max_turns: 8
8
8
  ---
9
9
 
10
+ > **Deprecated (ADR 0041):** The parent orchestrator should compile `artifacts/planning-context.yaml` using tools directly, or spawn `harness/planning/planning-context` once. This agent remains for backward compatibility only.
11
+
10
12
  You are the **Harness planning scout (graphify lane)**.
11
13
 
12
14
  ## Mission
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Plan-phase scout CocoIndex semantic code search (read-only).
2
+ description: "[DEPRECATED — ADR 0041] Legacy semantic-only scout. Prefer parent tools + planning-context.yaml."
3
3
  tools: read, bash, ls, submit_scout_findings
4
4
  disallowed_tools: write, edit, ask_user, approve_plan, create_plan, subagent, grep, find
5
5
  extensions: false
@@ -7,6 +7,8 @@ thinking: low
7
7
  max_turns: 6
8
8
  ---
9
9
 
10
+ > **Deprecated (ADR 0041):** Prefer parent tool use or `harness/planning/planning-context`.
11
+
10
12
  You are the **Harness planning scout (semantic lane)**.
11
13
 
12
14
  ## Mission
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Plan-phase scout ast-grep structural code search (read-only).
2
+ description: "[DEPRECATED — ADR 0041] Legacy structure-only scout. Prefer parent tools + planning-context.yaml."
3
3
  tools: read, bash, ls, submit_scout_findings
4
4
  disallowed_tools: write, edit, ask_user, approve_plan, create_plan, subagent, grep, find
5
5
  extensions: false
@@ -7,6 +7,8 @@ thinking: low
7
7
  max_turns: 6
8
8
  ---
9
9
 
10
+ > **Deprecated (ADR 0041):** Prefer parent tool use or `harness/planning/planning-context`.
11
+
10
12
  You are the **Harness planning scout (structure lane)**.
11
13
 
12
14
  ## Mission
@@ -7,6 +7,8 @@ thinking: medium
7
7
  max_turns: 12
8
8
  ---
9
9
 
10
+ **Inspection role:** Definition of Done auditor (sprint contract). See `.pi/harness/docs/practice-map.md`.
11
+
10
12
  ## Your task
11
13
 
12
14
  Audit `execution_plan.sprint_contract` and work_item `done_criteria` against ADR-020 (Sprint Contract, Done Criteria Types, Keep Quality Left).
@@ -0,0 +1,51 @@
1
+ ---
2
+ description: Propose architecture.manifest.json changes from graphify evidence (read-only governance steward).
3
+ tools: read, grep, find, ls, bash, submit_sentrux_manifest_proposal
4
+ disallowed_tools: write, edit, ask_user, approve_plan, create_plan, subagent
5
+ extensions: false
6
+ thinking: high
7
+ max_turns: 16
8
+ ---
9
+
10
+ You are the **Harness Sentrux Steward** — architectural **intent** governance, not setup or execution.
11
+
12
+ **Practice:** Architecture governance + fitness functions (Ford/Richards); integrated change control (PMBOK). See `.pi/harness/docs/practice-map.md` phase 4e.
13
+
14
+ ## Mission
15
+
16
+ Propose updates to `.pi/harness/sentrux/architecture.manifest.json` when the codebase or plan introduces a **new bounded context**, **new forbidden dependency class**, or **evidence-backed constraint tuning**. You never write the manifest, `rules.toml`, or merge patches yourself.
17
+
18
+ ## Spawn context
19
+
20
+ Read `HarnessSpawnContext` (`run_id`, `run_dir`, `plan_packet_path`, `task_summary`, scope hints). Read `artifacts/planning-context.yaml` and `artifacts/execution-plan-draft.yaml` when paths are provided.
21
+
22
+ ## Protocol (graphify-first)
23
+
24
+ 1. Read `graphify-out/GRAPH_REPORT.md` — god nodes, communities, surprising edges for paths in scope.
25
+ 2. Run **targeted** read-only graphify (no `graphify update`):
26
+ - `graphify query "<module> coupling boundaries layers"`
27
+ - `graphify path "<concept A>" "<concept B>"` when proposing a new boundary
28
+ - `graphify explain "Modularity"` or `"Architecture governance"` for corpus-backed rationale
29
+ 3. Compare manifest layers/boundaries to plan scope and repo structure (`sg -p` for import edges when proposing boundaries).
30
+ 4. Optional: `sentrux check .` — cite violation messages only; do not fix code.
31
+ 5. Classify proposal:
32
+ - `none` — existing layer globs cover changes; no new coupling class
33
+ - `tune_constraint` — e.g. `max_cc` with sentrux/graphify evidence
34
+ - `add_boundary` — new forbidden import direction
35
+ - `add_layer` / `split_layer` — new bounded context or split overloaded layer
36
+
37
+ ## Output
38
+
39
+ Call **`submit_sentrux_manifest_proposal`** before exit with document matching `sentrux-manifest-proposal.schema.json` → `artifacts/sentrux-manifest-proposal.yaml`.
40
+
41
+ - `manifest_patch`: JSON Merge Patch against current manifest (minimal diff).
42
+ - `evidence[]`: at least one entry per non-`none` change; prefer `source: graphify`.
43
+ - `adr_required: true` and `adr_draft` when material (new layer or boundary affecting multiple agents).
44
+ - `human_required: true` when `change_class` is not `none` and not a single numeric `tune_constraint` with clear sentrux evidence.
45
+
46
+ ## Guardrails
47
+
48
+ - Read-only — no file mutations, no `harness-sentrux-bootstrap`, no `/harness-sentrux-sync`.
49
+ - Do not duplicate full WBS decomposition — read planning artifacts instead.
50
+ - Do not auto-sync rules from directory trees.
51
+ - Never set `inherit_context: true`.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Load before other extensions: IPv4-first fetch for *.posthog.com (@posthog/pi uses global fetch).
3
+ */
4
+
5
+ import { installPostHogFetchPatch } from "./lib/posthog-client.js";
6
+
7
+ installPostHogFetchPatch();
8
+
9
+ export default function posthogNetworkBootstrap() {
10
+ // Side effects run at module load; no hooks required.
11
+ }
@@ -2,6 +2,7 @@ import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
+ import { evaluateCrossSessionResume } from "../lib/harness-run-context.js";
5
6
  import {
6
7
  deriveHarnessStatusHint,
7
8
  formatHarnessPhaseLabel,
@@ -283,6 +284,22 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
283
284
  if (mountCtx) remountHarnessLiveWidget(mountCtx);
284
285
  });
285
286
 
287
+ pi.events.on("harness-run-context:updated", () => {
288
+ stateStore.setCrossSessionResumeCommand(null);
289
+ if (mountCtx) scheduleRefresh(mountCtx);
290
+ });
291
+
292
+ pi.events.on("harness-cross-session-resume", (payload: unknown) => {
293
+ const data =
294
+ payload && typeof payload === "object"
295
+ ? (payload as { resume_command?: string })
296
+ : null;
297
+ const cmd =
298
+ typeof data?.resume_command === "string" ? data.resume_command : null;
299
+ stateStore.setCrossSessionResumeCommand(cmd);
300
+ if (mountCtx) scheduleRefresh(mountCtx);
301
+ });
302
+
286
303
  function updateStatusFallback(
287
304
  ctx: ExtensionContext,
288
305
  state: HarnessUiState,
@@ -304,6 +321,7 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
304
321
  policyDecision: state.policyDecision,
305
322
  flowSubstate: state.flowSubstate,
306
323
  nextRecommendedCommand: state.nextRecommendedCommand,
324
+ crossSessionResumeCommand: state.crossSessionResumeCommand,
307
325
  });
308
326
  }
309
327
 
@@ -322,9 +340,17 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
322
340
  });
323
341
  }
324
342
 
325
- pi.on("session_start", (_event, ctx) => {
343
+ pi.on("session_start", async (_event, ctx) => {
326
344
  mountCtx = ctx;
327
345
  mountHarnessWidget(ctx);
346
+ const info = await evaluateCrossSessionResume(
347
+ process.cwd(),
348
+ ctx.sessionManager.getEntries(),
349
+ );
350
+ if (info) {
351
+ stateStore.setCrossSessionResumeCommand(info.resumeCommand);
352
+ scheduleRefresh(ctx);
353
+ }
328
354
  });
329
355
 
330
356
  pi.on("context", (_event, ctx) => {
@@ -2,12 +2,8 @@
2
2
  * harness-plan-approval — PlanPacket approval UI and transcript renderer for parent sessions.
3
3
  */
4
4
 
5
- import { constants } from "node:fs";
6
- import { access } from "node:fs/promises";
7
- import { join } from "node:path";
8
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
6
  import { Text } from "@earendil-works/pi-tui";
10
- import { Type } from "@sinclair/typebox";
11
7
  import type { PlanPacketLike } from "../lib/harness-run-context.js";
12
8
  import {
13
9
  appendPlanApprovalIfNew,
@@ -33,8 +29,10 @@ import {
33
29
  renderApprovePlanResult,
34
30
  renderHarnessPlanDraft,
35
31
  } from "./lib/plan-approval/render.js";
32
+ import { resolveApprovePlanParamsFromDisk } from "./lib/plan-approval/resolve-disk.js";
36
33
  import {
37
34
  ApprovePlanParamsSchema,
35
+ CreatePlanParamsSchema,
38
36
  PROMPT_GUIDELINES,
39
37
  PROMPT_SNIPPET,
40
38
  } from "./lib/plan-approval/schema.js";
@@ -47,21 +45,12 @@ import {
47
45
  toApprovePlanToolDetails,
48
46
  validateApprovePlanParams,
49
47
  } from "./lib/plan-approval/validate.js";
48
+ import { validatePlanApprovalReadiness } from "./lib/plan-approval-readiness.js";
50
49
  import { validatePlanDebateGate } from "./lib/plan-debate-gate.js";
51
50
 
52
51
  // @ts-expect-error pi extensions run as ESM
53
52
  const MODULE_URL = import.meta.url;
54
53
 
55
- const CreatePlanParamsSchema = Type.Object({
56
- plan_packet: Type.Object(
57
- {},
58
- {
59
- description:
60
- "Approved PlanPacket to persist (same object as approve_plan).",
61
- },
62
- ),
63
- });
64
-
65
54
  export default function harnessPlanApproval(pi: ExtensionAPI) {
66
55
  if (!claimExtensionLoad("harness-plan-approval", MODULE_URL)) return;
67
56
  pi.registerMessageRenderer(
@@ -103,12 +92,37 @@ export default function harnessPlanApproval(pi: ExtensionAPI) {
103
92
  parameters: ApprovePlanParamsSchema,
104
93
 
105
94
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
106
- const validated = validateApprovePlanParams(params as ApprovePlanParams);
95
+ const entries = ctx.sessionManager.getEntries();
96
+ const projectRoot = process.cwd();
97
+ const resolved = await resolveApprovePlanParamsFromDisk(
98
+ params as ApprovePlanParams,
99
+ entries,
100
+ projectRoot,
101
+ );
102
+ if (!resolved.ok) {
103
+ return {
104
+ content: [{ type: "text", text: resolved.error }],
105
+ details: {
106
+ plan_packet: (params as ApprovePlanParams).plan_packet ?? {},
107
+ options: [],
108
+ response: null,
109
+ cancelled: true,
110
+ },
111
+ isError: true,
112
+ };
113
+ }
114
+ const validated = validateApprovePlanParams({
115
+ ...(params as ApprovePlanParams),
116
+ plan_packet: resolved.plan_packet,
117
+ research_brief:
118
+ resolved.research_brief ??
119
+ (params as ApprovePlanParams).research_brief,
120
+ });
107
121
  if (typeof validated === "string") {
108
122
  return {
109
123
  content: [{ type: "text", text: validated }],
110
124
  details: {
111
- plan_packet: (params as ApprovePlanParams).plan_packet ?? {},
125
+ plan_packet: resolved.plan_packet,
112
126
  options: [],
113
127
  response: null,
114
128
  cancelled: true,
@@ -116,7 +130,6 @@ export default function harnessPlanApproval(pi: ExtensionAPI) {
116
130
  };
117
131
  }
118
132
 
119
- const entries = ctx.sessionManager.getEntries();
120
133
  if (
121
134
  hasPlanUserApproval(entries, {
122
135
  sincePlanCommand: true,
@@ -148,43 +161,33 @@ export default function harnessPlanApproval(pi: ExtensionAPI) {
148
161
  validated.human_summary?.trim() ||
149
162
  `Plan ${planId} — pending your approval`;
150
163
  const runCtx = getLatestRunContext(entries);
151
- const projectRoot = process.cwd();
152
164
  const implWarnings: string[] = [];
165
+ const risk = String(
166
+ validated.plan_packet.risk_level ?? "med",
167
+ ).toLowerCase();
153
168
  if (runCtx?.run_id) {
154
- const implPath = join(
169
+ const readiness = await validatePlanApprovalReadiness(
155
170
  projectRoot,
156
- ".pi",
157
- "harness",
158
- "runs",
159
171
  runCtx.run_id,
160
- "artifacts",
161
- "implementation-research.yaml",
172
+ { risk_level: risk },
162
173
  );
163
- let implExists = false;
164
- try {
165
- await access(implPath, constants.R_OK);
166
- implExists = true;
167
- } catch {
168
- implExists = false;
169
- }
170
- const risk = String(
171
- validated.plan_packet.risk_level ?? "med",
172
- ).toLowerCase();
173
- if (!implExists) {
174
- const msg =
175
- "approve_plan: missing artifacts/implementation-research.yaml (Phase 3.5 required)";
176
- if (risk === "high") {
177
- return {
178
- content: [{ type: "text", text: msg }],
179
- details: {
180
- plan_packet: validated.plan_packet,
181
- cancelled: true,
174
+ if (!readiness.ok) {
175
+ return {
176
+ content: [
177
+ {
178
+ type: "text",
179
+ text: `approve_plan blocked — plan phase not ready:\n- ${readiness.errors.join("\n- ")}`,
182
180
  },
183
- isError: true,
184
- };
185
- }
186
- implWarnings.push(msg);
181
+ ],
182
+ details: {
183
+ plan_packet: validated.plan_packet,
184
+ readiness,
185
+ cancelled: true,
186
+ },
187
+ isError: true,
188
+ };
187
189
  }
190
+ implWarnings.push(...readiness.warnings);
188
191
  }
189
192
  if (runCtx?.run_id) {
190
193
  const gate = await validatePlanDebateGate(projectRoot, runCtx.run_id);
@@ -308,19 +311,22 @@ export default function harnessPlanApproval(pi: ExtensionAPI) {
308
311
  parameters: CreatePlanParamsSchema,
309
312
 
310
313
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
311
- const validated = validateApprovePlanParams(params as ApprovePlanParams);
312
- if (typeof validated === "string") {
314
+ const entries = ctx.sessionManager.getEntries();
315
+ const runCtx = getLatestRunContext(entries);
316
+ const projectRoot = process.cwd();
317
+ const resolved = await resolveApprovePlanParamsFromDisk(
318
+ params as ApprovePlanParams,
319
+ entries,
320
+ projectRoot,
321
+ );
322
+ if (!resolved.ok) {
313
323
  return {
314
- content: [{ type: "text", text: validated }],
315
- details: { error: validated },
324
+ content: [{ type: "text", text: resolved.error }],
325
+ details: { error: resolved.error },
316
326
  isError: true,
317
327
  };
318
328
  }
319
-
320
- const entries = ctx.sessionManager.getEntries();
321
- const runCtx = getLatestRunContext(entries);
322
- const projectRoot = process.cwd();
323
- const result = await executeCreatePlan(validated.plan_packet, {
329
+ const result = await executeCreatePlan(resolved.plan_packet, {
324
330
  projectRoot,
325
331
  getParentEntries: () => entries,
326
332
  getSubagentEntries: () => entries,