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
@@ -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({
@@ -22,6 +22,12 @@ function roundPath(prefix: string, doc: Record<string, unknown>): string {
22
22
  }
23
23
 
24
24
  export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
25
+ {
26
+ toolName: "submit_planning_context",
27
+ agents: ["harness/planning/planning-context"],
28
+ schemaFile: "plan-planning-context.schema.json",
29
+ artifactPath: "artifacts/planning-context.yaml",
30
+ },
25
31
  {
26
32
  toolName: "submit_scout_findings",
27
33
  agents: [
@@ -42,13 +48,16 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
42
48
  },
43
49
  {
44
50
  toolName: "submit_decomposition_brief",
45
- agents: ["harness/planning/decompose"],
51
+ agents: ["harness/planning/decompose", "harness/planning/plan-synthesizer"],
46
52
  schemaFile: "plan-decomposition-brief.schema.json",
47
53
  artifactPath: "artifacts/decomposition.yaml",
48
54
  },
49
55
  {
50
56
  toolName: "submit_hypothesis_brief",
51
- agents: ["harness/planning/hypothesis"],
57
+ agents: [
58
+ "harness/planning/hypothesis",
59
+ "harness/planning/plan-synthesizer",
60
+ ],
52
61
  schemaFile: "plan-hypothesis-brief.schema.json",
53
62
  artifactPath: "artifacts/hypothesis.yaml",
54
63
  },
@@ -66,7 +75,10 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
66
75
  },
67
76
  {
68
77
  toolName: "submit_execution_plan_brief",
69
- agents: ["harness/planning/execution-plan-author"],
78
+ agents: [
79
+ "harness/planning/execution-plan-author",
80
+ "harness/planning/plan-synthesizer",
81
+ ],
70
82
  schemaFile: "plan-execution-plan-brief.schema.json",
71
83
  artifactPath: "artifacts/execution-plan-draft.yaml",
72
84
  },
@@ -129,6 +141,12 @@ export const SUBMIT_TOOL_SPECS: readonly SubmitToolSpec[] = [
129
141
  artifactPath: "artifacts/human-required.yaml",
130
142
  humanRequired: true,
131
143
  },
144
+ {
145
+ toolName: "submit_sentrux_manifest_proposal",
146
+ agents: ["harness/sentrux-steward"],
147
+ schemaFile: "sentrux-manifest-proposal.schema.json",
148
+ artifactPath: "artifacts/sentrux-manifest-proposal.yaml",
149
+ },
132
150
  ] as const;
133
151
 
134
152
  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) {