pi-goal-x 0.16.0 → 0.16.1

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.
@@ -202,7 +202,8 @@ export function validateTaskSkip(args: {
202
202
  const task = args.goal.taskList.tasks.find((t) => t.id === args.taskId);
203
203
  if (!task) return { ok: false, message: `Task "${args.taskId}" not found.` };
204
204
  if (task.status === "complete") return { ok: false, message: `Task "${args.taskId}" is already complete.` };
205
- if (task.status === "skipped") return { ok: false, message: `Task "${args.taskId}" was already skipped.` };
205
+ // Skipped tasks toggle via the executor; reason is only required for first-time skips.
206
+ if (task.status === "skipped") return { ok: true };
206
207
  if (!args.reason.trim()) return { ok: false, message: "skip_task requires a non-empty reason." };
207
208
  return { ok: true };
208
209
  }
@@ -2555,6 +2555,7 @@ export default function goalExtension(pi: ExtensionAPI): void {
2555
2555
  promptGuidelines: [
2556
2556
  "Use propose_task_list after a goal is confirmed, on the first continuation turn, if the objective naturally decomposes into trackable milestones.",
2557
2557
  "Do not add a task list for simple, single-step goals.",
2558
+ "If a task list already exists on the goal, only call propose_task_list to restructure it when (a) the user explicitly asks for a change, or (b) the goal objective or requirements have structurally changed. Do not restructure the task list autonomously.",
2558
2559
  "Existing tasks with matching IDs preserve their status/evidence/timestamps; new IDs start as pending; removed IDs are gone.",
2559
2560
  "After confirmation the turn stops; the next continuation will arrive automatically.",
2560
2561
  "You may optionally specify a verificationContract per task to define what verification evidence is required before completing that task.",
@@ -2811,9 +2812,12 @@ export default function goalExtension(pi: ExtensionAPI): void {
2811
2812
  label: "Skip Task",
2812
2813
  description: "Skip a pending task in the current goal's task list. Does NOT stop the turn — the agent can continue working.",
2813
2814
  promptSnippet: "Skip a task with a reason. Does not stop the turn.",
2814
- promptGuidelines: [
2815
+ promptGuidelines: [
2815
2816
  "Use skip_task to mark a task as skipped with a required reason.",
2816
2817
  "The turn does NOT stop after skip_task — you may continue with other work.",
2818
+ "Only skip a task when the user explicitly asks you to, or when the task directly contradicts a hard constraint (e.g. an impossible requirement).",
2819
+ "Do NOT autonomously skip tasks to avoid work, or because they look optional, inconvenient, or out of scope. When in doubt, ask the user first.",
2820
+ "If skip_task is called on an already-skipped task, it toggles back to pending (unskip).",
2817
2821
  ],
2818
2822
  parameters: Type.Object({
2819
2823
  taskId: Type.String({ description: "Task id to skip" }),
@@ -2837,8 +2841,15 @@ export default function goalExtension(pi: ExtensionAPI): void {
2837
2841
  }
2838
2842
  if (!state.goal?.taskList) throw new Error("Task list disappeared during task skip.");
2839
2843
  const now = nowIso();
2844
+ const wasAlreadySkipped = findTaskInTree(state.goal.taskList.tasks, params.taskId)?.status === "skipped";
2845
+
2840
2846
  const updatedTasks = updateTaskInTree(state.goal.taskList.tasks, params.taskId, (t) => {
2841
- // Cascade skip to all subtasks (full subtasks only)
2847
+ if (t.status === "skipped") {
2848
+ // Toggle back to pending — clear skip state, do NOT cascade to subtasks
2849
+ const { skippedAt, skipReason, ...rest } = t;
2850
+ return { ...rest, status: "pending" as const };
2851
+ }
2852
+ // First-time skip: cascade to all subtasks (full subtasks only)
2842
2853
  const base = { ...t, status: "skipped" as const, skippedAt: now, skipReason: params.reason.trim() };
2843
2854
  if (t.subtasks && t.subtasks.length > 0 && !t.lightweightSubtasks) {
2844
2855
  return skipAllSubtasks(base, now, params.reason.trim());
@@ -2856,22 +2867,33 @@ export default function goalExtension(pi: ExtensionAPI): void {
2856
2867
  syncGoalTools();
2857
2868
  updateUI(ctx);
2858
2869
 
2859
- // Append ledger event
2870
+ // Append ledger event
2860
2871
  try {
2861
- appendGoalEvent(ctx, {
2862
- type: "task_skipped",
2863
- goalId: state.goal.id,
2864
- taskId: params.taskId,
2865
- reason: params.reason.trim(),
2866
- at: now,
2867
- });
2872
+ if (wasAlreadySkipped) {
2873
+ appendGoalEvent(ctx, {
2874
+ type: "task_skipped",
2875
+ goalId: state.goal.id,
2876
+ taskId: params.taskId,
2877
+ reason: "unskipped (toggle via skip_task)",
2878
+ at: now,
2879
+ });
2880
+ } else {
2881
+ appendGoalEvent(ctx, {
2882
+ type: "task_skipped",
2883
+ goalId: state.goal.id,
2884
+ taskId: params.taskId,
2885
+ reason: params.reason.trim(),
2886
+ at: now,
2887
+ });
2888
+ }
2868
2889
  } catch {
2869
2890
  // Ledger failure should not block task skip
2870
2891
  }
2871
2892
 
2872
2893
  const taskSummary = buildTaskSummary(state.goal.taskList!);
2894
+ const action = wasAlreadySkipped ? "unsikpped" : "skipped";
2873
2895
  return {
2874
- content: [{ type: "text", text: `${params.taskId} skipped. ${taskSummary}.` }],
2896
+ content: [{ type: "text", text: `${params.taskId} ${action}. ${taskSummary}.` }],
2875
2897
  details: goalDetails(state.goal),
2876
2898
  };
2877
2899
  },
@@ -130,10 +130,12 @@ ${untrustedObjectiveBlock(goal)}
130
130
 
131
131
  Available work tools for pursuing the active goal include write, read, bash, and edit. Use those tools directly for file and shell work; do not call get_goal repeatedly to discover tools.
132
132
 
133
- If the objective naturally decomposes into trackable milestones, you may call propose_task_list to set up structured tasks. Do not add a task list for simple, single-step goals.
133
+ After goal confirmation, you may call propose_task_list once to set up an initial task list if the objective decomposes into trackable milestones. If a task list already exists, only restructure it when the user asks or the goal structurally changes — do not restructure autonomously. Do not add a task list for simple, single-step goals.
134
134
 
135
135
  To ask the user a structured question (e.g. when the user's spec changes and you need to clarify before updating the goal), use goal_question. It opens a question dialog and returns the user's answer as tool output. Use plain conversation for simple clarifications.
136
136
 
137
+ Task skipping restrictions: Only skip a task when the user explicitly asks you to, or when the task directly contradicts a hard constraint (e.g. an impossible requirement). Do NOT autonomously skip tasks to avoid work, or because they look optional, inconvenient, or out of scope. When in doubt, ask the user first. Calling skip_task on an already-skipped task toggles it back to pending (unskip).
138
+
137
139
  Keep this goal in force until it is actually achieved. Do not pause for confirmation just because a phase, chapter, file, or checklist item is finished. At each natural stopping point, compare every explicit requirement with concrete evidence from the workspace/session. If the objective is complete, call complete_goal with status=complete and provide a verificationSummary; complete_goal will launch an independent pi auditor agent and only archive if that auditor returns <approved/>. If it is not complete, choose the next concrete action and do it.
138
140
 
139
141
  The completion auditor is independent and semantic, not a paperwork checklist. It may inspect files and command output, and it will reject scaffold-only, alpha, template, proxy-metric, or weakly verified completions with <disapproved/>.
@@ -168,7 +170,9 @@ export function continuationPrompt(goal: GoalRecord, settings?: GoalSettings): s
168
170
  "",
169
171
  "Available work tools for pursuing the active goal include write, read, bash, and edit. Use those tools directly for file and shell work; do not call get_goal repeatedly to discover tools.",
170
172
  "",
171
- "To ask the user a structured question (e.g. when the user's spec changes and you need to clarify before updating the goal), use goal_question. It opens a question dialog and returns the user's answer as tool output. Use plain conversation for simple clarifications.",
173
+ "To ask the user a structured question (e.g. when the user's spec changes and you need to clarify before updating the goal), use goal_question. It opens a question dialog and returns the user's answer as tool output. Use plain conversation for simple clarifications.",
174
+ "",
175
+ "Task skipping restrictions: Only skip a task when the user explicitly asks you to, or when the task directly contradicts a hard constraint (e.g. an impossible requirement). Do NOT autonomously skip tasks to avoid work, or because they look optional, inconvenient, or out of scope. When in doubt, ask the user first. Calling skip_task on an already-skipped task toggles it back to pending (unskip).",
172
176
  "",
173
177
  "Avoid repeating work that is already done. Choose the next concrete action toward the objective.",
174
178
  "",
@@ -46,13 +46,6 @@ export async function showEscapeDialog(
46
46
  },
47
47
  ];
48
48
 
49
- /** Build a bordered line: fits exactly `innerWidth` visible chars between ││ */
50
- function line(leftContent: string): string {
51
- const vis = visibleWidth(leftContent);
52
- const fill = innerWidth - vis;
53
- return accent("│") + leftContent + (fill > 0 ? " ".repeat(fill) : "") + accent("│");
54
- }
55
-
56
49
  const accent = (s: string) => theme.fg("accent", s);
57
50
  const dim = (s: string) => theme.fg("dim", s);
58
51
  const warning = (s: string) => theme.fg("warning", s);
@@ -70,6 +63,14 @@ export async function showEscapeDialog(
70
63
  render(width: number): string[] {
71
64
  const termWidth = Math.min(width, 80);
72
65
  const innerWidth = Math.min(termWidth, 64) - 2; // inner content width between ││
66
+
67
+ /** Build a bordered line: fits exactly `innerWidth` visible chars between ││ */
68
+ function line(leftContent: string): string {
69
+ const vis = visibleWidth(leftContent);
70
+ const fill = innerWidth - vis;
71
+ return accent("│") + leftContent + (fill > 0 ? " ".repeat(fill) : "") + accent("│");
72
+ }
73
+
73
74
  const horizLine = "─".repeat(innerWidth);
74
75
  const lines: string[] = [];
75
76
  const p = " "; // left padding inside the border
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-x",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Goal mode extension for pi: persistent long-running objectives, /goal-set drafting, Sisyphus prompt style, autoContinue, and an above-editor status overlay. Fork of @capyup/pi-goal.",
5
5
  "license": "MIT",
6
6
  "author": "pi-goal-x contributors",