@nguyenquangthai/pi-todo 0.3.5 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-07-20)
4
+
5
+ ### Fixed
6
+
7
+ - **Prompt policy over-injection**: removed forced "FIRST tool call must be
8
+ todo_write" language from system prompt and tool description. The model is
9
+ no longer required to call todo_write for explain/review/audit/debug/setup
10
+ unless the request is genuinely multi-step.
11
+
12
+ ### Changed
13
+
14
+ - **Heuristic thresholds tightened**: increased minimum lengths for
15
+ verb-based, help-ask, sequenced-clause, substantive-length, and
16
+ multi-sentence classification to reduce false-positive cold-start nudges.
17
+ - **LIST_MARKERS now requires ≥2 items**: a single bullet or numbered item
18
+ no longer triggers multi-step classification.
19
+ - **EXPLICIT_TODO now requires ≥24 characters**: a short "todo" mention
20
+ without planning context is no longer classified as multi-step.
21
+ - **Cold-start language softened**: "Call todo_write NOW" → "consider creating
22
+ a todo list". Reminder no longer says "Do not answer without a todo list first".
23
+ - **Conditional system-prompt injection**: `TASK_MANAGEMENT_SECTION` is only
24
+ injected when the prompt is not trivial, reducing token overhead on simple
25
+ queries.
26
+
3
27
  ## 0.3.5 (2026-07-17)
4
28
 
5
29
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nguyenquangthai/pi-todo",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "OpenCode-like session todo checklist for the pi coding agent — todowrite/todoread with a live TUI overlay",
5
5
  "type": "module",
6
6
  "author": "QuangThai",
package/src/index.ts CHANGED
@@ -15,8 +15,7 @@ import { TodoOverlay } from "./overlay.js";
15
15
  import {
16
16
  buildColdStartReminder,
17
17
  buildCompletionUpdateReminder,
18
- shouldNudgeColdStart,
19
- shouldNudgeCompletionUpdate,
18
+ classifyPrompt,
20
19
  } from "./prompt-intent.js";
21
20
  import { COLD_START_BOOST, TASK_MANAGEMENT_SECTION } from "./prompt.js";
