pi-goal-x 0.17.0 → 0.18.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.
- package/extensions/goal-draft.ts +45 -17
- package/extensions/goal-questionnaire.ts +104 -13
- package/extensions/goal.ts +294 -0
- package/extensions/widgets/goal-widget.ts +69 -2
- package/package.json +1 -1
- package/docs/CHANGELOG.md +0 -145
package/extensions/goal-draft.ts
CHANGED
|
@@ -85,19 +85,29 @@ export function buildDraftConfirmationText(args: {
|
|
|
85
85
|
}): string {
|
|
86
86
|
const lines: string[] = [];
|
|
87
87
|
const modeLabel = args.focus === "sisyphus" ? "Sisyphus (prompt/criteria style)" : "Normal goal";
|
|
88
|
-
lines.push("Goal draft ready for confirmation.");
|
|
88
|
+
lines.push("● Goal draft ready for confirmation.");
|
|
89
89
|
lines.push("");
|
|
90
|
-
lines.push("Draft
|
|
91
|
-
lines.push(
|
|
92
|
-
lines.push(
|
|
90
|
+
lines.push("─── Draft Details ───");
|
|
91
|
+
lines.push(`│ Mode: ${modeLabel}`);
|
|
92
|
+
lines.push(`│ Auto-continue: ${args.autoContinue ? "yes" : "no"}`);
|
|
93
93
|
lines.push("");
|
|
94
|
-
lines.push("Original
|
|
94
|
+
lines.push("─── Original Topic ───");
|
|
95
95
|
lines.push("");
|
|
96
|
-
|
|
96
|
+
for (const topicLine of args.originalTopic.trim().split("\n")) {
|
|
97
|
+
if (topicLine.trim()) lines.push(`│ ${topicLine}`);
|
|
98
|
+
}
|
|
97
99
|
lines.push("");
|
|
98
|
-
lines.push("Proposed
|
|
100
|
+
lines.push("─── Proposed Goal ───");
|
|
99
101
|
lines.push("");
|
|
100
|
-
|
|
102
|
+
for (const objLine of args.objective.split("\n")) {
|
|
103
|
+
const trimmed = objLine.trim();
|
|
104
|
+
if (!trimmed) continue;
|
|
105
|
+
if (trimmed.startsWith("│")) {
|
|
106
|
+
lines.push(objLine);
|
|
107
|
+
} else {
|
|
108
|
+
lines.push(`│ ${objLine}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
101
111
|
return lines.join("\n");
|
|
102
112
|
}
|
|
103
113
|
|
|
@@ -109,22 +119,40 @@ export function buildTweakConfirmationText(args: {
|
|
|
109
119
|
}): string {
|
|
110
120
|
const lines: string[] = [];
|
|
111
121
|
const modeLabel = args.sisyphus ? "Sisyphus (prompt/criteria style)" : "Normal goal";
|
|
112
|
-
lines.push("Goal tweak ready for confirmation.");
|
|
122
|
+
lines.push("● Goal tweak ready for confirmation.");
|
|
113
123
|
lines.push("");
|
|
114
|
-
lines.push("Draft
|
|
115
|
-
lines.push(
|
|
124
|
+
lines.push("─── Draft Details ───");
|
|
125
|
+
lines.push(`│ Mode: ${modeLabel}`);
|
|
116
126
|
lines.push("");
|
|
117
|
-
lines.push("Change
|
|
127
|
+
lines.push("─── Change ───");
|
|
118
128
|
lines.push("");
|
|
119
|
-
|
|
129
|
+
for (const changeLine of args.changeSummary.split("\n")) {
|
|
130
|
+
if (changeLine.trim()) lines.push(`│ ${changeLine}`);
|
|
131
|
+
}
|
|
120
132
|
lines.push("");
|
|
121
|
-
lines.push("Current
|
|
133
|
+
lines.push("─── Current Objective ───");
|
|
122
134
|
lines.push("");
|
|
123
|
-
|
|
135
|
+
for (const curLine of args.currentObjective.split("\n")) {
|
|
136
|
+
const trimmed = curLine.trim();
|
|
137
|
+
if (!trimmed) continue;
|
|
138
|
+
if (trimmed.startsWith("│")) {
|
|
139
|
+
lines.push(curLine);
|
|
140
|
+
} else {
|
|
141
|
+
lines.push(`│ ${curLine}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
124
144
|
lines.push("");
|
|
125
|
-
lines.push("Proposed
|
|
145
|
+
lines.push("─── Proposed New Objective ───");
|
|
126
146
|
lines.push("");
|
|
127
|
-
|
|
147
|
+
for (const newLine of args.newObjective.split("\n")) {
|
|
148
|
+
const trimmed = newLine.trim();
|
|
149
|
+
if (!trimmed) continue;
|
|
150
|
+
if (trimmed.startsWith("│")) {
|
|
151
|
+
lines.push(newLine);
|
|
152
|
+
} else {
|
|
153
|
+
lines.push(`│ ${newLine}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
128
156
|
return lines.join("\n");
|
|
129
157
|
}
|
|
130
158
|
|
|
@@ -302,8 +302,6 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
302
302
|
if (matchesKey(data, Key.escape)) submit(true);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
const MAX_CONTEXT_LINES = 12; // prevent viewport jumping by capping context display
|
|
306
|
-
|
|
307
305
|
function render(width: number): string[] {
|
|
308
306
|
if (cachedLines) return cachedLines;
|
|
309
307
|
const safeWidth = Math.max(20, width);
|
|
@@ -312,16 +310,109 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
312
310
|
const opts = displayOptions();
|
|
313
311
|
const add = (s: string) => lines.push(truncateToWidth(s, safeWidth, "…", true));
|
|
314
312
|
const addWrapped = (s: string) => lines.push(...wrapTextWithAnsi(s, safeWidth));
|
|
313
|
+
/**
|
|
314
|
+
* Wraps a pipe-prefixed line and prepends "│ " to continuation lines
|
|
315
|
+
* so wrapped content stays within the ASCII box.
|
|
316
|
+
*/
|
|
317
|
+
const addWrappedPipe = (styledLine: string) => {
|
|
318
|
+
const wrapped = wrapTextWithAnsi(styledLine, safeWidth);
|
|
319
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
320
|
+
lines.push(i === 0 ? wrapped[i] : "│ " + wrapped[i]);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/** Render context lines with per-line styling. No truncation. */
|
|
325
|
+
const renderContextLines = (context: string): void => {
|
|
326
|
+
const rawLines = context.split("\n");
|
|
327
|
+
for (const rawLine of rawLines) {
|
|
328
|
+
const trimmed = rawLine.trim();
|
|
329
|
+
// Empty line — preserve as spacing
|
|
330
|
+
if (!trimmed) {
|
|
331
|
+
lines.push("");
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 1. Announcement header — "● Goal draft/tweak ready for confirmation."
|
|
336
|
+
if (/^● Goal (draft|tweak) ready for confirmation\.$/.test(trimmed)) {
|
|
337
|
+
addWrapped(theme.fg("accent", rawLine));
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 2. Section marker — "─── Name ───" → full-width box-drawing header
|
|
342
|
+
const sectionMatch = trimmed.match(/^───\s+(.+?)\s+───$/);
|
|
343
|
+
if (sectionMatch) {
|
|
344
|
+
const sectionName = sectionMatch[1];
|
|
345
|
+
const namePart = ` ${sectionName} `;
|
|
346
|
+
const left = "┌─";
|
|
347
|
+
const right = "─┐";
|
|
348
|
+
const fill = Math.max(0, safeWidth - 2 - visibleWidth(left + namePart + right));
|
|
349
|
+
add(theme.fg("accent", left + namePart + "─".repeat(fill) + right));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 3. Lines with │ prefix come from buildDraftConfirmationText / buildTweakConfirmationText.
|
|
354
|
+
if (trimmed.startsWith("│")) {
|
|
355
|
+
const afterPipe = trimmed.slice(1).trim();
|
|
356
|
+
// 3a. Task checkbox under │ prefix — detect before key-value to avoid
|
|
357
|
+
// "[x] t1: ..." being misinterpreted as a key-value pair.
|
|
358
|
+
const pipeTaskMatch = afterPipe.match(/^(\[.\])(\s+)(.+)$/);
|
|
359
|
+
if (pipeTaskMatch) {
|
|
360
|
+
const bracket = pipeTaskMatch[1];
|
|
361
|
+
const sep = pipeTaskMatch[2];
|
|
362
|
+
const rest = pipeTaskMatch[3];
|
|
363
|
+
// Preserve inner whitespace between │ and the task marker (e.g. " " in "│ [x]...")
|
|
364
|
+
const pipeContent = trimmed.slice(1);
|
|
365
|
+
const innerWs = pipeContent.slice(0, pipeContent.length - pipeContent.trimStart().length);
|
|
366
|
+
const linePrefix = "│" + innerWs;
|
|
367
|
+
const color = bracket === "[x]" ? "success" : "warning";
|
|
368
|
+
addWrappedPipe(linePrefix + theme.fg(color, bracket) + sep + theme.fg("muted", rest));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
// 3b. Key-value content (e.g. "│ Mode: Normal goal", "│ Auto-continue: yes")
|
|
372
|
+
if (afterPipe.includes(": ")) {
|
|
373
|
+
const colonIdx = afterPipe.indexOf(": ");
|
|
374
|
+
const val = afterPipe.slice(colonIdx + 2).trim();
|
|
375
|
+
const keyPart = rawLine.slice(0, rawLine.indexOf(afterPipe) + colonIdx + 2);
|
|
376
|
+
if (val === "yes" || val === "no") {
|
|
377
|
+
addWrappedPipe(theme.fg("muted", keyPart) + theme.fg(val === "yes" ? "success" : "warning", val));
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
addWrappedPipe(theme.fg("muted", rawLine));
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// 3c. Generic content under │ prefix (topic, goal text, etc.)
|
|
384
|
+
addWrappedPipe(theme.fg("muted", rawLine));
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 4. Goal objective structure lines — detected before task checkboxes
|
|
389
|
+
// because === Goal could overlap with ─── markers but we already checked those.
|
|
390
|
+
const GOAL_SECTION_RE = /^(=== (Goal|Sisyphus Goal) ===|Objective:|Success criteria:|Boundaries:|Constraints:|Verification contract:|If blocked:)/;
|
|
391
|
+
if (GOAL_SECTION_RE.test(trimmed)) {
|
|
392
|
+
addWrapped(theme.fg("accent", rawLine));
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 5. Actual box-drawing borders (┌ └ ├ └ ┐ ┤ ┘ ─) — NOT │ which is handled above
|
|
397
|
+
if (/^[┌├└┐┤┘─]/.test(trimmed)) {
|
|
398
|
+
addWrapped(theme.fg("dim", rawLine));
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 6. Task checkbox item — "[ ] ...", "[x] ...", or "[~] ..." (with optional indent)
|
|
403
|
+
const checkMatch = trimmed.match(/^(\[.\])(\s+)(.+)$/);
|
|
404
|
+
if (checkMatch) {
|
|
405
|
+
const bracket = checkMatch[1];
|
|
406
|
+
const sep = checkMatch[2];
|
|
407
|
+
const rest = checkMatch[3];
|
|
408
|
+
const indent = rawLine.slice(0, rawLine.length - trimmed.length);
|
|
409
|
+
const color = bracket === "[x]" ? "success" : "warning";
|
|
410
|
+
addWrapped(indent + theme.fg(color, bracket) + sep + theme.fg("muted", rest));
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
315
413
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const wrapped = wrapTextWithAnsi(s, safeWidth);
|
|
319
|
-
if (wrapped.length <= MAX_CONTEXT_LINES) {
|
|
320
|
-
lines.push(...wrapped);
|
|
321
|
-
} else {
|
|
322
|
-
lines.push(...wrapped.slice(0, MAX_CONTEXT_LINES));
|
|
323
|
-
const overflow = wrapped.length - MAX_CONTEXT_LINES;
|
|
324
|
-
lines.push(theme.fg("dim", ` ... ${overflow} more line${overflow === 1 ? "" : "s"} (full details after confirmation)`));
|
|
414
|
+
// 7. Default: any remaining content (fallback)
|
|
415
|
+
addWrapped(theme.fg("muted", rawLine));
|
|
325
416
|
}
|
|
326
417
|
};
|
|
327
418
|
|
|
@@ -354,7 +445,7 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
354
445
|
|
|
355
446
|
if (inputMode && q) {
|
|
356
447
|
addWrapped(theme.fg("text", ` ${q.question}`));
|
|
357
|
-
if (q.context)
|
|
448
|
+
if (q.context) renderContextLines(q.context);
|
|
358
449
|
lines.push("");
|
|
359
450
|
if (q.options.length > 0) {
|
|
360
451
|
renderOptions();
|
|
@@ -375,7 +466,7 @@ export async function runGoalQuestionnaire(ctx: ExtensionContext, rawQuestions:
|
|
|
375
466
|
add(allAnswered() ? theme.fg("success", " Press Enter to submit") : theme.fg("warning", ` Unanswered: ${questions.filter((qq) => !answers.has(qq.id)).map((qq) => qq.id).join(", ")}`));
|
|
376
467
|
} else if (q) {
|
|
377
468
|
addWrapped(theme.fg("text", ` ${q.question}`));
|
|
378
|
-
if (q.context)
|
|
469
|
+
if (q.context) renderContextLines(q.context);
|
|
379
470
|
// Auditor toggle line between context and options
|
|
380
471
|
if (auditorToggleInit) {
|
|
381
472
|
const circle = auditorEnabled ? "●" : "○";
|
package/extensions/goal.ts
CHANGED
|
@@ -81,9 +81,14 @@ import {
|
|
|
81
81
|
import { buildCompactionSummary } from "./goal-compaction.ts";
|
|
82
82
|
import {
|
|
83
83
|
archiveGoalFile,
|
|
84
|
+
atomicWriteGoalFile,
|
|
85
|
+
ensureDirectory,
|
|
86
|
+
GOALS_DIR,
|
|
84
87
|
mergeGoalPromptFromDisk,
|
|
85
88
|
readActiveGoalPool,
|
|
89
|
+
safeUnlinkGoalFile,
|
|
86
90
|
sanitizeGoalPaths,
|
|
91
|
+
serializeGoalFile,
|
|
87
92
|
writeActiveGoalFile,
|
|
88
93
|
} from "./storage/goal-files.ts";
|
|
89
94
|
import {
|
|
@@ -410,6 +415,10 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
410
415
|
let auditAnimationTimer: ReturnType<typeof setInterval> | null = null;
|
|
411
416
|
let auditAbortController: AbortController | null = null;
|
|
412
417
|
let showingEscapeDialog = false;
|
|
418
|
+
let debugMode = false;
|
|
419
|
+
let debugGoalCounter = 0;
|
|
420
|
+
let debugMockAuditTimer: ReturnType<typeof setInterval> | null = null;
|
|
421
|
+
const DEBUG_GOALS_DIR = ".pi/goals/debug";
|
|
413
422
|
|
|
414
423
|
|
|
415
424
|
// Per-turn flags reset in turn_start (#4, C9 fix).
|
|
@@ -805,6 +814,7 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
805
814
|
getOpenGoalCount: () => openGoals().length,
|
|
806
815
|
getAuditorProgress: () => auditProgress,
|
|
807
816
|
getSettings: () => loadGoalSettings(ctx.cwd),
|
|
817
|
+
getDebugMode: () => debugMode,
|
|
808
818
|
});
|
|
809
819
|
return goalWidgetComponent;
|
|
810
820
|
},
|
|
@@ -833,6 +843,7 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
833
843
|
getOpenGoalCount: () => openGoals().length,
|
|
834
844
|
getAuditorProgress: () => auditProgress,
|
|
835
845
|
getSettings: () => loadGoalSettings(ctx.cwd),
|
|
846
|
+
getDebugMode: () => debugMode,
|
|
836
847
|
});
|
|
837
848
|
return goalWidgetComponent;
|
|
838
849
|
},
|
|
@@ -971,8 +982,291 @@ export default function goalExtension(pi: ExtensionAPI): void {
|
|
|
971
982
|
pauseActiveGoal(ctx);
|
|
972
983
|
return { consume: true };
|
|
973
984
|
}
|
|
985
|
+
|
|
986
|
+
// ── Debug mode keybindings (hidden from normal view) ────────────────
|
|
987
|
+
|
|
988
|
+
// Ctrl+Shift+X — toggle debug mode on/off
|
|
989
|
+
if (matchesKey(data, "ctrl+shift+x")) {
|
|
990
|
+
debugMode = !debugMode;
|
|
991
|
+
ctx.ui.notify(debugMode ? "Debug mode ON" : "Debug mode OFF", "info");
|
|
992
|
+
goalWidgetComponent?.invalidate();
|
|
993
|
+
return { consume: true };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Only process the following debug keybindings when debug mode is active
|
|
997
|
+
if (!debugMode) return undefined;
|
|
998
|
+
|
|
999
|
+
// Ctrl+Shift+N — create a test goal
|
|
1000
|
+
if (matchesKey(data, "ctrl+shift+n")) {
|
|
1001
|
+
createDebugGoal(ctx);
|
|
1002
|
+
return { consume: true };
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Ctrl+Shift+T — inject sample tasks into current goal
|
|
1006
|
+
if (matchesKey(data, "ctrl+shift+t")) {
|
|
1007
|
+
injectDebugTasks(ctx);
|
|
1008
|
+
return { consume: true };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Ctrl+Shift+R — start mock completion audit
|
|
1012
|
+
if (matchesKey(data, "ctrl+shift+r")) {
|
|
1013
|
+
startMockAudit(ctx);
|
|
1014
|
+
return { consume: true };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Ctrl+Shift+O — open proposal dialog with sample data
|
|
1018
|
+
if (matchesKey(data, "ctrl+shift+o")) {
|
|
1019
|
+
openDebugProposal(ctx);
|
|
1020
|
+
return { consume: true };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
974
1023
|
return undefined;
|
|
975
1024
|
});
|
|
1025
|
+
|
|
1026
|
+
/** Toggle a test goal: create (first press) or remove (second press) */
|
|
1027
|
+
function createDebugGoal(ctx: ExtensionContext): void {
|
|
1028
|
+
const prev = state.goal;
|
|
1029
|
+
if (prev && prev.id.startsWith("debug-")) {
|
|
1030
|
+
// Toggle off — remove debug goal entirely (no archive, full delete)
|
|
1031
|
+
const filePath = `${DEBUG_GOALS_DIR}/debug_goal.md`;
|
|
1032
|
+
try {
|
|
1033
|
+
safeUnlinkGoalFile({ cwd: ctx.cwd }, DEBUG_GOALS_DIR, filePath);
|
|
1034
|
+
} catch {}
|
|
1035
|
+
const prevId = prev.id;
|
|
1036
|
+
state.goal = null;
|
|
1037
|
+
if (focusedGoalId === prevId) {
|
|
1038
|
+
goalsById.delete(prevId);
|
|
1039
|
+
focusedGoalId = null;
|
|
1040
|
+
}
|
|
1041
|
+
clearStoppedRuntimeState();
|
|
1042
|
+
syncGoalTools();
|
|
1043
|
+
updateUI(ctx);
|
|
1044
|
+
ctx.ui.notify("Debug goal removed", "info");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Toggle on — create a new debug goal, write to temp dir
|
|
1049
|
+
debugGoalCounter++;
|
|
1050
|
+
const goal = createGoal({
|
|
1051
|
+
objective: "=== Goal ===\nObjective: Debug test goal",
|
|
1052
|
+
autoContinue: true,
|
|
1053
|
+
sisyphus: false,
|
|
1054
|
+
});
|
|
1055
|
+
goal.id = `debug-${nowIso().replace(/[:.]/g, "-")}-${debugGoalCounter}`;
|
|
1056
|
+
goal.createdAt = nowIso();
|
|
1057
|
+
goal.updatedAt = nowIso();
|
|
1058
|
+
goal.activePath = `${DEBUG_GOALS_DIR}/debug_goal.md`;
|
|
1059
|
+
const gfc = { cwd: ctx.cwd };
|
|
1060
|
+
ensureDirectory(gfc, DEBUG_GOALS_DIR);
|
|
1061
|
+
atomicWriteGoalFile(gfc, DEBUG_GOALS_DIR, goal.activePath, serializeGoalFile(goal));
|
|
1062
|
+
setGoal(goal, ctx, false, "created"); // no persist (we already wrote the file)
|
|
1063
|
+
ctx.ui.notify(`Debug goal created: ${goal.id}`, "info");
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** Inject 3-4 sample tasks into the current goal */
|
|
1067
|
+
function injectDebugTasks(ctx: ExtensionContext): void {
|
|
1068
|
+
if (!state.goal) {
|
|
1069
|
+
ctx.ui.notify("No goal to inject tasks into; create one first (Ctrl+Shift+N)", "warning");
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const now = nowIso();
|
|
1073
|
+
const tasks: GoalTask[] = [
|
|
1074
|
+
{
|
|
1075
|
+
id: "t1",
|
|
1076
|
+
title: "Set up project structure",
|
|
1077
|
+
status: "complete",
|
|
1078
|
+
completedAt: now,
|
|
1079
|
+
subtasks: [
|
|
1080
|
+
{ id: "t1a", title: "Initialize repo", status: "complete", completedAt: now },
|
|
1081
|
+
{ id: "t1b", title: "Add build config", status: "pending" },
|
|
1082
|
+
],
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
id: "t2",
|
|
1086
|
+
title: "Implement core feature",
|
|
1087
|
+
status: "pending",
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
id: "t3",
|
|
1091
|
+
title: "Write tests",
|
|
1092
|
+
status: "pending",
|
|
1093
|
+
},
|
|
1094
|
+
];
|
|
1095
|
+
const next = cloneGoal(state.goal);
|
|
1096
|
+
next.taskList = { tasks, blockCompletion: false, proposedAt: now };
|
|
1097
|
+
next.updatedAt = now;
|
|
1098
|
+
setGoal(next, ctx);
|
|
1099
|
+
ctx.ui.notify("Sample tasks injected (3 tasks, 1 completed)", "info");
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/** Stop mock audit timer if running */
|
|
1103
|
+
function stopMockAuditTimer(): void {
|
|
1104
|
+
if (debugMockAuditTimer) {
|
|
1105
|
+
clearInterval(debugMockAuditTimer);
|
|
1106
|
+
debugMockAuditTimer = null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** Start a mock completion audit that transitions through phases */
|
|
1111
|
+
function startMockAudit(ctx: ExtensionContext): void {
|
|
1112
|
+
stopMockAuditTimer();
|
|
1113
|
+
const startedAt = Date.now();
|
|
1114
|
+
const phases: { phase: AuditorWidgetProgress["phase"]; atMs: number; label: string; percentage: number }[] = [
|
|
1115
|
+
{ phase: "tool_executing", atMs: 0, label: "Checking test results...", percentage: 10 },
|
|
1116
|
+
{ phase: "tool_executing", atMs: 800, label: "Verifying requirements...", percentage: 30 },
|
|
1117
|
+
{ phase: "thinking", atMs: 1800, label: "Evaluating completion criteria...", percentage: 60 },
|
|
1118
|
+
{ phase: "producing_report", atMs: 3200, label: "Writing audit report...", percentage: 85 },
|
|
1119
|
+
{ phase: "done", atMs: 4800, label: "Audit complete", percentage: 100 },
|
|
1120
|
+
];
|
|
1121
|
+
auditProgress = {
|
|
1122
|
+
recentOutput: [],
|
|
1123
|
+
phase: "running",
|
|
1124
|
+
elapsedMs: 0,
|
|
1125
|
+
};
|
|
1126
|
+
goalWidgetComponent?.invalidate();
|
|
1127
|
+
|
|
1128
|
+
debugMockAuditTimer = setInterval(() => {
|
|
1129
|
+
const elapsed = Date.now() - startedAt;
|
|
1130
|
+
let currentPhase: AuditorWidgetProgress["phase"] = "done";
|
|
1131
|
+
let currentLabel = "Audit complete";
|
|
1132
|
+
let currentPct = 100;
|
|
1133
|
+
for (let i = phases.length - 1; i >= 0; i--) {
|
|
1134
|
+
if (elapsed >= phases[i].atMs) {
|
|
1135
|
+
currentPhase = phases[i].phase;
|
|
1136
|
+
currentLabel = phases[i].label;
|
|
1137
|
+
currentPct = phases[i].percentage;
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
auditProgress = {
|
|
1142
|
+
phase: currentPhase,
|
|
1143
|
+
label: currentLabel,
|
|
1144
|
+
percentage: currentPct,
|
|
1145
|
+
elapsedMs: elapsed,
|
|
1146
|
+
recentOutput: auditProgress?.recentOutput ?? [],
|
|
1147
|
+
};
|
|
1148
|
+
if (currentPhase === "done") {
|
|
1149
|
+
if (auditProgress) auditProgress.recentOutput = [
|
|
1150
|
+
"✓ All requirements verified",
|
|
1151
|
+
"✓ Tests pass: 310/310",
|
|
1152
|
+
"✓ No truncation cap remaining",
|
|
1153
|
+
];
|
|
1154
|
+
stopMockAuditTimer();
|
|
1155
|
+
// Auto-clear audit after 3 more seconds
|
|
1156
|
+
setTimeout(() => {
|
|
1157
|
+
auditProgress = null;
|
|
1158
|
+
goalWidgetComponent?.invalidate();
|
|
1159
|
+
}, 3000);
|
|
1160
|
+
}
|
|
1161
|
+
goalWidgetComponent?.invalidate();
|
|
1162
|
+
}, 100);
|
|
1163
|
+
debugMockAuditTimer.unref?.();
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/** Render task lines exactly like propose_task_list does */
|
|
1167
|
+
function renderDebugTaskLines(tasks: GoalTask[], indent = 0): string[] {
|
|
1168
|
+
const prefix = " ".repeat(indent);
|
|
1169
|
+
const lines: string[] = [];
|
|
1170
|
+
for (const t of tasks) {
|
|
1171
|
+
const marker = t.status === "complete" ? "[x]" : t.status === "skipped" ? "[~]" : "[ ]";
|
|
1172
|
+
const lw = t.lightweightSubtasks ? " (lightweight)" : "";
|
|
1173
|
+
lines.push(`${prefix}${marker} ${t.id}: ${t.title}${lw}`);
|
|
1174
|
+
if (t.subtasks && t.subtasks.length > 0) {
|
|
1175
|
+
lines.push(...renderDebugTaskLines(t.subtasks, indent + 1));
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return lines;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/** Show the proposal dialog using real goal state — no hardcoded text */
|
|
1182
|
+
function openDebugProposal(ctx: ExtensionContext): void {
|
|
1183
|
+
// Build a fresh debug goal + tasks in memory for the dialog
|
|
1184
|
+
debugGoalCounter++;
|
|
1185
|
+
const goal = createGoal({
|
|
1186
|
+
objective: `=== Goal ===
|
|
1187
|
+
Objective: Add collapsible task sections to the goal widget so large task lists are navigable
|
|
1188
|
+
|
|
1189
|
+
Success criteria:
|
|
1190
|
+
- Tasks are grouped into sections by status (pending, active, complete) with visible section headers
|
|
1191
|
+
- Each section header is toggleable — clicking it expands or collapses that section
|
|
1192
|
+
- When collapsed, the section shows a header line only with a task count badge
|
|
1193
|
+
- When expanded, tasks render with normal indentation and per-line styling
|
|
1194
|
+
- Default state: pending section expanded, active and complete sections collapsed
|
|
1195
|
+
- Section state is tracked per-render (no persistence needed)
|
|
1196
|
+
- All 310 existing tests still pass
|
|
1197
|
+
|
|
1198
|
+
Boundaries:
|
|
1199
|
+
- In scope: GoalWidgetComponent.render() grouping logic, section header toggling, expand/collapse state per render cycle
|
|
1200
|
+
- Out of scope: task reordering, drag-and-drop, keyboard navigation for sections, persistence of section state across pi restarts
|
|
1201
|
+
- Out of scope: modifying GoalTask or GoalRecord types
|
|
1202
|
+
|
|
1203
|
+
Constraints:
|
|
1204
|
+
- Render width must respect the existing width parameter — no hardcoded widths
|
|
1205
|
+
- Section collapse state is a render-only map, not stored in goal record
|
|
1206
|
+
- Collapse toggle must be keyboard-accessible via existing widget interaction model
|
|
1207
|
+
- Do not change the GoalWidgetComponent public API (constructor options, render signature)
|
|
1208
|
+
- Section headers must use theme.fg("accent", ...) consistent with existing render patterns
|
|
1209
|
+
|
|
1210
|
+
Verification contract:
|
|
1211
|
+
- Run npm test and confirm 310/310 pass (0 failures)
|
|
1212
|
+
- Read render method and confirm task grouping logic exists
|
|
1213
|
+
- Read expand/collapse toggle handler and confirm it inverts section state
|
|
1214
|
+
- Confirm collapsed sections only render the header line with task count
|
|
1215
|
+
- Confirm expanded sections render tasks with correct indentation and styling`,
|
|
1216
|
+
autoContinue: true,
|
|
1217
|
+
sisyphus: false,
|
|
1218
|
+
});
|
|
1219
|
+
goal.id = `debug-${nowIso().replace(/[:.]/g, "-")}-${debugGoalCounter}`;
|
|
1220
|
+
goal.createdAt = nowIso();
|
|
1221
|
+
goal.updatedAt = nowIso();
|
|
1222
|
+
|
|
1223
|
+
const now = nowIso();
|
|
1224
|
+
const tasks: GoalTask[] = [
|
|
1225
|
+
{
|
|
1226
|
+
id: "t1",
|
|
1227
|
+
title: "Set up project structure",
|
|
1228
|
+
status: "complete",
|
|
1229
|
+
completedAt: now,
|
|
1230
|
+
subtasks: [
|
|
1231
|
+
{ id: "t1a", title: "Initialize repo", status: "complete", completedAt: now },
|
|
1232
|
+
{ id: "t1b", title: "Add build config", status: "pending" },
|
|
1233
|
+
],
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
id: "t2",
|
|
1237
|
+
title: "Implement core feature",
|
|
1238
|
+
status: "pending",
|
|
1239
|
+
subtasks: [
|
|
1240
|
+
{ id: "t2a", title: "Status grouping logic", status: "pending" },
|
|
1241
|
+
{ id: "t2b", title: "Section header component", status: "pending" },
|
|
1242
|
+
{ id: "t2c", title: "Expand/collapse state", status: "pending" },
|
|
1243
|
+
{ id: "t2d", title: "Task count badge", status: "pending" },
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
{ id: "t3", title: "Update tests", status: "pending" },
|
|
1247
|
+
{ id: "t4", title: "Manual TUI verification", status: "pending" },
|
|
1248
|
+
];
|
|
1249
|
+
goal.taskList = { tasks, blockCompletion: false, proposedAt: now };
|
|
1250
|
+
|
|
1251
|
+
// Build proposal from goal state — exactly like the real flow
|
|
1252
|
+
const confirmationText = buildDraftConfirmationText({
|
|
1253
|
+
focus: "goal",
|
|
1254
|
+
originalTopic: "Refactor the goal widget component to support collapsible task sections",
|
|
1255
|
+
objective: goal.objective,
|
|
1256
|
+
autoContinue: goal.autoContinue,
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
// Append task proposal — exactly like propose_task_list would
|
|
1260
|
+
const taskLines = renderDebugTaskLines(tasks).map((l) => `│ ${l}`);
|
|
1261
|
+
const taskProposal = [
|
|
1262
|
+
"",
|
|
1263
|
+
"│ Proposed task list:",
|
|
1264
|
+
"",
|
|
1265
|
+
...taskLines,
|
|
1266
|
+
].join("\n");
|
|
1267
|
+
|
|
1268
|
+
showProposalDialog(ctx, confirmationText + taskProposal, "goal", true);
|
|
1269
|
+
}
|
|
976
1270
|
}
|
|
977
1271
|
|
|
978
1272
|
function sendQueuedContinuation(ctx: ExtensionContext, goalId: string): void {
|
|
@@ -14,11 +14,15 @@ import type { GoalSettings } from "../goal-settings.ts";
|
|
|
14
14
|
type GoalWidgetColor = Extract<ThemeColor, "accent" | "warning" | "success" | "error" | "dim" | "muted" | "text">;
|
|
15
15
|
|
|
16
16
|
export interface GoalWidgetRecord extends GoalDisplayRecordLike {
|
|
17
|
+
id: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
17
20
|
activePath?: string | null;
|
|
18
21
|
archivedPath?: string | null;
|
|
19
22
|
pauseReason?: string;
|
|
20
23
|
pauseSuggestedAction?: string;
|
|
21
24
|
taskList?: GoalTaskList | null;
|
|
25
|
+
verificationContract?: string;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export interface AuditorWidgetProgress {
|
|
@@ -41,6 +45,7 @@ export interface GoalWidgetOptions {
|
|
|
41
45
|
getOpenGoalCount?: () => number;
|
|
42
46
|
getAuditorProgress?: () => AuditorWidgetProgress | null;
|
|
43
47
|
getSettings?: () => GoalSettings;
|
|
48
|
+
getDebugMode?: () => boolean;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
function fit(value: string, width: number): string {
|
|
@@ -280,8 +285,8 @@ export class GoalWidgetComponent implements Component {
|
|
|
280
285
|
private getGoal: () => GoalWidgetRecord | null;
|
|
281
286
|
private getOpenGoalCount: () => number;
|
|
282
287
|
private getAuditorProgress: () => AuditorWidgetProgress | null;
|
|
283
|
-
|
|
284
288
|
private getSettings: () => GoalSettings;
|
|
289
|
+
private getDebugMode: () => boolean;
|
|
285
290
|
|
|
286
291
|
constructor(options: GoalWidgetOptions) {
|
|
287
292
|
this.theme = options.theme;
|
|
@@ -290,19 +295,81 @@ export class GoalWidgetComponent implements Component {
|
|
|
290
295
|
this.getOpenGoalCount = options.getOpenGoalCount ?? (() => (this.getGoal() ? 1 : 0));
|
|
291
296
|
this.getAuditorProgress = options.getAuditorProgress ?? (() => null);
|
|
292
297
|
this.getSettings = options.getSettings ?? (() => ({}));
|
|
298
|
+
this.getDebugMode = options.getDebugMode ?? (() => false);
|
|
293
299
|
}
|
|
294
300
|
|
|
295
301
|
update(): void {
|
|
296
302
|
this.tui.requestRender();
|
|
297
303
|
}
|
|
298
304
|
|
|
305
|
+
/** Render debug info panel when debug mode is active */
|
|
306
|
+
private renderDebugPanel(width: number): string[] {
|
|
307
|
+
const t = this.theme;
|
|
308
|
+
const lines: string[] = [];
|
|
309
|
+
const safeWidth = Math.max(20, width);
|
|
310
|
+
|
|
311
|
+
// Divider
|
|
312
|
+
lines.push(t.fg("dim", "─".repeat(safeWidth)));
|
|
313
|
+
lines.push(t.fg("warning", "⊙ [DEBUG MODE]"));
|
|
314
|
+
lines.push("");
|
|
315
|
+
|
|
316
|
+
const goal = this.getGoal();
|
|
317
|
+
if (goal) {
|
|
318
|
+
lines.push(t.fg("dim", ` id: ${goal.id}`));
|
|
319
|
+
lines.push(t.fg("dim", ` status: ${goal.status}`));
|
|
320
|
+
lines.push(t.fg("dim", ` objective: ${truncateText(goal.objective, 80)}`));
|
|
321
|
+
lines.push(t.fg("dim", ` sisyphus: ${goal.sisyphus}`));
|
|
322
|
+
lines.push(t.fg("dim", ` autoContinue: ${goal.autoContinue}`));
|
|
323
|
+
lines.push(t.fg("dim", ` tokens: ${goal.usage.tokensUsed}`));
|
|
324
|
+
lines.push(t.fg("dim", ` activeSeconds: ${goal.usage.activeSeconds}`));
|
|
325
|
+
lines.push(t.fg("dim", ` createdAt: ${goal.createdAt}`));
|
|
326
|
+
lines.push(t.fg("dim", ` updatedAt: ${goal.updatedAt}`));
|
|
327
|
+
if (goal.pauseReason) lines.push(t.fg("dim", ` pauseReason: ${goal.pauseReason}`));
|
|
328
|
+
if (goal.pauseSuggestedAction) lines.push(t.fg("dim", ` pauseSuggestedAction: ${goal.pauseSuggestedAction}`));
|
|
329
|
+
if (goal.stopReason) lines.push(t.fg("dim", ` stopReason: ${goal.stopReason}`));
|
|
330
|
+
if (goal.activePath) lines.push(t.fg("dim", ` activePath: ${goal.activePath}`));
|
|
331
|
+
if (goal.archivedPath) lines.push(t.fg("dim", ` archivedPath: ${goal.archivedPath}`));
|
|
332
|
+
if (goal.verificationContract) lines.push(t.fg("dim", ` vContract: ${truncateText(goal.verificationContract, 60)}`));
|
|
333
|
+
|
|
334
|
+
// Task tree summary
|
|
335
|
+
if (goal.taskList && goal.taskList.tasks.length > 0) {
|
|
336
|
+
const { total, done } = countFlatTasks(goal.taskList.tasks);
|
|
337
|
+
lines.push(t.fg("dim", ` tasks: ${done}/${total}`));
|
|
338
|
+
const firstPending = findFirstPending(goal.taskList.tasks);
|
|
339
|
+
if (firstPending) lines.push(t.fg("dim", ` next: ${firstPending.id} (${truncateText(firstPending.title, 40)})`));
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
lines.push(t.fg("dim", " (no goal)"));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
lines.push("");
|
|
346
|
+
lines.push(t.fg("dim", "── Debug keybindings ──"));
|
|
347
|
+
lines.push(t.fg("dim", " Ctrl+Shift+X Toggle debug mode"));
|
|
348
|
+
lines.push(t.fg("dim", " Ctrl+Shift+N Create test goal"));
|
|
349
|
+
lines.push(t.fg("dim", " Ctrl+Shift+T Inject sample tasks"));
|
|
350
|
+
lines.push(t.fg("dim", " Ctrl+Shift+R Mock audit animation"));
|
|
351
|
+
lines.push(t.fg("dim", " Ctrl+Shift+O Open proposal dialog"));
|
|
352
|
+
|
|
353
|
+
return lines;
|
|
354
|
+
}
|
|
355
|
+
|
|
299
356
|
render(width: number): string[] {
|
|
300
357
|
const settings = this.getSettings();
|
|
301
|
-
|
|
358
|
+
let lines = renderGoalWidgetLines(this.getGoal(), this.theme, width, {
|
|
302
359
|
openGoalCount: this.getOpenGoalCount(),
|
|
303
360
|
auditorProgress: this.getAuditorProgress(),
|
|
304
361
|
disableTasks: settings.disableTasks,
|
|
305
362
|
});
|
|
363
|
+
if (this.getDebugMode()) {
|
|
364
|
+
lines.push(...this.renderDebugPanel(width));
|
|
365
|
+
}
|
|
366
|
+
// Safety net: ensure no returned line exceeds the terminal width
|
|
367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
368
|
+
if (visibleWidth(lines[i]) > width) {
|
|
369
|
+
lines[i] = truncateToWidth(lines[i], width);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return lines;
|
|
306
373
|
}
|
|
307
374
|
|
|
308
375
|
invalidate(): void {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|
package/docs/CHANGELOG.md
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.17.0 (2026-05-29)
|
|
4
|
-
|
|
5
|
-
### Features
|
|
6
|
-
|
|
7
|
-
- **Per-goal auditor toggle** — press `a` during the confirmation dialog to toggle the auditor on/off for a specific goal. Default from settings; override persists within session.
|
|
8
|
-
- **Task workflow prompt guidance** — added `[TASK WORKFLOW]` section to both `goalPrompt` and `continuationPrompt`, directing agents to complete subtasks one-by-one as progress trackers (not batch-marking at the end).
|
|
9
|
-
- **Recursive duplicate ID validation** — `validateTaskListProposal` now checks all task IDs across the entire tree, preventing collisions between parent/subtask or sibling subtask IDs.
|
|
10
|
-
- **Escape dialog during audit** — pressing Escape during a completion audit shows a TUI dialog with "Mark complete without audit" or "Continue working" options.
|
|
11
|
-
|
|
12
|
-
### Fixes
|
|
13
|
-
|
|
14
|
-
- `validateTaskCompletion` and `validateTaskSkip` now use recursive `findTaskInTree` instead of flat `Array.find()` for nested subtask support.
|
|
15
|
-
- Updated README references from legacy `update_goal` to `complete_goal`.
|
|
16
|
-
|
|
17
|
-
### Tests
|
|
18
|
-
|
|
19
|
-
- 310 total tests (up from 308).
|
|
20
|
-
- Added tests for recursive duplicate ID detection across nested subtask trees.
|
|
21
|
-
- Added e2e test for `skipAuditor=true` path.
|
|
22
|
-
|
|
23
|
-
## 0.16.1 (2026-05-29)
|
|
24
|
-
|
|
25
|
-
### Features
|
|
26
|
-
|
|
27
|
-
- **Escape-to-skip audit** — press Escape during an auditor run to abort it and complete the goal immediately. The skip is recorded in the ledger with the reason `user_aborted` and auditor model metadata.
|
|
28
|
-
- **Audit progress widget** — the TUI shows a spinner, progress bar, step labels, current tool, and output lines while the auditor runs.
|
|
29
|
-
- **Audit abort detection** — the auditor detects aborts both from exceptions and from `session.prompt()` returning after an abort signal, preventing stuck goals or ghost states.
|
|
30
|
-
- **Goal status for Sisyphus** — `COMPLETED` status label for completed Sisyphus goals.
|
|
31
|
-
- **Multi-session focus isolation** — goal focus data uses `goalFocusDetails` which includes the goal id and reason but not full balance data, preventing cross-session focus leakage.
|
|
32
|
-
|
|
33
|
-
### Fixes
|
|
34
|
-
|
|
35
|
-
- Fixed a merge bug where `propose_task_list` could produce duplicate task list when called during a continuation.
|
|
36
|
-
|
|
37
|
-
## 0.16.0 (2026-05-29)
|
|
38
|
-
|
|
39
|
-
### Features
|
|
40
|
-
|
|
41
|
-
- **`delete_goal` tool** — new lifecycle tool for archiving goals by id. Accepts a required `goalId` and optional `reason`. Agent-facing only; not intended for user use.
|
|
42
|
-
- **`complete_goal` `status` optional** — the `status` parameter on `complete_goal` is now optional. When omitted, defaults to `"complete"`. Explicitly setting an invalid value (anything other than `"complete"`) still produces an error.
|
|
43
|
-
- **SCROLL FIX** — the confirmation dialog no longer scrolls to the bottom when the user is scrolled up and new content arrives. Uses `addContextWrapped()` which suppresses viewport resets.
|
|
44
|
-
- **Task list shown first** — the task list section now appears FIRST in the confirmation dialog context (before the objective), with context capped at 12 lines so tasks don't scroll off-screen.
|
|
45
|
-
- **Audit completion flow** — the completion report card no longer says "Goal audit approved." when the auditor was skipped (now shows "Goal audit skipped." with reason).
|
|
46
|
-
|
|
47
|
-
### Fixes
|
|
48
|
-
|
|
49
|
-
- Fixed task completion/skip validation for nested subtasks (uses recursive `findTaskInTree`).
|
|
50
|
-
- All `complete_goal` calls default to `status: "complete"` when no explicit status is provided.
|
|
51
|
-
- Updated prompts and tool descriptions to reflect the `complete_goal` naming.
|
|
52
|
-
|
|
53
|
-
### Tests
|
|
54
|
-
|
|
55
|
-
- Updated e2e tests to verify `complete_goal` accepts calls without status.
|
|
56
|
-
- Added e2e test verifying `complete_goal` rejects invalid explicit status.
|
|
57
|
-
|
|
58
|
-
## 0.15.1 (2026-05-28)
|
|
59
|
-
|
|
60
|
-
### Fixes
|
|
61
|
-
|
|
62
|
-
- Fixed settings file reference in storage writes.
|
|
63
|
-
|
|
64
|
-
### Documentation
|
|
65
|
-
|
|
66
|
-
- Reorganized README settings documentation for clarity.
|
|
67
|
-
|
|
68
|
-
## 0.14.0 (2026-05-27)
|
|
69
|
-
|
|
70
|
-
### Features
|
|
71
|
-
|
|
72
|
-
- **Subtask hierarchy** — tasks can have nested sub-tasks via `subtasks?: GoalTask[]`. Subtask depth controlled by `subtaskDepth` setting (default: 1). Deep subtrees are rejected at proposal.
|
|
73
|
-
- **Lightweight subtasks** — `lightweightSubtasks?: boolean` on tasks. When true, parent can complete regardless of subtask status. Full subtasks require all sub-items completed first.
|
|
74
|
-
- **Per-task contracts** — `propose_task_list` supports optional `verificationContract` per task. If set, `complete_task` requires a non-empty `verificationSummary`.
|
|
75
|
-
- **Task list block** — tasks are listed in prompts with checkboxes and status indicators.
|
|
76
|
-
|
|
77
|
-
### Tests
|
|
78
|
-
|
|
79
|
-
- Added e2e tests for goal creation with task list, scroll fix, and subtask validation.
|
|
80
|
-
|
|
81
|
-
## 0.13.0 (2026-05-22)
|
|
82
|
-
|
|
83
|
-
### Features
|
|
84
|
-
|
|
85
|
-
- **Verification contract system** — goals can include a `Verification contract:` section. Extracted and stored on the goal record. `complete_goal` rejects calls without `verificationSummary` when a contract is set.
|
|
86
|
-
- **Per-goal verification contracts** — the contract is extracted during goal drafting and enforced by tools and prompts.
|
|
87
|
-
- **`complete_goal` `testResults` removed** — replaced with `verificationSummary`. The old structured test results interface is gone.
|
|
88
|
-
- **Auditor integration** — the independent completion auditor receives both the `verificationContract` and `verificationSummary` and cross-checks claims against real artifacts.
|
|
89
|
-
|
|
90
|
-
### Tests
|
|
91
|
-
|
|
92
|
-
- Updated verification contract tests.
|
|
93
|
-
|
|
94
|
-
## 0.12.0 (2026-04-29)
|
|
95
|
-
|
|
96
|
-
### Features
|
|
97
|
-
|
|
98
|
-
- **Task list system** — `propose_task_list` tool with confirmation dialog. Tasks stored on goal record, rendered in prompts and widget, serialized to disk.
|
|
99
|
-
- **Unified goal + task acceptance** — `propose_goal_draft` accepts optional `tasks` array. Single dialog shows goal + task list together.
|
|
100
|
-
- **`complete_task` and `skip_task` tools** — per-task completion with evidence/verificationSummary. Neither stops the turn.
|
|
101
|
-
- **`update_goal` renamed to `complete_goal`** — the core completion tool now uses `complete_goal({status: "complete"})` and requires explicit status acceptance.
|
|
102
|
-
- **Completion report heading fix** — the report now shows `Goal complete.` instead of `Goal audit approved.` when no contract or auditor is involved.
|
|
103
|
-
|
|
104
|
-
### Tests
|
|
105
|
-
|
|
106
|
-
- Full task lifecycle tests (policy, round-trip, render, edge cases).
|
|
107
|
-
- Verification contract tests for both goal-level and per-task contracts.
|
|
108
|
-
|
|
109
|
-
## 0.11.0 (2026-04-23)
|
|
110
|
-
|
|
111
|
-
### Features
|
|
112
|
-
|
|
113
|
-
- **Deferred archival** — goals are archived at `turn_end`, not inline in the tool handler. Prevents premature archiving before the agent sees the audit result.
|
|
114
|
-
- **`propose_goal_tweak`** — sole mechanism for updating the goal objective during `/goal-tweak`. Uses the same Confirm/Continue Chatting dialog as goal creation.
|
|
115
|
-
- **Focus isolation** — goal focus is stored as a branch-local session entry, not in goal markdown metadata. Multiple sessions can have different focused goals.
|
|
116
|
-
- **Auditor bypass with user confirmation** — `confirmBypassAuditor: true` bypasses the auditor when the user explicitly opts out.
|
|
117
|
-
|
|
118
|
-
### Fixes
|
|
119
|
-
|
|
120
|
-
- Cleaned up lifecycle issues with AbortSignal wiring and timer cleanup.
|
|
121
|
-
|
|
122
|
-
## 0.10.0 (2026-04-15)
|
|
123
|
-
|
|
124
|
-
### Features
|
|
125
|
-
|
|
126
|
-
- **Completion audit system** — independent pi auditor agent verifies completion claims before archiving.
|
|
127
|
-
- **Audit progress** — real-time TUI progress widget with spinner, progress bar, and step labels.
|
|
128
|
-
- **Ledger system** — structured event log for all goal lifecycle events.
|
|
129
|
-
|
|
130
|
-
## 0.9.0 (2026-04-08)
|
|
131
|
-
|
|
132
|
-
### Features
|
|
133
|
-
|
|
134
|
-
- **`goal_question` and `goal_questionnaire`** — structured drafting question tools.
|
|
135
|
-
- **`/goal-settings`** — interactive settings configuration.
|
|
136
|
-
- **Sisyphus goal style** — patient ordered execution with prompt/criteria variant.
|
|
137
|
-
|
|
138
|
-
## 0.8.1 (2026-04-01)
|
|
139
|
-
|
|
140
|
-
### Features
|
|
141
|
-
|
|
142
|
-
- Initial fork from @capyup/pi-goal.
|
|
143
|
-
- Pause/resume/abort lifecycle.
|
|
144
|
-
- Multiple open goals.
|
|
145
|
-
- Auto-continue loop.
|