@agent-plan/pi-adapter 0.2.27 → 0.2.29

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AA0/BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAkuK9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AA0/BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAquK9D"}
package/dist/index.js CHANGED
@@ -3839,8 +3839,8 @@ export default function planPiExtension(pi) {
3839
3839
  && phase.handoffAudit.contentLength === phase.handoff.length);
3840
3840
  const resumeReady = persistenceVerified && Boolean(phase.handoffAudit?.resumeReadyAt);
3841
3841
  const status = resumeReady
3842
- ? "Resume-ready: persisted read-back and source reconciliation completed."
3843
- : "NOT resume-ready: after reading this entire persisted body, call handoff_verify with its contentHash and a fresh comparison against every required source. If any gap exists, rewrite instead of verifying.";
3842
+ ? "Resume-ready: persisted compact capsule read-back completed."
3843
+ : "NOT resume-ready: after reading this persisted capsule, call handoff_verify with its contentHash. The planner derives legacy evidence from persisted state; rewrite only if the capsule itself omits resume-critical context.";
3844
3844
  return {
3845
3845
  content: [{ type: "text", text: `Handoff for ${r.compositeRef}\n${status}\nContent hash: ${contentHash}\n\n${phaseWorkMap.content}\n\nBefore proposing new work, reread the canonical phase and relevant sibling task full view; do not duplicate an already-owned capability.\n\n${bounded.content}` }],
3846
3846
  details: { phaseRef: r.compositeRef, phaseId: r.phase.id, phaseWorkMap, ...bounded, contentHash, persistenceVerified, resumeReady, verificationRequired: !resumeReady, handoffAudit: phase.handoffAudit },
@@ -3850,15 +3850,15 @@ export default function planPiExtension(pi) {
3850
3850
  pi.registerTool({
3851
3851
  name: "handoff_verify",
3852
3852
  label: "Handoff Verify",
3853
- description: "Finalize one persisted handoff as resume-ready only after handoff_show has displayed the entire body and the agent has compared it again with conversation corrections, planner entities, working tree/diff, verification/runtime evidence, and peer output. Any discovered omission blocks verification and requires prepare+rewrite.",
3853
+ description: "Finalize one persisted handoff as resume-ready after handoff_show confirms the persisted compact resume capsule. Legacy sourceReviews and omissionsFound evidence remains accepted but is optional; the planner derives omitted evidence from persisted state.",
3854
3854
  parameters: Type.Object({
3855
3855
  phaseRef: Type.String(),
3856
3856
  expectedContentHash: Type.String({ pattern: "^[a-f0-9]{64}$" }),
3857
- sourceReviews: Type.Array(Type.Object({
3857
+ sourceReviews: Type.Optional(Type.Array(Type.Object({
3858
3858
  source: Type.Union([Type.Literal("conversation"), Type.Literal("planner-entities"), Type.Literal("working-tree"), Type.Literal("verification-runtime"), Type.Literal("peer-agent-output")]),
3859
3859
  detail: Type.String({ minLength: 12 }),
3860
- })),
3861
- omissionsFound: Type.Array(Type.String({ minLength: 1 })),
3860
+ }))),
3861
+ omissionsFound: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
3862
3862
  }),
3863
3863
  async execute(_id, params, _signal, _onUpdate, ctx) {
3864
3864
  const st = await requirePlan(ctx);
@@ -3870,8 +3870,8 @@ export default function planPiExtension(pi) {
3870
3870
  try {
3871
3871
  const result = await st.verifyPhaseHandoffReadBack(r.phase.id, {
3872
3872
  expectedContentHash: params.expectedContentHash,
3873
- sourceReviews: params.sourceReviews,
3874
- omissionsFound: params.omissionsFound,
3873
+ ...(params.sourceReviews ? { sourceReviews: params.sourceReviews } : {}),
3874
+ ...(params.omissionsFound ? { omissionsFound: params.omissionsFound } : {}),
3875
3875
  });
3876
3876
  await st.writeGenerated();
3877
3877
  return {
@@ -3890,7 +3890,7 @@ export default function planPiExtension(pi) {
3890
3890
  pi.registerTool({
3891
3891
  name: "handoff_prepare",
3892
3892
  label: "Handoff Prepare",
3893
- description: "Audit one exact phase before writing a handoff. Returns the canonical scaffold, a bounded priority-ordered phase work map with sibling goals/dependencies/capability ownership, the current token, missing task evidence, required audits, and the 24,000-character inline budget. Reread the canonical phase and relevant sibling tasks before proposing work; reconcile everything through handoff_write.",
3893
+ description: "Prepare one exact phase before writing a handoff. Returns a compact resume capsule scaffold, bounded phase work map, current token, missing task evidence, and inline budget. Reread the canonical phase and relevant sibling tasks before proposing work; do not duplicate planner evidence in Markdown.",
3894
3894
  parameters: Type.Object({ phaseRef: Type.String({ description: "Exact confirmed phase ref: P00x | P00x(F00x) | UUID | title." }) }),
3895
3895
  async execute(_id, params, _signal, _onUpdate, ctx) {
3896
3896
  const st = await requirePlan(ctx);
@@ -3901,7 +3901,7 @@ export default function planPiExtension(pi) {
3901
3901
  return { content: [{ type: "text", text: `❌ ${r.error}` }], details: { error: r.error } };
3902
3902
  try {
3903
3903
  const audit = await st.preparePhaseHandoff(r.phase.id);
3904
- const { handoff, ...auditDetails } = audit;
3904
+ const { handoff, completenessCategories: _completenessCategories, coldStartSourceReviews: _coldStartSourceReviews, coldStartInventoryCategories: _coldStartInventoryCategories, ...auditDetails } = audit;
3905
3905
  const boundedExisting = boundedHandoffForTransport(handoff);
3906
3906
  const missing = audit.missingCompletionTasks.map((task) => `- T${String(task.number).padStart(3, "0")} — ${task.title}`).join("\n") || "- None";
3907
3907
  const existing = boundedExisting.content.trim() || "(none)";
@@ -3918,19 +3918,21 @@ export default function planPiExtension(pi) {
3918
3918
  "Done tasks missing durable completion/verification evidence:",
3919
3919
  missing,
3920
3920
  "",
3921
- `Required completeness audit v${audit.completenessVersion} — every category must be captured or marked not-applicable with a substantive reason:`,
3922
- ...audit.completenessCategories.map((entry) => `- ${entry.id} — ${entry.label}`),
3923
- "",
3924
- `Required cold-start source review v${audit.coldStartInventoryVersion} — inspect and substantively summarize every source before drafting:`,
3925
- ...audit.coldStartSourceReviews.map((entry) => `- ${entry.id} — ${entry.label}`),
3926
- "Required cold-start inventory — every category needs a concrete item (use an explicit verified-negative statement when nothing exists), and every item must appear verbatim in the handoff or a validated supporting document:",
3927
- ...audit.coldStartInventoryCategories.map((entry) => `- ${entry.id} — ${entry.label}`),
3928
- `Maximum canonical inline content: ${audit.maxContentChars} characters. Link extended material from .planner/docs/.`,
3921
+ "Compact handoff contract: include only the exact focus/resume point, current or partial state, preservation constraints, blockers, decisions, verification, and ordered next actions needed by the next agent.",
3922
+ "No Markdown heading is mandatory: concise free-form resume prose is accepted. Use headings only when they make the capsule clearer.",
3923
+ "Completeness audit and cold-start evidence are planner-owned metadata. Do not copy their categories or source reviews into Markdown; provide legacy evidence only when it already exists.",
3924
+ `Inline target: ${audit.targetContentChars} characters; absolute compatibility ceiling: ${audit.maxContentChars}. Extended detail is externalized automatically when needed.`,
3929
3925
  "",
3930
3926
  "Existing active handoff (reconcile all still-relevant content):",
3931
3927
  existing,
3932
3928
  ].join("\n") }],
3933
- details: { phaseRef: r.compositeRef, ...auditDetails, existingHandoffLength: boundedExisting.fullLength, existingHandoffTruncated: boundedExisting.truncated },
3929
+ details: {
3930
+ phaseRef: r.compositeRef,
3931
+ ...auditDetails,
3932
+ evidenceContract: "Planner-owned completeness and cold-start evidence is derived from persisted state; no category inventory is required from the agent.",
3933
+ existingHandoffLength: boundedExisting.fullLength,
3934
+ existingHandoffTruncated: boundedExisting.truncated,
3935
+ },
3934
3936
  };
3935
3937
  }
3936
3938
  catch (error) {
@@ -3942,7 +3944,7 @@ export default function planPiExtension(pi) {
3942
3944
  pi.registerTool({
3943
3945
  name: "handoff_write",
3944
3946
  label: "Handoff Write",
3945
- description: "Persist a reconciled handoff candidate with durable context synchronization and mandatory audits. The result always has resumeReady=false until a separate handoff_show read-back and handoff_verify source reconciliation succeeds. Run handoff_prepare first; extended detail belongs in linked .planner/docs/ Markdown.",
3947
+ description: "Persist a compact reconciled handoff candidate with durable context synchronization. The result always has resumeReady=false until a separate handoff_show read-back and handoff_verify succeeds. Run handoff_prepare first; extended detail belongs in linked .planner/docs/ Markdown.",
3946
3948
  parameters: Type.Object({
3947
3949
  phaseRef: Type.String({ description: "Exact confirmed phase ref: P00x | P00x(F00x) | UUID | title." }),
3948
3950
  title: Type.Optional(Type.String({ description: "Meaningful handoff title summarizing the work (becomes the H1 / first line in lists)." })),
@@ -3957,7 +3959,7 @@ export default function planPiExtension(pi) {
3957
3959
  entries: Type.Array(Type.Object({
3958
3960
  category: Type.String({ description: `One required category id from handoff_prepare: ${HANDOFF_COMPLETENESS_CATEGORIES.map((entry) => entry.id).join(", ")}` }),
3959
3961
  status: Type.Union([Type.Literal("captured"), Type.Literal("not-applicable")]),
3960
- detail: Type.String({ description: "Substantive captured detail or a substantive reason why the category is not applicable." }),
3962
+ detail: Type.String({ description: "Legacy evidence detail; omit the entire audit for compact handoffs." }),
3961
3963
  })),
3962
3964
  })),
3963
3965
  coldStartInventory: Type.Optional(Type.Object({
@@ -3968,8 +3970,8 @@ export default function planPiExtension(pi) {
3968
3970
  })),
3969
3971
  entries: Type.Array(Type.Object({
3970
3972
  category: Type.String({ description: `One required cold-start category id from handoff_prepare: ${HANDOFF_COLD_START_INVENTORY_CATEGORIES.map((entry) => entry.id).join(", ")}` }),
3971
- items: Type.Array(Type.String({ description: "Exact resume-critical item; it must appear verbatim in the handoff or a validated supporting document." })),
3972
- notApplicableReason: Type.Optional(Type.String({ description: "Optional explanation only; every category still requires a concrete item, using an explicit verified-negative statement when nothing exists." })),
3973
+ items: Type.Array(Type.String({ description: "Legacy resume evidence item; omit the entire inventory for compact handoffs." })),
3974
+ notApplicableReason: Type.Optional(Type.String({ description: "Legacy evidence explanation." })),
3973
3975
  })),
3974
3976
  })),
3975
3977
  supportingDocuments: Type.Optional(Type.Array(Type.Object({
@@ -4064,7 +4066,7 @@ export default function planPiExtension(pi) {
4064
4066
  });
4065
4067
  await st.writeGenerated();
4066
4068
  return {
4067
- content: [{ type: "text", text: `⚠️ Handoff candidate persisted on ${r.compositeRef}, but it is NOT resume-ready yet. Required next action: call handoff_show ${r.compositeRef}, read the entire persisted body, compare it again with every required source, then call handoff_verify with the returned contentHash. If that review finds any gap, run prepare+write again instead of verifying.` }],
4069
+ content: [{ type: "text", text: `⚠️ Handoff candidate persisted on ${r.compositeRef}, but it is NOT resume-ready yet. Required next action: call handoff_show ${r.compositeRef}, read the persisted compact capsule, then call handoff_verify with the returned contentHash. Rewrite only if resume-critical context is missing.` }],
4068
4070
  details: {
4069
4071
  phaseRef: r.compositeRef,
4070
4072
  phaseId: r.phase.id,
@@ -4075,9 +4077,8 @@ export default function planPiExtension(pi) {
4075
4077
  resumeReady: false,
4076
4078
  verificationRequired: true,
4077
4079
  nextActions: [
4078
- `Call handoff_show ${r.compositeRef} and read the entire persisted body.`,
4079
- "Compare the persisted body against conversation corrections, planner entities, working tree/diff, verification/runtime evidence, and peer-agent output.",
4080
- "If any omission exists, run handoff_prepare and handoff_write again; otherwise call handoff_verify with the shown contentHash.",
4080
+ `Call handoff_show ${r.compositeRef} and read the persisted compact capsule.`,
4081
+ "Call handoff_verify with the shown contentHash; legacy sourceReviews and omissionsFound are optional because the planner derives omitted evidence from persisted state.",
4081
4082
  ],
4082
4083
  },
4083
4084
  };
@@ -4846,10 +4847,7 @@ export default function planPiExtension(pi) {
4846
4847
  const staleParentRefs = descriptionFreshness?.diagnostics
4847
4848
  .filter((entry) => entry.state === "stale" && entry.ownerId === phase.featureId)
4848
4849
  .map((entry) => entry.ownerRef) ?? [];
4849
- const freshnessNotice = staleParentRefs.length > 0
4850
- ? `\n⚠️ Parent description review required: ${staleParentRefs.join(", ")}. Run description_freshness for the non-mutating reconciliation preview.`
4851
- : "";
4852
- return { content: [{ type: "text", text: `✅ Phase updated: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, phaseUpdateFeatures))} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.${freshnessNotice}` }], details: { ...phase, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) } };
4850
+ return { content: [{ type: "text", text: `✅ Phase updated: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, phaseUpdateFeatures))} — ${phase.title}${phase.shortId ? ` · ${phase.shortId}` : ""}. Fields saved: ${receivedFields.join(", ")}.` }], details: { ...phase, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) } };
4853
4851
  },
4854
4852
  });
4855
4853
  pi.registerTool({
@@ -5420,10 +5418,7 @@ export default function planPiExtension(pi) {
5420
5418
  });
5421
5419
  }
5422
5420
  try {
5423
- const updatedPhase = await st.updatePhase(hostPhase.id, (phase) => {
5424
- const task = phase.tasks.find((t) => t.id === found.task.id);
5425
- if (!task)
5426
- return phase;
5421
+ const updatedPhase = await st.updateTask(hostPhase.id, found.task.id, (task) => {
5427
5422
  if (params.title !== undefined)
5428
5423
  task.title = params.title;
5429
5424
  if (params.priority !== undefined)
@@ -5466,17 +5461,21 @@ export default function planPiExtension(pi) {
5466
5461
  applyTaskLifecycleDates(task, params.status, now);
5467
5462
  }
5468
5463
  task.updatedAt = now;
5469
- phase.updatedAt = now;
5470
5464
  updatedTask = task;
5471
- return phase;
5465
+ return task;
5472
5466
  });
5473
5467
  void updatedPhase;
5474
5468
  }
5475
5469
  catch (e) {
5476
- return { content: [{ type: "text", text: `Update failed: ${e}` }], details: {} };
5470
+ const errorCode = e instanceof PlanStoreError ? e.details?.errorCode : undefined;
5471
+ return {
5472
+ content: [{ type: "text", text: `Update failed: ${e instanceof Error ? e.message : String(e)}` }],
5473
+ details: { updated: false, errorCode: errorCode ?? "TASK_UPDATE_FAILED" },
5474
+ isError: true,
5475
+ };
5477
5476
  }
5478
5477
  if (!updatedTask)
5479
- return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
5478
+ return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: { updated: false, errorCode: "TASK_NOT_FOUND" }, isError: true };
5480
5479
  await st.syncTaskStatusRollup(found.phase.id);
5481
5480
  await st.writeGenerated();
5482
5481
  const descriptionFreshness = receivedFields.some((field) => field === "description" || field === "descriptionRef")
@@ -5486,10 +5485,7 @@ export default function planPiExtension(pi) {
5486
5485
  const staleParentRefs = descriptionFreshness?.diagnostics
5487
5486
  .filter((entry) => entry.state === "stale" && ownerIds.has(entry.ownerId))
5488
5487
  .map((entry) => entry.ownerRef) ?? [];
5489
- const freshnessNotice = staleParentRefs.length > 0
5490
- ? `\n⚠️ Parent description review required: ${staleParentRefs.join(", ")}. Run description_freshness for the non-mutating reconciliation preview.`
5491
- : "";
5492
- return { content: [{ type: "text", text: `Task updated: ${updatedTask.id} (${updatedTask.status}). Fields saved: ${receivedFields.join(", ")}.${freshnessNotice}` }], details: { ...updatedTask, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) } };
5488
+ return { content: [{ type: "text", text: `Task updated: ${updatedTask.id} (${updatedTask.status}). Fields saved: ${receivedFields.join(", ")}.` }], details: { ...updatedTask, updated: true, updatedFields: receivedFields, ...(descriptionFreshness ? { descriptionFreshness, staleParentRefs } : {}) } };
5493
5489
  },
5494
5490
  });
5495
5491
  pi.registerTool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/pi-adapter",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "private": false,
5
5
  "description": "Pi agent extension for Agent Plan — structured project planning in .planner/",
6
6
  "keywords": [
@@ -28,8 +28,8 @@
28
28
  "dependencies": {
29
29
  "typebox": "^1.1.24",
30
30
  "zod": "^3.24.0",
31
- "@agent-plan/core": "0.2.27",
32
- "@agent-plan/server": "0.2.27"
31
+ "@agent-plan/core": "0.2.29",
32
+ "@agent-plan/server": "0.2.29"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@earendil-works/pi-coding-agent": "^0.74.0",