@agent-plan/pi-adapter 0.2.19-next.13 → 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,CA0gG9D"}
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)" },
@@ -1770,7 +1773,7 @@ export default function planPiExtension(pi) {
1770
1773
  // ═══════════════════════════════════════════════════════════════
1771
1774
  if (a === "task") {
1772
1775
  if (!b) {
1773
- 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");
1774
1777
  return;
1775
1778
  }
1776
1779
  if (b === "add") {
@@ -2086,7 +2089,75 @@ export default function planPiExtension(pi) {
2086
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");
2087
2090
  return;
2088
2091
  }
2089
- 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");
2090
2161
  return;
2091
2162
  }
2092
2163
  // ── discuss (legacy alias) ──
@@ -3339,6 +3410,7 @@ export default function planPiExtension(pi) {
3339
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 }),
3340
3411
  status: Type.Optional(Type.String({ description: "Initial status. Default: planned" })),
3341
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)." })),
3342
3414
  }),
3343
3415
  async execute(_id, params, _signal, _onUpdate, ctx) {
3344
3416
  const st = await requirePlan(ctx);
@@ -3367,7 +3439,10 @@ export default function planPiExtension(pi) {
3367
3439
  statusLog: [],
3368
3440
  decisions: [],
3369
3441
  acceptedDecisions: [],
3370
- 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: [],
3371
3446
  dependsOn: [],
3372
3447
  startedAt: initialStatus === "in-progress" || initialStatus === "done" ? now : "",
3373
3448
  completedAt: initialStatus === "done" ? now : "",
@@ -3614,32 +3689,91 @@ export default function planPiExtension(pi) {
3614
3689
  result = `Task "${found.task.title}" has no checklist.`;
3615
3690
  return phase;
3616
3691
  }
3617
- let target;
3618
- const cMatch = params.item.match(/^C(\d+)$/i);
3619
- if (cMatch) {
3620
- const idx = parseInt(cMatch[1], 10) - 1;
3621
- if (idx < 0 || idx >= ck.length) {
3622
- result = `C${idx + 1} not found (1..${ck.length}).`;
3623
- return phase;
3624
- }
3625
- target = ck[idx];
3626
- }
3627
- else {
3628
- const needle = params.item.trim().toLowerCase();
3629
- target = ck.find((i) => i.id === params.item)
3630
- ?? ck.find((i) => i.title.trim().toLowerCase() === needle)
3631
- ?? ck.find((i) => i.title.trim().toLowerCase().includes(needle));
3632
- }
3692
+ const target = toggleChecklistItem(ck, params.item, params.checked);
3633
3693
  if (!target) {
3634
3694
  result = `No checklist item matching "${params.item}".`;
3635
3695
  return phase;
3636
3696
  }
3637
- const newValue = params.checked ?? !target.checked;
3638
- target.checked = newValue;
3639
3697
  task.updatedAt = nowISO();
3640
3698
  phase.updatedAt = nowISO();
3641
3699
  const doneCount = ck.filter((i) => i.checked).length;
3642
- result = `C${target.number} "${target.title}" → ${newValue ? "done" : "open"} (${doneCount}/${ck.length} checked)`;
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)`;
3643
3777
  return phase;
3644
3778
  });
3645
3779
  await st.writeGenerated();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/pi-adapter",
3
- "version": "0.2.19-next.13",
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.13",
32
- "@agent-plan/server": "0.2.19-next.13"
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",