@agent-plan/pi-adapter 0.2.19-next.5 → 0.2.19-next.6

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;AA6uBtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAk7F9D"}
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,CAs9F9D"}
package/dist/index.js CHANGED
@@ -512,6 +512,9 @@ function statusIcon(status) {
512
512
  };
513
513
  return icons[status] ?? "❓";
514
514
  }
515
+ function pad(n) {
516
+ return String(n).padStart(3, "0");
517
+ }
515
518
  function applyTaskLifecycleDates(task, nextStatus, now) {
516
519
  const previousStatus = task.status;
517
520
  if (nextStatus === "in-progress" && !task.startedAt) {
@@ -1379,7 +1382,7 @@ export default function planPiExtension(pi) {
1379
1382
  return doc;
1380
1383
  });
1381
1384
  await st.writeGenerated();
1382
- ctx.ui.notify(`Feature created: ${feature.id} — ${feature.name}`, "info");
1385
+ ctx.ui.notify(`Feature created: ${formatFeatureRef(feature.number)}${feature.shortId ? ` +MI+ ${feature.shortId}` : ""} +EM+ ${feature.name}`, "info");
1383
1386
  return;
1384
1387
  }
1385
1388
  if (b === "show") {
@@ -1460,7 +1463,7 @@ export default function planPiExtension(pi) {
1460
1463
  });
1461
1464
  feature = updatedDoc.features.find((entry) => entry.id === featureId) ?? feature;
1462
1465
  await st.writeGenerated();
1463
- ctx.ui.notify(`Feature updated: ${feature.id} — ${feature.name} (${feature.status})`, "info");
1466
+ ctx.ui.notify(`Feature updated: ${formatFeatureRef(feature.number)}${feature.shortId ? ` +MI+ ${feature.shortId}` : ""} +EM+ ${feature.name} (${feature.status})`, "info");
1464
1467
  return;
1465
1468
  }
