@agent-plan/pi-adapter 0.2.19-next.12 → 0.2.19-next.14

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;AAgvBtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAw9F9D"}
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;AAmvBtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwmG9D"}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * - inject context into prompts
10
10
  */
11
11
  import { Type } from "typebox";
12
- import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap } from "@agent-plan/core";
12
+ import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem } from "@agent-plan/core";
13
13
  import { createChecklistItemId, createFeatureId, createPhaseId, createShortId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, featureNumberOfPhase } from "@agent-plan/core/naming";
14
14
  import { join, dirname } from "node:path";
15
15
  import { homedir } from "node:os";
@@ -124,6 +124,9 @@ const PLANNER_COMMAND_COMPLETIONS = [
124
124
  { value: "task delete", label: "task delete", description: "Delete a task" },
125
125
  { value: "task start", label: "task start", description: "Mark a task in-progress" },
126
126
  { value: "task complete", label: "task complete", description: "Mark a task done" },
127
+ { value: "task checklist-add", label: "task checklist-add", description: "Add a checklist step: /planner task checklist-add <T00x> <title>" },
128
+ { value: "task checklist-remove", label: "task checklist-remove", description: "Remove a step: /planner task checklist-remove <T00x> <C{n}|title>" },
129
+ { value: "task checklist-toggle", label: "task checklist-toggle", description: "Tick/untick a step: /planner task checklist-toggle <T00x> <C{n}|title> [on|off]" },
127
130
  { value: "handoff list", label: "handoff list", description: "List phases with a handoff" },
128
131
  { value: "handoff prepare", label: "handoff prepare", description: "Tell the agent to write the handoff (legacy file-based)" },
129
132
  { value: "handoff show", label: "handoff show [P00x]", description: "Show a phase handoff (default: in-progress phase)" },
@@ -1374,6 +1377,7 @@ export default function planPiExtension(pi) {
1374
1377
  acceptedDecisions: [],
1375
1378
  phaseIds: [],
1376
1379
  dependsOn: [],
1380
+ statusLog: [],
1377
1381
  createdAt: now,
1378
1382
  updatedAt: now,
1379
1383
  };
@@ -1553,6 +1557,7 @@ export default function planPiExtension(pi) {
1553
1557
  handoff: "", handoffUpdatedAt: "",
1554
1558
  handoffReadAt: "",
1555
1559
  handoffHistory: [],
1560
+ statusLog: [],
1556
1561
  };
1557
1562
  await st.savePhase(phase);
1558
1563
  await st.updateFeatures((doc) => {
@@ -1768,7 +1773,7 @@ export default function planPiExtension(pi) {
1768
1773
  // ═══════════════════════════════════════════════════════════════
1769
1774
  if (a === "task") {
1770
1775
  if (!b) {
1771
- ctx.ui.notify("task actions: add | show [id] | discuss [id|name] | delete | update | start [id] | complete [id]", "info");
1776
+ ctx.ui.notify("task actions: add | show [id] | discuss [id|name] | delete | update | start [id] | complete [id] | checklist-add <T00x> <title> | checklist-remove <T00x> <C{n}|title> | checklist-toggle <T00x> <C{n}|title> [on|off]", "info");
1772
1777
  return;
1773
1778
  }
1774
1779
  if (b === "add") {
@@ -1839,7 +1844,7 @@ export default function planPiExtension(pi) {
1839
1844
  if (description?.trim())
1840
1845
  task.description = description.trim();
1841
1846
  if (checklistSeed?.trim()) {
1842
- task.checklist = splitCsv(checklistSeed).map((itemTitle, index) => ({
1847
+ task.checklist = splitCsv(checklistSeed).map((itemTitle, index) => ({ number: index + 1,
1843
1848
  id: createChecklistItemId(task.id, index + 1, itemTitle),
1844
1849
  title: itemTitle,
1845
1850
  checked: false,
@@ -2084,7 +2089,75 @@ export default function planPiExtension(pi) {
2084
2089
  ctx.ui.notify(`✅ Task completed: ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, (await st.loadFeatures()).features))}/T${String(task.number).padStart(3, "0")} — ${task.title} (done)${task.shortId ? ` · ${task.shortId}` : ""}`, "info");
2085
2090
  return;
2086
2091
  }
2087
- ctx.ui.notify(`Unknown task action "${b}". Try: add, show, discuss, delete, update, start, complete`, "warning");
2092
+ // checklist-add <T00x> <title> / checklist-remove <T00x> <C{n}|title> / checklist-toggle <T00x> <C{n}|title> [on|off]
2093
+ if (b === "checklist-add" || b === "checklist-remove" || b === "checklist-toggle") {
2094
+ const [taskRef, ...itemParts] = rest;
2095
+ const itemText = itemParts.join(" ").trim();
2096
+ if (!taskRef || !itemText) {
2097
+ ctx.ui.notify(`Usage: /planner task ${b} <T00x> <${b === "checklist-add" ? "title" : "C{n}|title"}>${b === "checklist-toggle" ? " [on|off]" : ""}`, "warning");
2098
+ return;
2099
+ }
2100
+ const resolved = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, taskRef.trim());
2101
+ if (!resolved) {
2102
+ ctx.ui.notify(`Task not found: ${taskRef}`, "warning");
2103
+ return;
2104
+ }
2105
+ let checked;
2106
+ if (b === "checklist-toggle") {
2107
+ const m = itemText.match(/\s+(on|off)$/i);
2108
+ if (m) {
2109
+ checked = m[1].toLowerCase() === "on";
2110
+ }
2111
+ }
2112
+ const selector = b === "checklist-toggle" ? itemText.replace(/\s+(on|off)$/i, "").trim() : itemText;
2113
+ let msg = "";
2114
+ await st.updatePhase(resolved.phase.id, (phase) => {
2115
+ const task = phase.tasks.find((t) => t.id === resolved.task.id);
2116
+ if (!task) {
2117
+ msg = `Task not found: ${taskRef}`;
2118
+ return phase;
2119
+ }
2120
+ const ck = task.checklist ?? [];
2121
+ if (b === "checklist-add") {
2122
+ const item = addChecklistItem(ck, task.id, selector);
2123
+ ck.push(item);
2124
+ task.checklist = ck;
2125
+ msg = `Added C${item.number} "${item.title}" (${ck.length} items)`;
2126
+ }
2127
+ else if (b === "checklist-remove") {
2128
+ if (ck.length === 0) {
2129
+ msg = `Task "${task.title}" has no checklist.`;
2130
+ return phase;
2131
+ }
2132
+ const removed = removeChecklistItem(ck, selector);
2133
+ if (!removed) {
2134
+ msg = `No checklist item matching "${selector}".`;
2135
+ return phase;
2136
+ }
2137
+ msg = `Removed C${removed.number} "${removed.title}" (${ck.length} items left)`;
2138
+ }
2139
+ else {
2140
+ if (ck.length === 0) {
2141
+ msg = `Task "${task.title}" has no checklist.`;
2142
+ return phase;
2143
+ }
2144
+ const target = toggleChecklistItem(ck, selector, checked);
2145
+ if (!target) {
2146
+ msg = `No checklist item matching "${selector}".`;
2147
+ return phase;
2148
+ }
2149
+ const doneCount = ck.filter((i) => i.checked).length;
2150
+ msg = `C${target.number} "${target.title}" → ${target.checked ? "done" : "open"} (${doneCount}/${ck.length} checked)`;
2151
+ }
2152
+ task.updatedAt = nowISO();
2153
+ phase.updatedAt = nowISO();
2154
+ return phase;
2155
+ });
2156
+ await st.writeGenerated();
2157
+ ctx.ui.notify(`✅ ${msg}`, "info");
2158
+ return;
2159
+ }
2160
+ ctx.ui.notify(`Unknown task action "${b}". Try: add, show, discuss, delete, update, start, complete, checklist-add, checklist-remove, checklist-toggle`, "warning");
2088
2161
  return;
2089
2162
  }
2090
2163
  // ── discuss (legacy alias) ──
@@ -2904,6 +2977,7 @@ export default function planPiExtension(pi) {
2904
2977
  acceptedDecisions: [],
2905
2978
  phaseIds: [],
2906
2979
  dependsOn: [],
2980
+ statusLog: [],
2907
2981
  createdAt: now,
2908
2982
  updatedAt: now,
2909
2983
  };
@@ -3119,6 +3193,7 @@ export default function planPiExtension(pi) {
3119
3193
  handoff: "", handoffUpdatedAt: "",
3120
3194
  handoffReadAt: "",
3121
3195
  handoffHistory: [],
3196
+ statusLog: [],
3122
3197
  };
3123
3198
  await st.savePhase(phase);
3124
3199
  // Atomic: serialize concurrent phase_create linking to the same feature.
@@ -3335,6 +3410,7 @@ export default function planPiExtension(pi) {
3335
3410
  description: Type.String({ description: "REQUIRED — execution context: code references (file:line), current state vs desired state, structs/traits to modify, concrete implementation steps, edge cases. Not a one-liner. Prefix with 'design-only' for pre-implementation design tasks.", minLength: 50 }),
3336
3411
  status: Type.Optional(Type.String({ description: "Initial status. Default: planned" })),
3337
3412
  shortName: Type.Optional(Type.String({ description: "Short slug for the task id. Auto-derived from title if omitted." })),
3413
+ checklist: Type.Optional(Type.Array(Type.String(), { description: "Initial checklist items (plain strings). Seeded as C1, C2, … (unchecked)." })),
3338
3414
  }),
3339
3415
  async execute(_id, params, _signal, _onUpdate, ctx) {
3340
3416
  const st = await requirePlan(ctx);
@@ -3363,7 +3439,10 @@ export default function planPiExtension(pi) {
3363
3439
  statusLog: [],
3364
3440
  decisions: [],
3365
3441
  acceptedDecisions: [],
3366
- checklist: [], subtasks: [],
3442
+ checklist: params.checklist
3443
+ ? params.checklist.map((itemTitle, index) => ({ number: index + 1, id: createChecklistItemId(taskId, index + 1, itemTitle), title: itemTitle, checked: false }))
3444
+ : [],
3445
+ subtasks: [],
3367
3446
  dependsOn: [],
3368
3447
  startedAt: initialStatus === "in-progress" || initialStatus === "done" ? now : "",
3369
3448
  completedAt: initialStatus === "done" ? now : "",
@@ -3433,7 +3512,7 @@ export default function planPiExtension(pi) {
3433
3512
  if (params.acceptedDecisions !== undefined)
3434
3513
  task.acceptedDecisions = params.acceptedDecisions;
3435
3514
  if (params.checklist !== undefined) {
3436
- task.checklist = params.checklist.map((itemTitle, index) => ({
3515
+ task.checklist = params.checklist.map((itemTitle, index) => ({ number: index + 1,
3437
3516
  id: createChecklistItemId(task.id, index + 1, itemTitle),
3438
3517
  title: itemTitle,
3439
3518
  checked: false,
@@ -3582,6 +3661,125 @@ export default function planPiExtension(pi) {
3582
3661
  return { content: [{ type: "text", text: `✅ Task completed: ${completedTask.id} — ${completedTask.title} (done)` }], details: completedTask };
3583
3662
  },
3584
3663
  });
3664
+ pi.registerTool({
3665
+ name: "task_checklist_toggle",
3666
+ label: "Task Checklist Toggle",
3667
+ description: "Tick/untick a task checklist item (a 'step') without rewriting the whole list. Accepts the item as C{n} (e.g. C2), item id, or title (case-insensitive, first match). Pass checked=true/false to set explicitly, or omit to toggle. Use this instead of writing DONE in step titles. Items are numbered C1/C2… per task.",
3668
+ parameters: Type.Object({
3669
+ taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
3670
+ item: Type.String({ description: "Checklist item selector: C{n} (e.g. C2), item id, or title (case-insensitive)" }),
3671
+ checked: Type.Optional(Type.Boolean({ description: "Set explicitly: true=done, false=open. Omit to toggle." })),
3672
+ }),
3673
+ async execute(_id, params, _signal, _onUpdate, ctx) {
3674
+ const st = await requirePlan(ctx);
3675
+ if (!st)
3676
+ return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3677
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, params.taskId.trim());
3678
+ if (!found)
3679
+ return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
3680
+ let result = "";
3681
+ await st.updatePhase(found.phase.id, (phase) => {
3682
+ const task = phase.tasks.find((t) => t.id === found.task.id);
3683
+ if (!task) {
3684
+ result = `Task not found: ${params.taskId}`;
3685
+ return phase;
3686
+ }
3687
+ const ck = task.checklist ?? [];
3688
+ if (ck.length === 0) {
3689
+ result = `Task "${found.task.title}" has no checklist.`;
3690
+ return phase;
3691
+ }
3692
+ const target = toggleChecklistItem(ck, params.item, params.checked);
3693
+ if (!target) {
3694
+ result = `No checklist item matching "${params.item}".`;
3695
+ return phase;
3696
+ }
3697
+ task.updatedAt = nowISO();
3698
+ phase.updatedAt = nowISO();
3699
+ const doneCount = ck.filter((i) => i.checked).length;
3700
+ result = `C${target.number} "${target.title}" → ${target.checked ? "done" : "open"} (${doneCount}/${ck.length} checked)`;
3701
+ return phase;
3702
+ });
3703
+ await st.writeGenerated();
3704
+ return { content: [{ type: "text", text: `✅ ${result}` }], details: {} };
3705
+ },
3706
+ });
3707
+ pi.registerTool({
3708
+ name: "task_checklist_add",
3709
+ label: "Task Checklist Add",
3710
+ description: "Add a single checklist item (a 'step') to a task without rewriting the list. The new item is appended as C{n} (next progressive number, stable id, unchecked). Use this to subdivide a task into smaller steps INSTEAD of spawning sub-tasks — the checklist keeps description, notes, statusLog and steps concentrated in one task (sub-tasks disperse context). Also use for granular adds instead of replacing the whole checklist via task_update.",
3711
+ parameters: Type.Object({
3712
+ taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
3713
+ title: Type.String({ description: "Checklist item text (a single step)" }),
3714
+ }),
3715
+ async execute(_id, params, _signal, _onUpdate, ctx) {
3716
+ const st = await requirePlan(ctx);
3717
+ if (!st)
3718
+ return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3719
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, params.taskId.trim());
3720
+ if (!found)
3721
+ return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
3722
+ let result = "";
3723
+ await st.updatePhase(found.phase.id, (phase) => {
3724
+ const task = phase.tasks.find((t) => t.id === found.task.id);
3725
+ if (!task) {
3726
+ result = `Task not found: ${params.taskId}`;
3727
+ return phase;
3728
+ }
3729
+ const ck = task.checklist ?? [];
3730
+ const item = addChecklistItem(ck, task.id, params.title);
3731
+ ck.push(item);
3732
+ task.checklist = ck;
3733
+ task.updatedAt = nowISO();
3734
+ phase.updatedAt = nowISO();
3735
+ result = `Added C${item.number} "${item.title}" (${ck.length} items)`;
3736
+ return phase;
3737
+ });
3738
+ await st.writeGenerated();
3739
+ return { content: [{ type: "text", text: `✅ ${result}` }], details: {} };
3740
+ },
3741
+ });
3742
+ pi.registerTool({
3743
+ name: "task_checklist_remove",
3744
+ label: "Task Checklist Remove",
3745
+ description: "Remove a single checklist item (a 'step') from a task by C{n} (e.g. C2), item id, or title (case-insensitive). Remaining items are renumbered C1..Cn for readability; their stable ids are preserved. Use this for granular removes instead of replacing the whole checklist via task_update.",
3746
+ parameters: Type.Object({
3747
+ taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
3748
+ item: Type.String({ description: "Checklist item selector: C{n} (e.g. C2), item id, or title (case-insensitive)" }),
3749
+ }),
3750
+ async execute(_id, params, _signal, _onUpdate, ctx) {
3751
+ const st = await requirePlan(ctx);
3752
+ if (!st)
3753
+ return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3754
+ const found = findTaskByRef(await st.loadAllPhases(), (await st.loadFeatures()).features, params.taskId.trim());
3755
+ if (!found)
3756
+ return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
3757
+ let result = "";
3758
+ await st.updatePhase(found.phase.id, (phase) => {
3759
+ const task = phase.tasks.find((t) => t.id === found.task.id);
3760
+ if (!task) {
3761
+ result = `Task not found: ${params.taskId}`;
3762
+ return phase;
3763
+ }
3764
+ const ck = task.checklist ?? [];
3765
+ if (ck.length === 0) {
3766
+ result = `Task "${found.task.title}" has no checklist.`;
3767
+ return phase;
3768
+ }
3769
+ const removed = removeChecklistItem(ck, params.item);
3770
+ if (!removed) {
3771
+ result = `No checklist item matching "${params.item}".`;
3772
+ return phase;
3773
+ }
3774
+ task.updatedAt = nowISO();
3775
+ phase.updatedAt = nowISO();
3776
+ result = `Removed C${removed.number} "${removed.title}" (${ck.length} items left)`;
3777
+ return phase;
3778
+ });
3779
+ await st.writeGenerated();
3780
+ return { content: [{ type: "text", text: `✅ ${result}` }], details: {} };
3781
+ },
3782
+ });
3585
3783
  // ── Web lifecycle tools (agent-callable; parity with @agent-plan/mcp planner-web) ──
3586
3784
  // These let the agent manage the web dashboard directly, without relying
3587
3785
  // on the /planner slash command (which is not intercepted when the planner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/pi-adapter",
3
- "version": "0.2.19-next.12",
3
+ "version": "0.2.19-next.14",
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.19-next.12",
32
- "@agent-plan/server": "0.2.19-next.12"
31
+ "@agent-plan/core": "0.2.19-next.14",
32
+ "@agent-plan/server": "0.2.19-next.14"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@earendil-works/pi-coding-agent": "^0.74.0",