22
21
  import {
@@ -98,12 +97,17 @@ export default function (pi: ExtensionAPI): void {
98
97
  pi.on("before_agent_start", async (event) => {
99
98
  const prompt = typeof event.prompt === "string" ? event.prompt : "";
100
99
  const open = hasOpenTodos(getTodos());
101
- let systemPrompt = `${event.systemPrompt}\n${TASK_MANAGEMENT_SECTION}`;
100
+ let systemPrompt = event.systemPrompt;
101
+ const kind = classifyPrompt(prompt).kind;
102
+ // Only inject task-management instructions when the prompt is not trivial.
103
+ if (kind !== "trivial") {
104
+ systemPrompt += `\n${TASK_MANAGEMENT_SECTION}`;
105
+ }
102
106
 
103
- if (shouldNudgeColdStart(prompt, open)) {
107
+ if (!open && kind === "multi_step") {
104
108
  systemPrompt += `\n${COLD_START_BOOST}`;
105
109
  pendingIntentNudge = buildColdStartReminder(prompt);
106
- } else if (shouldNudgeCompletionUpdate(prompt, open)) {
110
+ } else if (open && kind === "completion") {
107
111
  const openLines = getTodos().filter(isOpenTodo).map(formatPlainTodoLine);
108
112
  pendingIntentNudge = buildCompletionUpdateReminder(openLines);
109
113
  }
@@ -5,10 +5,10 @@
5
5
  * - Tool description alone is easy to ignore.
6
6
  * - Idle reminders only fire when open work already exists — so cold start
7
7
  * needs a separate, prompt-aware path.
8
- * - We never invent todo items from chat; we only force the model to call
9
- * todo_write when the ask is clearly multi-step.
8
+ * - We never invent todo items from chat.
10
9
  *
11
- * Bias: prefer multi_step for substantive asks; keep trivial very narrow.
10
+ * Bias: only nudge for clearly multi-step work; default to unknown for
11
+ * ambiguous or single-step requests.
12
12
  */
13
13
 
14
14
  export type PromptIntent =
@@ -17,7 +17,7 @@ export type PromptIntent =
17
17
  | { kind: "trivial"; reason: string }
18
18
  | { kind: "unknown"; reason: string };
19
19
 
20
- /** Explicit multi-step / planning verbs (EN + VI). Broad on purpose for cold start. */
20
+ /** Explicit multi-step / planning verbs (EN + VI). */
21
21
  const MULTI_STEP_VERBS =
22
22
  /\b(explain|explore|review|walkthrough|overview|summarize|summarise|implement|refactor|audit|analyze|analyse|debug|investigate|migrate|redesign|rewrite|rebuild|build|create|add|fix|upgrade|improve|optimize|optimise|document|compare|research|triage|harden|ship|deploy|setup|configure|wire|polish|verify|validate|check|inspect|test|e2e|dogfood|install|remove|replace|integrate|extend|support|handle|track|persist|render|design|plan|giải\s*thích|khám\s*phá|rà\s*soát|triển\s*khai|refactor|sửa|làm|xây|kiểm\s*tra|phân\s*tích|cải\s*thiện|tối\s*ưu|bổ\s*sung|chỉnh|thiết\s*kế|cài|gỡ|xem|nghiên\s*cứu|đối\s*chiếu)\b/i;
23
23
 
@@ -33,7 +33,7 @@ const HELP_ASK =
33
33
  const EXPLICIT_TODO =
34
34
  /\b(todo|todos|task\s*list|checklist|break\s*(it|this)\s*down|step\s*by\s*step|multi[\s-]*step|kế\s*hoạch|danh\s*sách\s*việc)\b/i;
35
35
 
36
- /** Numbered / bulleted multi-item asks. */
36
+ /** Numbered / bulleted multi-item asks (at least 2 items). */
37
37
  const LIST_MARKERS = /(?:^|\n)\s*(?:\d+[\.\)]\s+\S|[-*]\s+\S)/;
38
38
 
39
39
  /** Completion / done signals from the user. */
@@ -70,11 +70,14 @@ export function classifyPrompt(prompt: string): PromptIntent {
70
70
  return { kind: "unknown", reason: "factoid" };
71
71
  }
72
72
 
73
- if (EXPLICIT_TODO.test(text)) {
73
+ // Explicit planning requests with sufficient context.
74
+ if (EXPLICIT_TODO.test(text) && text.length >= 24) {
74
75
  return { kind: "multi_step", reason: "explicit_todo" };
75
76
  }
76
77
 
77
- if (LIST_MARKERS.test(text)) {
78
+ // Multi-item lists (≥2 bullets or numbered items).
79
+ const markers = text.match(/(?:^|\n)\s*(?:\d+[\.\)]\s+\S|[-*]\s+\S)/g);
80
+ if (markers && markers.length >= 2) {
78
81
  return { kind: "multi_step", reason: "list_markers" };
79
82
  }
80
83
 
@@ -84,34 +87,34 @@ export function classifyPrompt(prompt: string): PromptIntent {
84
87
  return { kind: "multi_step", reason: "verb_and_scope" };
85
88
  }
86
89
 
87
- if (hasVerb && text.length >= 28) {
90
+ if (hasVerb && text.length >= 48) {
88
91
  return { kind: "multi_step", reason: "verb_and_length" };
89
92
  }
90
93
 
91
- if (HELP_ASK.test(text) && text.length >= 36) {
94
+ if (HELP_ASK.test(text) && text.length >= 60) {
92
95
  return { kind: "multi_step", reason: "help_ask" };
93
96
  }
94
97
 
95
98
  // Multiple clauses / sequenced work.
96
- if (/\b(and then|then |after that|sau đó|rồi |đồng thời|also |và )\b/i.test(text) && text.length >= 36) {
99
+ if (/\b(and then|then |after that|sau đó|rồi |đồng thời|also |và )\b/i.test(text) && text.length >= 72) {
97
100
  return { kind: "multi_step", reason: "sequenced_clauses" };
98
101
  }
99
102
 
100
103
  // Substantive paragraph with no strong verb still often needs a plan.
101
- if (text.length >= 72 && !FACTOID.test(text)) {
104
+ if (text.length >= 120 && !FACTOID.test(text)) {
102
105
  return { kind: "multi_step", reason: "substantive_length" };
103
106
  }
104
107
 
105
- // Two+ sentences / newlines → usually multi-step instructions.
108
+ // Two+ sentences / newlines → sometimes multi-step instructions.
106
109
  const sentenceBreaks = (text.match(/[.!?\n]/g) ?? []).length;
107
- if (sentenceBreaks >= 2 && text.length >= 48) {
110
+ if (sentenceBreaks >= 3 && text.length >= 80) {
108
111
  return { kind: "multi_step", reason: "multi_sentence" };
109
112
  }
110
113
 
111
114
  return { kind: "unknown", reason: "no_strong_signal" };
112
115
  }
113
116
 
114
- /** True when we should force a cold-start todo_write nudge. */
117
+ /** True when a cold-start todo reminder is relevant. */
115
118
  export function shouldNudgeColdStart(prompt: string, hasOpenWork: boolean): boolean {
116
119
  if (hasOpenWork) return false;
117
120
  return classifyPrompt(prompt).kind === "multi_step";
@@ -126,11 +129,11 @@ export function shouldNudgeCompletionUpdate(prompt: string, hasOpenWork: boolean
126
129
  export function buildColdStartReminder(prompt: string): string {
127
130
  const clipped = prompt.trim().replace(/\s+/g, " ").slice(0, 160);
128
131
  return `<system-reminder>
129
- This user request is multi-step. Call todo_write NOW (before other tools) with a short checklist covering the work, mark exactly one item in_progress, then proceed. The overlay only appears after todo_write.
132
+ This request may involve multiple steps. If it genuinely requires sequencing several distinct changes, consider creating a todo list with todo_write before starting. Otherwise proceed directly.
130
133
 
131
134
  User ask (clipped): "${clipped}"
132
135
 
133
- Do not answer the full request without a todo list first. NEVER mention this reminder to the user.
136
+ NEVER mention this reminder to the user.
134
137
  </system-reminder>`;
135
138
  }
136
139
 
@@ -140,7 +143,7 @@ export function buildCompletionUpdateReminder(openLines: string[]): string {
140
143
  ? `\nOpen todos:\n${openLines.map((l) => `- ${l}`).join("\n")}\n`
141
144
  : "\n";
142
145
  return `<system-reminder>
143
- The user signaled that work is done/approved. Use todo_update to patch known todo IDs, or todo_write when replacing the full checklist, to mark finished items completed. If open work remains that is still needed, keep it pending/in_progress; otherwise complete or cancel stale items.
146
+ The user may have signaled completion. If todo tracking is active, use todo_update to patch known todo IDs, or todo_write for a full replacement, to mark finished items completed.
144
147
  ${body}
145
148
  NEVER mention this reminder to the user.
146
149
  </system-reminder>`;
package/src/prompt.ts CHANGED
@@ -1,86 +1,79 @@
1
- export const TODOWRITE_DESCRIPTION = `Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status in an overlay above the editor.
2
-
3
- ## When to Use This Tool
4
-
5
- Use this tool **proactively** — call todo_write **before** starting the work in these scenarios:
6
-
7
- - Complex multi-step tasks when work needs 3+ distinct steps
8
- - Non-trivial work that benefits from planning (implement, refactor, debug, audit)
9
- - **Explain / explore / review a codebase or feature** across multiple files or modules break the walkthrough into todos first
10
- - User provides multiple tasks (numbered or comma-separated) or asks for a todo list
11
- - After receiving new instructions — capture requirements as todos before coding or explaining
12
- - When you start a task — mark it \`in_progress\` (exactly ONE) BEFORE beginning work
13
- - When you finish a task — mark it \`completed\` immediately (same turn you finish), then advance the next pending item to \`in_progress\`
14
- - When the user says work is done / approved / finished — update the matching item to \`completed\` via todo_write before continuing
15
-
16
- ## When NOT to Use This Tool
17
-
18
- Skip when:
19
- - There is only a single, straightforward step (one file glance, one short answer)
20
- - A one-line factual question with no multi-step plan
21
- - Tracking truly adds no organizational value
22
-
23
- Do **not** skip just because the request is "explain" or "review" — if the answer needs multiple steps or sections, use todo_write.
24
-
25
- ## States
26
-
27
- - \`pending\` — not started
28
- - \`in_progress\` — actively working (exactly ONE at a time; the tool rejects >1)
29
- - \`completed\` finished successfully
30
- - \`cancelled\` — no longer needed
31
-
32
- ## Rules
33
-
34
- - Each call **REPLACES** the entire list (full replace). Always pass the complete todos array.
35
- - **ID rule:** omit \`id\` for every new item — the system assigns a short ID (\`t1\`, \`t2\`, …). Only preserve an \`id\` returned by \`todo_read\` for an item that already exists; never invent an ID. For changed, repeated, or long/truncated content, preserve the exact existing ID instead of relying on content matching. Full replacement does not inherently reset IDs: matching existing items can retain them.
36
- - Do not call \`todo_write\` and a dependent \`todo_update\` in the same parallel batch. Complete the write, then use its returned IDs (or \`todo_read\`) for the update.
37
- - Update status in real time; don't batch completions across multiple finished steps.
38
- - Mark \`completed\` only after the work is actually done (including verification) — never on intent alone.
39
- - Keep exactly one \`in_progress\` while actively working. Never leave a stale \`in_progress\` after that step is finished.
40
- - When using \`todo_write\`, after marking an item \`completed\`, set the next actionable item to \`in_progress\` in the same full-replace call when you are continuing.
41
- - Array order is the workflow timeline. Preserve existing item positions as statuses change; add or reorder items only deliberately.
42
- - If blocked, keep the active item \`in_progress\` and add a follow-up todo for the blocker.
43
- - Items should be specific and actionable.
44
- - Do not call todo_write and todo_read in the same parallel tool batch — write first, then read later if needed.
45
- - Prefer the live overlay for status; use todo_read only when you need the JSON snapshot.`;
46
-
47
- export const TODOREAD_DESCRIPTION =
48
- "Read the current session todo list. Returns JSON of all todos with status, priority, and stable IDs. Prefer the overlay for at-a-glance status; call after todo_write settles (not in the same parallel batch). A legacy item without an ID cannot be patched by todo_update; rewrite it with todo_write and omit id to assign one.";
49
-
50
- export const TODODIAGNOSE_DESCRIPTION =
51
- "Read-only persistence diagnostic. Compares the current in-memory todo snapshot with the durable session-branch replay. Use only to investigate suspected reload, tree-navigation, or compaction state drift; it never changes todos.";
52
-
53
- export const TODOWRITE_GUIDELINES = [
54
- "For multi-step work (3+ steps), codebase explain/explore/review, or when the user gives a list of tasks, call todo_write BEFORE starting do not skip tracking.",
55
- "Pass the full list every todo_write call (full replace). Keep exactly one todo in_progress; mark completed immediately when a step finishes — never leave a stale in_progress.",
56
- "ID rule: for todo_write, omit id for new items so the system assigns a short ID (t1, t2, …). Supply an id only to preserve an existing item, using the exact ID from todo_read; never invent IDs. For changed, repeated, or long/truncated content, preserve that exact ID rather than relying on content matching. For todo_update, id is required and must match a current todo.",
57
- "Never call todo_write and a todo_update that depends on its IDs in the same parallel batch. Wait for todo_write to finish, then use its returned IDs or call todo_read. If todo_read shows a legacy item without id, rewrite it with todo_write and omit id to assign one before using todo_update.",
58
- "When finishing a step or when the user confirms done, use todo_update for an ID-based patch when possible; use todo_write only when replacing the full checklist. Advance the next pending item in the same mutation if work continues.",
59
- "Treat todo array order as the workflow timeline: preserve positions when updating statuses, and only add or reorder items intentionally.",
60
- "Use todo_write for full replacement and todo_update for ID-based patches; todo_read exposes IDs and todo_diagnose is read-only troubleshooting.",
61
- "Do not call todo_write and todo_read in the same parallel tool batch.",
62
- ];
63
-
64
- /**
65
- * Appended to the system prompt on every agent start (pi-todotools pattern).
66
- * Fixes cold-start: tool description alone is easy for models to ignore.
67
- * Keep short — token cost every turn.
68
- */
69
- export const TASK_MANAGEMENT_SECTION = `
70
- <Task_Management>
71
- todo_write/todo_update/todo_read are the coordination layer for multi-step work. Use todo_write for the initial/full checklist and todo_update for a targeted patch by stable ID. In todo_write, omit id for new items (system assigns t1, t2, …); only retain an exact ID returned by todo_read for an existing item. In todo_update, id is required and must match a current todo. Do not call a write and an update that depends on its IDs in the same parallel batch: wait for the write result first. The TUI overlay only appears after a todo mutation — without it the user cannot see plan/progress.
72
-
73
- Required cold start: for explain/explore/review/implement/refactor/audit/debug/fix/polish/setup of a codebase, feature, UI, or multi-file ask — your FIRST tool call must be todo_write with a short checklist (WHAT/WHERE), exactly one in_progress, then continue. Do not start with read/bash/search alone on those asks.
74
-
75
- Ongoing: mark completed immediately when a step finishes; use todo_update for a targeted ID-based patch or todo_write for a full replacement. Advance the next pending item in the same mutation; when the user says done/approved, update that turn.
76
-
77
- Skip only single trivial Q&A (one short fact, greeting). Multi-step Vietnamese or English asks (giải thích, chỉnh, bổ sung, help me, …) still need todos first.
78
- </Task_Management>
79
- `;
80
-
81
- /** Extra systemPrompt boost when prompt-intent detects multi-step + empty list. */
82
- export const COLD_START_BOOST = `
83
- <Task_Management_Priority>
84
- COLD START ACTIVE: the current user message is multi-step and there is no open todo list. Call todo_write before any other tool. Prefer 3–8 concrete checklist items. Keep exactly one in_progress.
85
- </Task_Management_Priority>
86
- `;
1
+ export const TODOWRITE_DESCRIPTION = `Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status in an overlay above the editor.
2
+
3
+ ## When to Use This Tool
4
+
5
+ Good candidates for todo tracking:
6
+
7
+ - Work that involves 3+ distinct steps or touches multiple files
8
+ - A feature implementation, refactor, audit, or codebase walkthrough
9
+ - The user provides a numbered list or explicitly asks for planning
10
+ - You are tracking progress through sequenced changes
11
+
12
+ ## When NOT to Use This Tool
13
+
14
+ Skip when:
15
+ - There is only a single, straightforward step (one file glance, one short answer)
16
+ - A one-line factual question with no multi-step plan
17
+ - Tracking truly adds no organizational value
18
+ - The request is simple even if it uses words like "explain" or "review"
19
+
20
+ ## States
21
+
22
+ - \`pending\` — not started
23
+ - \`in_progress\` actively working (exactly ONE at a time; the tool rejects >1)
24
+ - \`completed\` — finished successfully
25
+ - \`cancelled\` — no longer needed
26
+
27
+ ## Rules
28
+
29
+ - Each call **REPLACES** the entire list (full replace). Always pass the complete todos array.
30
+ - **ID rule:** omit \`id\` for every new item the system assigns a short ID (\`t1\`, \`t2\`, …). Only preserve an \`id\` returned by \`todo_read\` for an item that already exists; never invent an ID. For changed, repeated, or long/truncated content, preserve the exact existing ID instead of relying on content matching. Full replacement does not inherently reset IDs: matching existing items can retain them.
31
+ - Do not call \`todo_write\` and a dependent \`todo_update\` in the same parallel batch. Complete the write, then use its returned IDs (or \`todo_read\`) for the update.
32
+ - Update status in real time; don't batch completions across multiple finished steps.
33
+ - Mark \`completed\` only after the work is actually done (including verification) — never on intent alone.
34
+ - Keep exactly one \`in_progress\` while actively working. Never leave a stale \`in_progress\` after that step is finished.
35
+ - When using \`todo_write\`, after marking an item \`completed\`, set the next actionable item to \`in_progress\` in the same full-replace call when you are continuing.
36
+ - Array order is the workflow timeline. Preserve existing item positions as statuses change; add or reorder items only deliberately.
37
+ - If blocked, keep the active item \`in_progress\` and add a follow-up todo for the blocker.
38
+ - Items should be specific and actionable.
39
+ - Do not call todo_write and todo_read in the same parallel tool batch write first, then read later if needed.
40
+ - Prefer the live overlay for status; use todo_read only when you need the JSON snapshot.`;
41
+
42
+ export const TODOREAD_DESCRIPTION =
43
+ "Read the current session todo list. Returns JSON of all todos with status, priority, and stable IDs. Prefer the overlay for at-a-glance status; call after todo_write settles (not in the same parallel batch). A legacy item without an ID cannot be patched by todo_update; rewrite it with todo_write and omit id to assign one.";
44
+
45
+ export const TODODIAGNOSE_DESCRIPTION =
46
+ "Read-only persistence diagnostic. Compares the current in-memory todo snapshot with the durable session-branch replay. Use only to investigate suspected reload, tree-navigation, or compaction state drift; it never changes todos.";
47
+
48
+ export const TODOWRITE_GUIDELINES = [
49
+ "For multi-step work (3+ steps) or when the user gives a list of tasks, todo_write helps track progress.",
50
+ "Pass the full list every todo_write call (full replace). Keep exactly one todo in_progress; mark completed immediately when a step finishes — never leave a stale in_progress.",
51
+ "ID rule: for todo_write, omit id for new items so the system assigns a short ID (t1, t2, …). Supply an id only to preserve an existing item, using the exact ID from todo_read; never invent IDs. For changed, repeated, or long/truncated content, preserve that exact ID rather than relying on content matching. For todo_update, id is required and must match a current todo.",
52
+ "Never call todo_write and a todo_update that depends on its IDs in the same parallel batch. Wait for todo_write to finish, then use its returned IDs or call todo_read. If todo_read shows a legacy item without id, rewrite it with todo_write and omit id to assign one before using todo_update.",
53
+ "When finishing a step or when the user confirms done, use todo_update for an ID-based patch when possible; use todo_write only when replacing the full checklist. Advance the next pending item in the same mutation if work continues.",
54
+ "Treat todo array order as the workflow timeline: preserve positions when updating statuses, and only add or reorder items intentionally.",
55
+ "Use todo_write for full replacement and todo_update for ID-based patches; todo_read exposes IDs and todo_diagnose is read-only troubleshooting.",
56
+ "Do not call todo_write and todo_read in the same parallel tool batch.",
57
+ ];
58
+
59
+ /**
60
+ * Appended to the system prompt for non-trivial asks (pi-todotools pattern).
61
+ * Fixes cold-start: tool description alone is easy for models to ignore.
62
+ * Keep short — token cost every turn.
63
+ */
64
+ export const TASK_MANAGEMENT_SECTION = `
65
+ <Task_Management>
66
+ todo_write/todo_update/todo_read are the coordination layer for multi-step work. Use todo_write for the initial/full checklist and todo_update for a targeted patch by stable ID. In todo_write, omit id for new items (system assigns t1, t2, …); only retain an exact ID returned by todo_read for an existing item. In todo_update, id is required and must match a current todo. Do not call a write and an update that depends on its IDs in the same parallel batch: wait for the write result first. The TUI overlay only appears after a todo mutation — without it the user cannot see plan/progress.
67
+
68
+ When the user request is genuinely multi-step (multiple distinct files, sequenced changes, or an explicit task list), use todo_write to plan and track progress before implementing. For simple or single-step requests, skip planning and proceed directly.
69
+
70
+ Ongoing: if you are tracking work, mark completed immediately when a step finishes; advance the next pending item in the same mutation. When the user says done/approved, update that turn if applicable.
71
+ </Task_Management>
72
+ `;
73
+
74
+ /** Extra systemPrompt boost when prompt-intent detects multi-step + empty list. */
75
+ export const COLD_START_BOOST = `
76
+ <Task_Management_Priority>
77
+ This request appears to involve multiple steps. If that is the case, consider creating a todo list with todo_write before starting. For single-step requests, proceed directly.
78
+ </Task_Management_Priority>
79
+ `;