summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-11 20:38:14 +0300
committerPaul Buetow <paul@buetow.org>2026-04-11 20:38:14 +0300
commit5f63e9139ec0b4a4ae30651f5a7a2f77c66a689c (patch)
tree34af4f63fb526186f58884077a2e08e6ccb1715a
parenta133bc0355a03b088d63334c64b8a31253602d81 (diff)
Pi extensions: document and invoke task CLI as ~/go/bin/do
Use DO_CLI_REF and resolveDoExecutable in agent-plan-mode; accept both do and ~/go/bin/do in bash guards. Ask-mode shares matchDoInvocation. Nemotron/Qwen tool discipline points to ~/go/bin/do done. Made-with: Cursor
-rw-r--r--pi/agent/extensions/agent-plan-mode/README.md16
-rw-r--r--pi/agent/extensions/agent-plan-mode/index.ts51
-rw-r--r--pi/agent/extensions/agent-plan-mode/utils.ts22
-rw-r--r--pi/agent/extensions/ask-mode/utils.ts4
-rw-r--r--pi/agent/extensions/nemotron-tool-repair/index.ts6
5 files changed, 62 insertions, 37 deletions
diff --git a/pi/agent/extensions/agent-plan-mode/README.md b/pi/agent/extensions/agent-plan-mode/README.md
index 2470016..4ba700a 100644
--- a/pi/agent/extensions/agent-plan-mode/README.md
+++ b/pi/agent/extensions/agent-plan-mode/README.md
@@ -26,7 +26,7 @@ todo list.
- `/task-update <selector> :: <new description>`
Replace a task description.
- `/task-modify <selector> :: <mods>`
- Apply `do modify` arguments to a task.
+ Apply `~/go/bin/do modify` arguments to a task.
- `/tasks`
Show started and `+READY` tasks for the current repo.
- `/task-next [run]`
@@ -40,10 +40,10 @@ todo list.
## Rules
-- all task operations go through `do`, never raw `task`
-- tasks are scoped to the current git repo through the `do` wrapper
+- all task operations go through `~/go/bin/do`, never raw `task`
+- tasks are scoped to the current git repo through the `~/go/bin/do` wrapper
- use alias IDs for task references
-- `do add` prints `created task <alias-id>`, and subsequent task operations should keep using that alias ID
+- `~/go/bin/do add` prints `created task <alias-id>`, and subsequent task operations should keep using that alias ID
- planning mode is read-only by design
- the extracted plan is session-local, so `/plan`, the planning prompt,
`/plan-create-tasks`, and `/plan-exit` should happen in the same interactive
@@ -160,13 +160,13 @@ Analyze the repo and give me a Plan: for the next implementation slice.
## Notes And Limits
- Planning mode is read-only by design.
-- All task operations still go through `do`, never raw `task`.
-- `do` uses subcommand syntax. It is not a natural-language
- task assistant and should never be called like `do agent-task-management ...`.
+- All task operations still go through `~/go/bin/do`, never raw `task`.
+- `~/go/bin/do` uses subcommand syntax. It is not a natural-language
+ task assistant and should never be called like `~/go/bin/do agent-task-management ...`.
- Execution mode injects the current task back into the agent prompt
so the model works against the real task rather than an in-memory checklist.
- Execution mode treats the focused task as the already-selected starting
- point and blocks repeated identical `do info <id>` lookups until the
+ point and blocks repeated identical `~/go/bin/do info <id>` lookups until the
agent has moved on to repo inspection, implementation, tests, review, or a
different command.
- Full `/plan` state is not meant to be passed across unrelated one-shot `pi -p`
diff --git a/pi/agent/extensions/agent-plan-mode/index.ts b/pi/agent/extensions/agent-plan-mode/index.ts
index 4d93596..7514b82 100644
--- a/pi/agent/extensions/agent-plan-mode/index.ts
+++ b/pi/agent/extensions/agent-plan-mode/index.ts
@@ -5,13 +5,16 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
import { Key } from "@mariozechner/pi-tui";
import {
containsRawTaskCommand,
+ DO_CLI_REF,
dedupePlanItems,
extractPlanItems,
formatTaskDetails,
formatTaskLine,
isSafePlanCommand,
+ matchDoInvocation,
normalizeTaskText,
parseCreatedTaskId,
+ resolveDoExecutable,
stripAnsi,
type AgentTask,
type PlanItem,
@@ -51,21 +54,23 @@ function repeatedCurrentTaskLookupKey(command: string, currentTaskId?: string):
if (!currentTaskId) return undefined;
const normalized = normalizeCommandText(command);
- if (!/^do(?:\s|$)/.test(normalized)) return undefined;
+ const inv = matchDoInvocation(normalized);
+ if (!inv) return undefined;
if (isMutatingDoCommand(normalized)) return undefined;
- if (!new RegExp(`^do(?:\\s+--json)?\\s+info\\s+["']?${escapeRegExp(currentTaskId)}["']?$`).test(normalized)) {
- return undefined;
- }
+
+ const rest = inv.rest.trim();
+ const infoRe = new RegExp(`^(?:--json\\s+)?info\\s+["']?${escapeRegExp(currentTaskId)}["']?$`);
+ if (!infoRe.test(rest)) return undefined;
return normalized;
}
function malformedDoReason(command: string): string | undefined {
const normalized = normalizeCommandText(command);
- if (!/^do(?:\s|$)/.test(normalized)) return undefined;
+ if (!matchDoInvocation(normalized)) return undefined;
if (/\bagent-task-management\b/.test(normalized)) {
- return "The 'do' command uses subcommand syntax. Do not pass the skill name or natural-language workflow text to it. Use concrete do subcommands such as 'do list start.any:', 'do ready', 'do info <id>', 'do annotate <id> \"note\"', 'do modify <id> priority:H', or 'do done <id>'.";
+ return `The '${DO_CLI_REF}' command uses subcommand syntax. Do not pass the skill name or natural-language workflow text to it. Use concrete ${DO_CLI_REF} subcommands such as '${DO_CLI_REF} list start.any:', '${DO_CLI_REF} ready', '${DO_CLI_REF} info <id>', '${DO_CLI_REF} annotate <id> \"note\"', '${DO_CLI_REF} modify <id> priority:H', or '${DO_CLI_REF} done <id>'.`;
}
return undefined;
@@ -168,7 +173,7 @@ export default function agentPlanModeExtension(pi: ExtensionAPI): void {
ctx: ExtensionContext,
signal?: AbortSignal,
): Promise<{ stdout: string; stderr: string; code: number }> {
- return runCommand("do", ["--json", ...args], ctx, signal);
+ return runCommand(resolveDoExecutable(), ["--json", ...args], ctx, signal);
}
async function getProjectName(ctx: ExtensionContext): Promise<string> {
@@ -506,7 +511,7 @@ export default function agentPlanModeExtension(pi: ExtensionAPI): void {
if (runNow) {
pi.sendUserMessage(
- `Work on the current task for project ${projectName}. Use do for all task operations. Current task ID: ${task.id ?? "?"}.`,
+ `Work on the current task for project ${projectName}. Use ${DO_CLI_REF} for all task operations. Current task ID: ${task.id ?? "?"}.`,
);
}
}
@@ -578,7 +583,7 @@ export default function agentPlanModeExtension(pi: ExtensionAPI): void {
});
pi.registerCommand("task-modify", {
- description: "Run do modify args: /task-modify <selector> :: <mods>",
+ description: `Run ${DO_CLI_REF} modify args: /task-modify <selector> :: <mods>`,
handler: async (args, ctx) => {
const parsed = parseSelectorAndPayload(args);
if (!parsed) {
@@ -615,7 +620,7 @@ ${formatTaskDetails(currentTask)}
Workflow:
1. Treat the current focused task above as the already-selected starting point for this run.
-2. Only use do to load project-scoped tasks when the current task is missing, blocked, completed, or you are ready to pick the next task.
+2. Only use ${DO_CLI_REF} to load project-scoped tasks when the current task is missing, blocked, completed, or you are ready to pick the next task.
3. Use priority first, then urgency, as the stable ordering rule. Use the requested selection strategy only as a tie-breaker or framing hint.
4. Start and execute the chosen task.
5. Annotate meaningful implementation progress back to the task using alias IDs.
@@ -627,13 +632,13 @@ Workflow:
11. If blocked, annotate the blocker to the task and stop.
Rules:
-- Never use raw task; always use do.
-- 'do' is a CLI tool for task management, not a natural-language interface and not a skill runner.
-- Valid examples: 'do ready', 'do list start.any:', 'do info <id>', 'do annotate <id> \"note\"', 'do modify <id> priority:H', 'do done <id>'.
-- Invalid examples: 'do agent-task-management ...', 'do list tasks', 'do show task 298', or any other natural-language phrasing.
+- Never use raw task; always use ${DO_CLI_REF}.
+- '${DO_CLI_REF}' is a CLI tool for task management, not a natural-language interface and not a skill runner.
+- Valid examples: '${DO_CLI_REF} ready', '${DO_CLI_REF} list start.any:', '${DO_CLI_REF} info <id>', '${DO_CLI_REF} annotate <id> \"note\"', '${DO_CLI_REF} modify <id> priority:H', '${DO_CLI_REF} done <id>'.
+- Invalid examples: '${DO_CLI_REF} agent-task-management ...', '${DO_CLI_REF} list tasks', '${DO_CLI_REF} show task 298', or any other natural-language phrasing.
- Scope all work to project:${projectName} +agent tasks only.
- Use alias IDs for all long-lived references.
-- Do not repeat the same do lookup for the current task unless task state may have changed or required information is still missing.
+- Do not repeat the same ${DO_CLI_REF} lookup for the current task unless task state may have changed or required information is still missing.
- After one task lookup, move into repo inspection, implementation, testing, review, or annotation before refreshing task data again.
- Do not ask the user to choose a task unless there is a real ambiguity or risk.
- Keep working autonomously until the workflow reaches a stop condition.
@@ -722,7 +727,7 @@ Begin with the current focused task now. Do not re-check the task list immediate
if (containsRawTaskCommand(command)) {
return {
block: true,
- reason: "Use 'do ...' for all task operations. Raw 'task' is blocked by agent-plan-mode.",
+ reason: `Use '${DO_CLI_REF} ...' for all task operations. Raw 'task' is blocked by agent-plan-mode.`,
};
}
@@ -758,13 +763,13 @@ You are in planning mode for project ${projectName}.
Rules:
- Use read, bash, grep, find, ls for exploration.
-- For task operations, always use 'do ...'. Never use raw 'task'. All do operations are allowed (add, annotate, modify, done, start, stop, etc.).
+- For task operations, always use '${DO_CLI_REF} ...'. Never use raw 'task'. All ${DO_CLI_REF} operations are allowed (add, annotate, modify, done, start, stop, etc.).
- You may write or edit files only inside ${plansDir}. Create that directory first if it does not exist: mkdir -p ${plansDir}
- Write one plan markdown file there (e.g. ${plansDir}/<project>.md) describing the overall picture, goals, and task structure.
- Do NOT write any files inside the current project directory.
- Do NOT overwrite an existing plan file that belongs to a different plan. If this is a new, unrelated plan, create a new file with a distinct name.
- Once you write or open a plan file, it becomes the active plan for this session. Stick to that file unless explicitly asked to switch.
-- For every task created with 'do add', reuse the returned alias ID directly and annotate it with a reference to the plan file: 'do annotate <id> "See ${plansDir}/<project>.md for overall context"'.
+- For every task created with '${DO_CLI_REF} add', reuse the returned alias ID directly and annotate it with a reference to the plan file: '${DO_CLI_REF} annotate <id> "See ${plansDir}/<project>.md for overall context"'.
- Read existing started tasks first; if none, inspect the next READY tasks.
- Avoid duplicating tasks that already exist.
@@ -794,16 +799,16 @@ Plan:
Project: ${projectName}
Use the task workflow rules below:
-- Use 'do ...' for all task operations. Never use raw 'task'.
-- 'do' is a CLI tool. It does not understand the skill name or natural-language requests.
-- Valid examples: 'do list start.any:', 'do ready', 'do info <id>', 'do annotate <id> \"note\"', 'do modify <id> priority:H', 'do done <id>'.
-- Invalid examples: 'do agent-task-management ...', 'do list tasks', 'do show task 298', or any other natural-language phrasing.
+- Use '${DO_CLI_REF} ...' for all task operations. Never use raw 'task'.
+- '${DO_CLI_REF}' is a CLI tool. It does not understand the skill name or natural-language requests.
+- Valid examples: '${DO_CLI_REF} list start.any:', '${DO_CLI_REF} ready', '${DO_CLI_REF} info <id>', '${DO_CLI_REF} annotate <id> \"note\"', '${DO_CLI_REF} modify <id> priority:H', '${DO_CLI_REF} done <id>'.
+- Invalid examples: '${DO_CLI_REF} agent-task-management ...', '${DO_CLI_REF} list tasks', '${DO_CLI_REF} show task 298', or any other natural-language phrasing.
- Continue an already-started task before starting a new one.
- Use alias IDs for long-lived references and follow-up commands.
- The current task below is already the selected task for this turn. Do not immediately query the same ID again unless required details are missing or task state changed.
- After one task lookup, move to repo inspection or implementation work before refreshing task data again.
- Do not mark a task done until implementation, tests, and commit are complete.
-- Annotate meaningful progress back to the task with 'do annotate <id> "note"' when appropriate.
+- Annotate meaningful progress back to the task with '${DO_CLI_REF} annotate <id> "note"' when appropriate.
- Self-review first, then if the subagent tool is available use it for an independent fresh-context review before the task is marked done.
Current task:
diff --git a/pi/agent/extensions/agent-plan-mode/utils.ts b/pi/agent/extensions/agent-plan-mode/utils.ts
index 7a1f30a..3aeccbf 100644
--- a/pi/agent/extensions/agent-plan-mode/utils.ts
+++ b/pi/agent/extensions/agent-plan-mode/utils.ts
@@ -1,3 +1,21 @@
+/** Shown in prompts/docs for Pi skills and extensions (task CLI). */
+export const DO_CLI_REF = "~/go/bin/do";
+
+export function resolveDoExecutable(): string {
+ return `${process.env.HOME ?? ""}/go/bin/do`;
+}
+
+const DO_INVOCATION_PREFIXES = ["~/go/bin/do", "do"] as const;
+
+/** Bash may use `do` or the full `~/go/bin/do` path. */
+export function matchDoInvocation(trimmed: string): { rest: string } | undefined {
+ for (const prefix of DO_INVOCATION_PREFIXES) {
+ if (trimmed === prefix) return { rest: "" };
+ if (trimmed.startsWith(`${prefix} `)) return { rest: trimmed.slice(prefix.length + 1) };
+ }
+ return undefined;
+}
+
export interface PlanItem {
step: number;
text: string;
@@ -144,14 +162,14 @@ export function containsRawTaskCommand(command: string): boolean {
export function isSafeDoCommand(command: string): boolean {
const trimmed = command.trim();
- if (!trimmed.startsWith("do ")) return false;
+ if (!matchDoInvocation(trimmed)) return false;
if (containsRawTaskCommand(trimmed)) return false;
if (/[;&]/.test(trimmed) || /(^|[^|])\|([^|]|$)/.test(trimmed)) return false;
return !MUTATING_TASK_PATTERNS.some((pattern) => pattern.test(trimmed));
}
function isDoCommand(command: string): boolean {
- return command.trim().startsWith("do ") || command.trim() === "do";
+ return matchDoInvocation(command.trim()) !== undefined;
}
export function isSafePlanCommand(command: string): boolean {
diff --git a/pi/agent/extensions/ask-mode/utils.ts b/pi/agent/extensions/ask-mode/utils.ts
index 353c70a..b3c9ce1 100644
--- a/pi/agent/extensions/ask-mode/utils.ts
+++ b/pi/agent/extensions/ask-mode/utils.ts
@@ -1,3 +1,5 @@
+import { matchDoInvocation } from "../agent-plan-mode/utils.js";
+
const DESTRUCTIVE_PATTERNS = [
/\brm\b/i,
/\brmdir\b/i,
@@ -106,7 +108,7 @@ const MUTATING_DO_PATTERNS = [
function isReadOnlyDoCommand(command: string): boolean {
const trimmed = command.trim();
- if (!trimmed.startsWith("do ") && trimmed !== "do") return false;
+ if (!matchDoInvocation(trimmed)) return false;
if (/[;&]/.test(trimmed) || /(^|[^|])\|([^|]|$)/.test(trimmed)) return false;
return !MUTATING_DO_PATTERNS.some((pattern) => pattern.test(trimmed));
}
diff --git a/pi/agent/extensions/nemotron-tool-repair/index.ts b/pi/agent/extensions/nemotron-tool-repair/index.ts
index 64de6b2..0fb868c 100644
--- a/pi/agent/extensions/nemotron-tool-repair/index.ts
+++ b/pi/agent/extensions/nemotron-tool-repair/index.ts
@@ -36,15 +36,15 @@ Additional tool-use discipline for this model:
- Do not emit example tool-call markup or pseudo-tool syntax for the user to read.
- Emit at most one tool invocation at a time, then wait for the tool result.
- After a tool result, continue from that result instead of restating the plan.
-- There is no "submit" tool. When your task is complete, respond with your final answer directly. If you are working on a tracked task, mark it done via bash: do done <id>.
+- There is no "submit" tool. When your task is complete, respond with your final answer directly. If you are working on a tracked task, mark it done via bash: ~/go/bin/do done <id>.
`.trim();
// Qwen Coder models are trained on agent frameworks that include a "submit" tool
// as a task-completion signal. Since no such tool exists here, calling it causes
-// a "Tool submit not found" error. Redirect to a final answer or do done instead.
+// a "Tool submit not found" error. Redirect to a final answer or ~/go/bin/do done instead.
const QWEN_TOOL_DISCIPLINE = `
Additional tool-use discipline for this model:
-- There is no "submit" tool. When your task is complete, respond with your final answer directly. If you are working on a tracked task, mark it done via bash: do done <id>.
+- There is no "submit" tool. When your task is complete, respond with your final answer directly. If you are working on a tracked task, mark it done via bash: ~/go/bin/do done <id>.
`.trim();
interface FileModelConfig {