1466
1469
  if (b === "delete") {
@@ -2823,38 +2826,44 @@ export default function planPiExtension(pi) {
2823
2826
  pi.registerTool({
2824
2827
  name: "feature_list",
2825
2828
  label: "Feature List",
2826
- description: "List all features with their ids, statuses, and phase counts.",
2827
- parameters: Type.Object({}),
2828
- async execute(_id, _params, _signal, _onUpdate, ctx) {
2829
+ description: "List features (compact: F00x · shortId — name (status; N phases, M tasks)). Use this to discover refs cheaply — do NOT read .planner/ files or plan_get full=true to find entities.",
2830
+ parameters: Type.Object({ featureRef: Type.Optional(Type.String({ description: "Optional: filter to one feature (F00x/shortId/UUID/name)" })) }),
2831
+ async execute(_id, params, _signal, _onUpdate, ctx) {
2829
2832
  const st = await requirePlan(ctx);
2830
2833
  if (!st)
2831
2834
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
2832
- const features = (await st.loadFeatures()).features;
2835
+ const all = (await st.loadFeatures()).features;
2836
+ const ref = params.featureRef?.trim();
2837
+ const matchF = (f) => !ref || formatFeatureRef(f.number) === ref || f.shortId === ref || f.id === ref || f.name.toLowerCase() === ref.toLowerCase();
2838
+ const features = all.filter(matchF);
2833
2839
  const phases = await st.loadAllPhases();
2834
2840
  const summary = features.map((f) => {
2835
2841
  const fp = phases.filter((p) => p.featureId === f.id);
2836
- return `- ${statusIcon(f.status)} ${f.id} — ${f.name} (${fp.length} phases, ${fp.reduce((t, p) => t + p.tasks.length, 0)} tasks)`;
2842
+ return `- ${formatFeatureRef(f.number)}${f.shortId ? ` · ${f.shortId}` : ""} — ${f.name} (${f.status}; ${fp.length} phases, ${fp.reduce((t, p) => t + p.tasks.length, 0)} tasks)`;
2837
2843
  }).join("\n");
2838
- return { content: [{ type: "text", text: summary || "No features" }], details: { features } };
2844
+ return { content: [{ type: "text", text: summary || "No features" }], details: {} };
2839
2845
  },
2840
2846
  });
2841
2847
  pi.registerTool({
2842
2848
  name: "feature_get",
2843
2849
  label: "Feature Get",
2844
- description: "Read full details of a feature, including its phases and their tasks.",
2850
+ description: "Show a feature. Compact identity by default (saves tokens); pass full=true to include the description.",
2845
2851
  parameters: Type.Object({
2846
- featureId: Type.String({ description: "Feature ID, e.g. feature-001-dynamic-header" }),
2852
+ featureId: Type.String({ description: "Feature ref: F00x, shortId, UUID, or name" }),
2853
+ full: Type.Optional(Type.Boolean({ description: "If true, include the feature description. Default: compact identity only." })),
2847
2854
  }),
2848
2855
  async execute(_id, params, _signal, _onUpdate, ctx) {
2849
2856
  const st = await requirePlan(ctx);
2850
2857
  if (!st)
2851
2858
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
2852
2859
  const features = (await st.loadFeatures()).features;
2853
- const feature = features.find((f) => f.id === params.featureId);
2860
+ const ref = params.featureId.trim();
2861
+ const feature = features.find((f) => formatFeatureRef(f.number) === ref || f.shortId === ref || f.id === ref || f.name.toLowerCase() === ref.toLowerCase());
2854
2862
  if (!feature)
2855
- return { content: [{ type: "text", text: `Feature not found: ${params.featureId}` }], details: {} };
2863
+ return { content: [{ type: "text", text: `Feature not found: ${ref}` }], details: {} };
2856
2864
  const phases = (await st.loadAllPhases()).filter((p) => p.featureId === feature.id);
2857
- return { content: [{ type: "text", text: `${statusIcon(feature.status)} ${feature.name} (${feature.id}) — ${phases.length} phases` }], details: { feature, phases } };
2865
+ const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases)`;
2866
+ return { content: [{ type: "text", text: params.full ? `${summary}\n\n${feature.description || ""}` : summary }], details: {} };
2858
2867
  },
2859
2868
  });
2860
2869
  pi.registerTool({
@@ -3020,40 +3029,52 @@ export default function planPiExtension(pi) {
3020
3029
  pi.registerTool({
3021
3030
  name: "phase_list",
3022
3031
  label: "Phase List",
3023
- description: "List all phases (optionally filtered by featureId) with id, status, and task counts.",
3032
+ description: "List phases (compact: F00x/P00x · shortId — title (status; N tasks) [F00x]). Filters: featureRef, status. Cheap discovery — do NOT read .planner/ files or plan_get full=true.",
3024
3033
  parameters: Type.Object({
3025
- featureId: Type.Optional(Type.String({ description: "If provided, only list phases of this feature" })),
3034
+ featureRef: Type.Optional(Type.String({ description: "Optional: filter to one feature (F00x/shortId/UUID/name)" })),
3035
+ status: Type.Optional(Type.String({ description: "Optional: filter by status name" })),
3026
3036
  }),
3027
3037
  async execute(_id, params, _signal, _onUpdate, ctx) {
3028
3038
  const st = await requirePlan(ctx);
3029
3039
  if (!st)
3030
3040
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3041
+ const features = (await st.loadFeatures()).features;
3031
3042
  let phases = await st.loadAllPhases();
3032
- if (params.featureId)
3033
- phases = phases.filter((p) => p.featureId === params.featureId);
3034
- const summary = phases.map((p) => `- ${statusIcon(p.status)} ${p.id} — ${p.title} (${p.tasks.length} tasks, feature ${p.featureId ?? "none"})`).join("\n");
3035
- return { content: [{ type: "text", text: summary || "No phases" }], details: { phases } };
3043
+ const fref = params.featureRef?.trim();
3044
+ if (fref) {
3045
+ const f = features.find((x) => formatFeatureRef(x.number) === fref || x.shortId === fref || x.id === fref || x.name.toLowerCase() === fref.toLowerCase());
3046
+ if (!f)
3047
+ return { content: [{ type: "text", text: `Feature not found: ${fref}` }], details: {} };
3048
+ phases = phases.filter((p) => p.featureId === f.id);
3049
+ }
3050
+ if (params.status)
3051
+ phases = phases.filter((p) => p.status === params.status);
3052
+ const summary = phases.map((p) => {
3053
+ const fNum = featureNumberOfPhase(p, features);
3054
+ const fTag = fNum !== undefined ? ` [F${pad(fNum)}]` : "";
3055
+ return `- ${formatPhaseRef(p.number, fNum)}${p.shortId ? ` · ${p.shortId}` : ""} — ${p.title} (${p.status}; ${p.tasks.length} tasks)${fTag}`;
3056
+ }).join("\n");
3057
+ return { content: [{ type: "text", text: summary || "No phases" }], details: {} };
3036
3058
  },
3037
3059
  });
3038
3060
  pi.registerTool({
3039
3061
  name: "phase_get",
3040
3062
  label: "Phase Get",
3041
- description: "Read full details of a phase, including its tasks.",
3063
+ description: "Show a phase. Compact identity by default (saves tokens); pass full=true to include the description.",
3042
3064
  parameters: Type.Object({
3043
- phaseId: Type.String({ description: "Phase ID, e.g. phase-01-setup" }),
3065
+ phaseId: Type.String({ description: "Phase ref: F00x/P00x, bare P00x (global), shortId, UUID, or title" }),
3066
+ full: Type.Optional(Type.Boolean({ description: "If true, include the phase description. Default: compact identity only." })),
3044
3067
  }),
3045
3068
  async execute(_id, params, _signal, _onUpdate, ctx) {
3046
3069
  const st = await requirePlan(ctx);
3047
3070
  if (!st)
3048
3071
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3049
- let phase;
3050
- try {
3051
- phase = await st.loadPhase(params.phaseId);
3052
- }
3053
- catch {
3072
+ const features = (await st.loadFeatures()).features;
3073
+ const phase = findPhaseByRef(await st.loadAllPhases(), features, params.phaseId.trim());
3074
+ if (!phase)
3054
3075
  return { content: [{ type: "text", text: `Phase not found: ${params.phaseId}` }], details: {} };
3055
- }
3056
- return { content: [{ type: "text", text: `${statusIcon(phase.status)} ${phase.title} (${phase.id}) — ${phase.tasks.length} tasks` }], details: phase };
3076
+ const summary = `${phase.title} — ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}${phase.shortId ? ` · ${phase.shortId}` : ""} (${phase.status}; ${phase.tasks.length} tasks)`;
3077
+ return { content: [{ type: "text", text: params.full ? `${summary}\n\n${phase.description || ""}` : summary }], details: {} };
3057
3078
  },
3058
3079
  });
3059
3080
  pi.registerTool({
@@ -3242,42 +3263,64 @@ export default function planPiExtension(pi) {
3242
3263
  pi.registerTool({
3243
3264
  name: "task_list",
3244
3265
  label: "Task List",
3245
- description: "List tasks of a phase with id, status, and title.",
3266
+ description: "List tasks (compact: F00x/P00x/T00x · shortId — title (status)). Filters: featureRef, phaseRef, status. Omit all to list every task. Cheap discovery — do NOT read .planner/ files or plan_get full=true.",
3246
3267
  parameters: Type.Object({
3247
- phaseId: Type.String({ description: "Phase ref: F00x/P00x, bare P00x (global), shortId, UUID, or title" }),
3268
+ featureRef: Type.Optional(Type.String({})),
3269
+ phaseRef: Type.Optional(Type.String({})),
3270
+ status: Type.Optional(Type.String({ description: "Optional: filter by status name" })),
3248
3271
  }),
3249
3272
  async execute(_id, params, _signal, _onUpdate, ctx) {
3250
3273
  const st = await requirePlan(ctx);
3251
3274
  if (!st)
3252
3275
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3253
- let phase;
3254
- try {
3255
- phase = await st.loadPhase(params.phaseId);
3256
- }
3257
- catch {
3258
- return { content: [{ type: "text", text: `Phase not found: ${params.phaseId}` }], details: {} };
3259
- }
3260
- const summary = phase.tasks.map((t) => `- ${statusIcon(t.status)} ${t.id} — ${t.title}`).join("\n");
3261
- return { content: [{ type: "text", text: summary || "No tasks" }], details: { tasks: phase.tasks } };
3276
+ const features = (await st.loadFeatures()).features;
3277
+ let phases = await st.loadAllPhases();
3278
+ const fref = params.featureRef?.trim();
3279
+ if (fref) {
3280
+ const f = features.find((x) => formatFeatureRef(x.number) === fref || x.shortId === fref || x.id === fref || x.name.toLowerCase() === fref.toLowerCase());
3281
+ if (!f)
3282
+ return { content: [{ type: "text", text: `Feature not found: ${fref}` }], details: {} };
3283
+ phases = phases.filter((p) => p.featureId === f.id);
3284
+ }
3285
+ const pref = params.phaseRef?.trim();
3286
+ if (pref) {
3287
+ const p = findPhaseByRef(phases, features, pref);
3288
+ if (!p)
3289
+ return { content: [{ type: "text", text: `Phase not found: ${pref}` }], details: {} };
3290
+ phases = [p];
3291
+ }
3292
+ const out = [];
3293
+ for (const phase of phases) {
3294
+ for (const task of phase.tasks) {
3295
+ if (params.status && task.status !== params.status)
3296
+ continue;
3297
+ out.push(`- ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}/T${pad(task.number)}${task.shortId ? ` · ${task.shortId}` : ""} — ${task.title} (${task.status})`);
3298
+ }
3299
+ }
3300
+ return { content: [{ type: "text", text: out.join("\n") || "No tasks" }], details: {} };
3262
3301
  },
3263
3302
  });
3264
3303
  pi.registerTool({
3265
3304
  name: "task_get",
3266
3305
  label: "Task Get",
3267
- description: "Read full details of a single task.",
3306
+ description: "Show a task. Compact identity by default (saves tokens); pass full=true to include the description + statusLog.",
3268
3307
  parameters: Type.Object({
3269
3308
  taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title" }),
3309
+ full: Type.Optional(Type.Boolean({ description: "If true, include the task description + statusLog. Default: compact identity only." })),
3270
3310
  }),
3271
3311
  async execute(_id, params, _signal, _onUpdate, ctx) {
3272
3312
  const st = await requirePlan(ctx);
3273
3313
  if (!st)
3274
3314
  return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
3275
- const phases = await st.loadAllPhases();
3276
- const phase = phases.find((p) => p.tasks.some((t) => t.id === params.taskId));
3277
- const task = phase?.tasks.find((t) => t.id === params.taskId);
3278
- if (!task || !phase)
3315
+ const features = (await st.loadFeatures()).features;
3316
+ const found = findTaskByRef(await st.loadAllPhases(), features, params.taskId.trim());
3317
+ if (!found)
3279
3318
  return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
3280
- return { content: [{ type: "text", text: `${statusIcon(task.status)} ${task.title} (${task.id}) — phase ${phase.id}` }], details: { task, phaseId: phase.id } };
3319
+ const summary = `${found.task.title} — ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))}/T${pad(found.task.number)}${found.task.shortId ? ` · ${found.task.shortId}` : ""} (${found.task.status})`;
3320
+ if (!params.full)
3321
+ return { content: [{ type: "text", text: summary }], details: {} };
3322
+ const log = (found.task.statusLog ?? []).map((e) => ` - ${e.date.slice(0, 10)} ${e.title}`).join("\n");
3323
+ return { content: [{ type: "text", text: `${summary}\n\n${found.task.description || ""}${log ? `\n\nStatus log:\n${log}` : ""}` }], details: {} };
3281
3324
  },
3282
3325
  });
3283
3326
  pi.registerTool({
@@ -3776,7 +3819,7 @@ export default function planPiExtension(pi) {
3776
3819
  "3. IMMEDIATE SYNC: Update task status AT THE EXACT MOMENT of transition. Start = task_start NOW. Done = task_complete NOW. Blocked = task_update with motivation NOW. Never batch status updates.",
3777
3820
  "4. BLOCKED MOTIVATION: Transitions to blocked/canceled/rejected/deferred/waiting/planned(from non-planned) MUST include a detailed 'motivation' parameter. Write it as if the next person has zero context.",
3778
3821
  "5. NO SHORTCUTS: If a tool blocks you, follow the protocol. Bypasses are for emergencies only, not for convenience.",
3779
- "6. ENTITY REFERENCES: When you mention a feature/phase/task in chat, PREFER its 5-char shortId (globally unique, e.g. UUXD1). The composite T00x is per-phase and AMBIGUOUS unless fully qualified as T00x(P00x/F00x) — do NOT use bare 'T00x'. NEVER a raw UUID like bd6ed366. Tool responses already show shortId + compositeRef; reuse them.",
3822
+ "6. ENTITY REFERENCES & FAST LOOKUP: Feature/Phase/Task numbers are GLOBALLY unique (one P003, one T007 in the whole plan) — bare P00x/T00x is a valid unambiguous ref. PREFER the 5-char shortId (e.g. UUXD1) or composite F00x/P00x/T00x in chat; NEVER a raw UUID. To FIND an entity: call feature_list / phase_list / task_list (compact, ~2K tokens for the whole plan) — do NOT read .planner/*.json files or plan_get full=true (~32K tokens) to locate an entity. To ACT: pass the ref directly to the action tool (task_start/complete/update/phase_update accept F00x/P00x/T00x, bare P00x/T00x, shortId, UUID, title). To read a description: call task_get / phase_get / feature_get with full=true for that ONE entity. Tool responses already show shortId + compositeRef; reuse them.",
3780
3823
  "═══════════════════════════════════════════════════════════════",
3781
3824
  "",
3782
3825
  // ── Project details ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-plan/pi-adapter",
3
- "version": "0.2.19-next.5",
3
+ "version": "0.2.19-next.6",
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.5",
32
- "@agent-plan/server": "0.2.19-next.5"
31
+ "@agent-plan/core": "0.2.19-next.6",
32
+ "@agent-plan/server": "0.2.19-next.6"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@earendil-works/pi-coding-agent": "^0.74.0",