@quantmind-br/pi-goal-x 0.0.0-stage → 0.35.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/LICENSE +21 -0
- package/README.md +131 -2
- package/assets/badge-dark.svg +18 -0
- package/assets/badge-light.svg +18 -0
- package/docs/advanced-usage.md +106 -0
- package/docs/agent-flow-design.md +306 -0
- package/docs/agentic-runtime-prd.md +773 -0
- package/docs/architecture.md +368 -0
- package/docs/controls.md +140 -0
- package/docs/pi-autoresearch-survey.md +50 -0
- package/docs/pi-goal-x-unified-dashboard-plan.md +1667 -0
- package/docs/prompt-caching.md +31 -0
- package/docs/unified-dashboard.md +286 -0
- package/extensions/auditor-selector.ts +74 -0
- package/extensions/goal-accounting.ts +107 -0
- package/extensions/goal-activity.ts +202 -0
- package/extensions/goal-auditor.ts +480 -0
- package/extensions/goal-commands.ts +866 -0
- package/extensions/goal-compaction.ts +197 -0
- package/extensions/goal-completion.ts +173 -0
- package/extensions/goal-context.ts +12 -0
- package/extensions/goal-contract.ts +58 -0
- package/extensions/goal-controls.ts +202 -0
- package/extensions/goal-core-tools.ts +549 -0
- package/extensions/goal-core.ts +78 -0
- package/extensions/goal-detail.ts +65 -0
- package/extensions/goal-dialog-controls.ts +31 -0
- package/extensions/goal-draft.ts +244 -0
- package/extensions/goal-drafting.ts +535 -0
- package/extensions/goal-events.ts +692 -0
- package/extensions/goal-format.ts +376 -0
- package/extensions/goal-interruption.ts +53 -0
- package/extensions/goal-ledger-index.ts +68 -0
- package/extensions/goal-ledger.ts +1007 -0
- package/extensions/goal-live-retention.ts +219 -0
- package/extensions/goal-oracle.ts +346 -0
- package/extensions/goal-policy.ts +370 -0
- package/extensions/goal-pool.ts +103 -0
- package/extensions/goal-prompt-cache.ts +80 -0
- package/extensions/goal-questionnaire.ts +1073 -0
- package/extensions/goal-record.ts +388 -0
- package/extensions/goal-recovery.ts +256 -0
- package/extensions/goal-runtime.ts +277 -0
- package/extensions/goal-scheduler-state.ts +100 -0
- package/extensions/goal-scheduler.ts +436 -0
- package/extensions/goal-service.ts +843 -0
- package/extensions/goal-session-health.ts +130 -0
- package/extensions/goal-session-safety.ts +65 -0
- package/extensions/goal-settings.ts +1370 -0
- package/extensions/goal-state.ts +1069 -0
- package/extensions/goal-status.ts +305 -0
- package/extensions/goal-task-confirmation.ts +174 -0
- package/extensions/goal-task-count.ts +59 -0
- package/extensions/goal-task-derive.ts +46 -0
- package/extensions/goal-task-index.ts +57 -0
- package/extensions/goal-task-tools.ts +595 -0
- package/extensions/goal-tool-inputs.ts +102 -0
- package/extensions/goal-tool-names.ts +73 -0
- package/extensions/goal-tools.ts +16 -0
- package/extensions/goal-widget.ts +455 -0
- package/extensions/goal.ts +42 -0
- package/extensions/network-error-backoff.ts +55 -0
- package/extensions/prompts/goal-prompts.ts +321 -0
- package/extensions/storage/atomic-rename.ts +52 -0
- package/extensions/storage/goal-files.ts +695 -0
- package/extensions/storage/goal-lock.ts +131 -0
- package/extensions/storage/goal-root.ts +95 -0
- package/extensions/widgets/auditor-dashboard-model.ts +174 -0
- package/extensions/widgets/dialog-scaffold.ts +26 -0
- package/extensions/widgets/goal-controls-dialog.ts +329 -0
- package/extensions/widgets/goal-dashboard-model.ts +530 -0
- package/extensions/widgets/goal-dashboard-renderer.ts +674 -0
- package/extensions/widgets/goal-escape-dialog.ts +148 -0
- package/extensions/widgets/goal-notifications.ts +9 -0
- package/extensions/widgets/goal-widget.ts +723 -0
- package/extensions/widgets/task-list-overlay.ts +409 -0
- package/extensions/widgets/text-cache.ts +30 -0
- package/package.json +117 -4
- package/scripts/recover-session-checkpoints.mjs +289 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { taskIndex } from "./goal-task-index.ts";
|
|
3
|
+
import type { GoalRecord } from "./goal-record.ts";
|
|
4
|
+
import type { GoalLedgerEvent } from "./goal-ledger.ts";
|
|
5
|
+
|
|
6
|
+
export const GOAL_DETAIL_PAGE_CHARS = 4000;
|
|
7
|
+
export type GoalDetailSection = "objective" | "tasks" | "history";
|
|
8
|
+
export interface GoalDetailQuery { section: GoalDetailSection; task_id?: string; cursor?: string }
|
|
9
|
+
export type GoalDetailPage = {ok: true; text: string; content: string; nextCursor?: string; totalChars: number} | {ok: false; text: string};
|
|
10
|
+
|
|
11
|
+
interface DetailSource { source: string; key: string }
|
|
12
|
+
const detailCache: Array<{inputs: readonly unknown[]; result: DetailSource}> = [];
|
|
13
|
+
let detailCacheChars = 0;
|
|
14
|
+
|
|
15
|
+
function compiledSource(goal: GoalRecord, query: GoalDetailQuery, events: readonly GoalLedgerEvent[], revision?: object): DetailSource | undefined {
|
|
16
|
+
const index = query.section === "tasks" ? taskIndex(goal.taskList?.tasks) : undefined;
|
|
17
|
+
const inputs = [goal.id, query.section, query.task_id, ...(query.section === "objective" ? [goal.objective, goal.verificationContract]
|
|
18
|
+
: query.section === "tasks" ? [index, goal.currentTaskId] : [revision])];
|
|
19
|
+
// Arbitrary caller-owned histories remain content checked; only the ledger can supply a generation.
|
|
20
|
+
const cacheable = query.section !== "history" || revision !== undefined;
|
|
21
|
+
if (cacheable) for (let i = detailCache.length - 1; i >= 0; i--) {
|
|
22
|
+
const entry = detailCache[i]!;
|
|
23
|
+
if (inputs.length === entry.inputs.length && inputs.every((value, j) => value === entry.inputs[j])) return entry.result;
|
|
24
|
+
}
|
|
25
|
+
let source: string;
|
|
26
|
+
if (query.section === "objective") source = `${goal.objective}${goal.verificationContract ? `\n\nVerification contract:\n${goal.verificationContract}` : ""}`;
|
|
27
|
+
else if (query.section === "history") source = events.filter(e => "goalId" in e && e.goalId === goal.id).map(e => JSON.stringify(e)).join("\n");
|
|
28
|
+
else {
|
|
29
|
+
const rows = index!.ordered;
|
|
30
|
+
const selected = query.task_id ? rows.find(row => row.task.id === query.task_id) : undefined;
|
|
31
|
+
if (query.task_id && !selected) return undefined;
|
|
32
|
+
source = (selected ? [selected] : rows).map(({task: {subtasks, ...task}, parentId}) => JSON.stringify({...task, parent_id: parentId, ...(task.id === goal.currentTaskId ? {current: true} : {})})).join("\n");
|
|
33
|
+
}
|
|
34
|
+
const key = createHash("sha256").update(JSON.stringify([goal.id, query.section, query.task_id, source])).digest("hex");
|
|
35
|
+
const result = {source, key};
|
|
36
|
+
// Full data remains available even when it exceeds the bounded shared page cache.
|
|
37
|
+
if (cacheable && source.length <= 16_000_000) {
|
|
38
|
+
while (detailCache.length >= 16 || detailCacheChars + source.length > 16_000_000) detailCacheChars -= detailCache.shift()!.result.source.length;
|
|
39
|
+
detailCache.push({inputs, result}); detailCacheChars += source.length;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Stable, lossless detail text. Ledger revisions are opaque generations, never timestamps. */
|
|
45
|
+
export function goalDetailPage(goal: GoalRecord, query: GoalDetailQuery, events: readonly GoalLedgerEvent[] = [], historyRevision?: object): GoalDetailPage {
|
|
46
|
+
if (query.task_id !== undefined && query.section !== "tasks") return {ok: false, text: "task_id requires section=tasks."};
|
|
47
|
+
const compiled = compiledSource(goal, query, events, historyRevision);
|
|
48
|
+
if (!compiled) return {ok: false, text: `Task "${query.task_id}" not found.`};
|
|
49
|
+
const {source, key} = compiled;
|
|
50
|
+
let offset = 0;
|
|
51
|
+
if (query.cursor) {
|
|
52
|
+
try {
|
|
53
|
+
if (query.cursor.length > 256) throw new Error();
|
|
54
|
+
const parsed = JSON.parse(Buffer.from(query.cursor, "base64url").toString("utf8"));
|
|
55
|
+
if (parsed.v !== 1 || parsed.key !== key || !Number.isSafeInteger(parsed.offset) || parsed.offset < 0 || parsed.offset > source.length) throw new Error();
|
|
56
|
+
offset = parsed.offset;
|
|
57
|
+
} catch { return {ok: false, text: "Invalid or stale cursor: goal details changed or the section/task differs. Restart this section without cursor."}; }
|
|
58
|
+
}
|
|
59
|
+
let end = Math.min(offset + GOAL_DETAIL_PAGE_CHARS, source.length);
|
|
60
|
+
if (end < source.length && /[\uD800-\uDBFF]/.test(source[end - 1]!)) end--;
|
|
61
|
+
const content = source.slice(offset, end);
|
|
62
|
+
const nextCursor = end < source.length ? Buffer.from(JSON.stringify({v: 1, key, offset: end})).toString("base64url") : undefined;
|
|
63
|
+
const text = `${query.section} for ${goal.id} (${offset}–${end}/${source.length} chars)\n${content}${nextCursor ? `\nMore content: repeat this section/task with cursor="${nextCursor}".` : "\nEnd of section."}`;
|
|
64
|
+
return {ok: true, text, content, nextCursor, totalChars: source.length};
|
|
65
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getKeybindings, isKittyProtocolActive, matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
+
import { GOAL_DIALOG_KEYS, bindingKeys, sameGoalKey, type GoalDialogAction, type GoalKeybinding } from "./goal-controls.ts";
|
|
3
|
+
import { loadGoalSettings, formatGoalKeybinding } from "./goal-settings.ts";
|
|
4
|
+
|
|
5
|
+
/** Snapshot the dialog's controls once; the modal owns input until it closes. */
|
|
6
|
+
export function goalDialogControls(cwd: string) {
|
|
7
|
+
const configured = loadGoalSettings(cwd).keybindings?.dialog;
|
|
8
|
+
const binding = (action: GoalDialogAction) => configured?.[action] ?? GOAL_DIALOG_KEYS[action];
|
|
9
|
+
const hint = (action: GoalDialogAction) => action === "toggleAuditor" && binding(action) === "a" ? "a" : formatGoalKeybinding(binding(action));
|
|
10
|
+
let editorKeys: string[] = [];
|
|
11
|
+
try { editorKeys = Object.entries(getKeybindings().getResolvedBindings()).filter(([id]) => /^tui\.(editor|input)\./.test(id)).flatMap(([, keys]) => bindingKeys(keys)); } catch { /* Older keymap inspection is optional. */ }
|
|
12
|
+
const safeWhileEditing = (key: string) => {
|
|
13
|
+
const textKey = key.replace(/^shift\+/, "");
|
|
14
|
+
if (textKey.length === 1 || textKey === "space" || key === "left" || key === "right") return false;
|
|
15
|
+
// Tab has always switched questions from the answer editor.
|
|
16
|
+
return key === "tab" || key === "shift+tab" || !editorKeys.some(native => sameGoalKey(native, key, isKittyProtocolActive()));
|
|
17
|
+
};
|
|
18
|
+
const editingBindings = new Map(Object.keys(GOAL_DIALOG_KEYS).map(action => {
|
|
19
|
+
const id = action as GoalDialogAction;
|
|
20
|
+
return [id, bindingKeys(binding(id)).filter(safeWhileEditing)] as const;
|
|
21
|
+
}));
|
|
22
|
+
const matches = (data: string, action: GoalDialogAction, editing = false) => (editing ? editingBindings.get(action)! : bindingKeys(binding(action))).some(key => matchesKey(data, key));
|
|
23
|
+
return {
|
|
24
|
+
matches,
|
|
25
|
+
cancelEditing: (data: string) => matchesKey(data, "escape") || matches(data, "cancel", true),
|
|
26
|
+
editorCancelHint: () => formatGoalKeybinding([...new Set(["escape", ...editingBindings.get("cancel")!])] as GoalKeybinding),
|
|
27
|
+
hint,
|
|
28
|
+
navigationHint: () => hint("up") === "↑" && hint("down") === "↓" ? "↑↓" : `${hint("up")} / ${hint("down")}`,
|
|
29
|
+
pagingHint: () => `${hint("pageUp")}/${hint("pageDown")}`.replaceAll("PageUp", "PgUp").replaceAll("PageDown", "PgDn"),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { commandInstructions, type GoalCommands } from "./goal-controls.ts";
|
|
2
|
+
import type { GoalTask, GoalTaskList } from "./goal-record.ts";
|
|
3
|
+
import { promptSafeObjective } from "./goal-contract.ts";
|
|
4
|
+
import { renderConfirmationTasks } from "./goal-task-confirmation.ts";
|
|
5
|
+
|
|
6
|
+
export type GoalDraftingFocus = "goal" | "sisyphus";
|
|
7
|
+
|
|
8
|
+
export interface GoalConfirmationIntentLike {
|
|
9
|
+
focus: GoalDraftingFocus;
|
|
10
|
+
originalTopic: string;
|
|
11
|
+
startedAt?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DraftProposalInput {
|
|
15
|
+
intent: GoalConfirmationIntentLike | null;
|
|
16
|
+
hasUnfinishedGoal: boolean;
|
|
17
|
+
objective: string;
|
|
18
|
+
sisyphus?: boolean;
|
|
19
|
+
draftId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DraftProposalValidation =
|
|
23
|
+
| { ok: true; objective: string; expectedSisyphus: boolean }
|
|
24
|
+
| { ok: false; message: string; clearDrafting?: boolean };
|
|
25
|
+
|
|
26
|
+
export type ToolGateDecision =
|
|
27
|
+
| { block: false }
|
|
28
|
+
| { block: true; reason: string };
|
|
29
|
+
|
|
30
|
+
// ── Shared formatting helpers ──────────────────────────────────────────────
|
|
31
|
+
function formatModeLabel(sisyphus: boolean): string {
|
|
32
|
+
return sisyphus ? "Sisyphus (prompt/criteria style)" : "Normal goal";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatPrefixedLines(content: string): string[] {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
for (const rawLine of content.split("\n")) {
|
|
38
|
+
const trimmed = rawLine.trim();
|
|
39
|
+
if (!trimmed) continue;
|
|
40
|
+
if (trimmed.startsWith("│")) {
|
|
41
|
+
lines.push(rawLine);
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(`│ ${rawLine}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatSection(title: string, content: string): string[] {
|
|
50
|
+
const body = formatPrefixedLines(content);
|
|
51
|
+
return ["", `─── ${title} ───`, "", ...body];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildDraftConfirmationText(args: {
|
|
55
|
+
focus: GoalDraftingFocus;
|
|
56
|
+
originalTopic: string;
|
|
57
|
+
objective: string;
|
|
58
|
+
autoContinue: boolean;
|
|
59
|
+
}): string {
|
|
60
|
+
const lines: string[] = [];
|
|
61
|
+
lines.push("● Goal draft ready for confirmation.");
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push("─── Draft Details ───");
|
|
64
|
+
lines.push(`│ Mode: ${formatModeLabel(args.focus === "sisyphus")}`);
|
|
65
|
+
lines.push(`│ Auto-continue: ${args.autoContinue ? "yes" : "no"}`);
|
|
66
|
+
lines.push(...formatSection("Original Topic", args.originalTopic.trim()));
|
|
67
|
+
lines.push(...formatSection("Proposed Goal", args.objective));
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildTweakConfirmationText(args: {
|
|
72
|
+
currentObjective: string;
|
|
73
|
+
newObjective: string;
|
|
74
|
+
changeSummary: string;
|
|
75
|
+
sisyphus: boolean;
|
|
76
|
+
tasks?: GoalTask[];
|
|
77
|
+
}): string {
|
|
78
|
+
const lines: string[] = [];
|
|
79
|
+
lines.push("● Goal tweak ready for confirmation.");
|
|
80
|
+
lines.push("");
|
|
81
|
+
lines.push("─── Draft Details ───");
|
|
82
|
+
lines.push(`│ Mode: ${formatModeLabel(args.sisyphus)}`);
|
|
83
|
+
lines.push(...formatSection("Change", args.changeSummary));
|
|
84
|
+
lines.push(...formatSection("Current Objective", args.currentObjective));
|
|
85
|
+
lines.push(...formatSection("Proposed New Objective", args.newObjective));
|
|
86
|
+
if (args.tasks && args.tasks.length > 0) {
|
|
87
|
+
const taskLines = renderConfirmationTasks(args.tasks, 0);
|
|
88
|
+
lines.push("");
|
|
89
|
+
lines.push(`┌─ TASKS ─────────────────────────────────────┐`);
|
|
90
|
+
for (const tl of taskLines) lines.push(tl);
|
|
91
|
+
lines.push(`└──────────────────────────────────────────────┘`);
|
|
92
|
+
}
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function evaluateDraftingToolGate(args: {
|
|
97
|
+
toolName: string;
|
|
98
|
+
draftingFocus?: GoalDraftingFocus | null;
|
|
99
|
+
tweakDraftingGoalId?: string | null;
|
|
100
|
+
activeGoalId?: string | null;
|
|
101
|
+
proposeToolName?: string;
|
|
102
|
+
tweakApplyToolName?: string;
|
|
103
|
+
getGoalToolName?: string;
|
|
104
|
+
}): ToolGateDecision {
|
|
105
|
+
// Goal confirmation is prompt-guided, not runtime-enforced. The agent should
|
|
106
|
+
// avoid substantive work before confirmation, but minimal reconnaissance is allowed.
|
|
107
|
+
void args;
|
|
108
|
+
return { block: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function validateGoalDraftProposal(input: DraftProposalInput): DraftProposalValidation {
|
|
112
|
+
if (input.intent === null) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
message: "propose_goal_draft REJECTED: no /goal, /sisyphus, or /goal-tweak guided draft is in progress.",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const expectedSisyphus = input.intent.focus === "sisyphus";
|
|
120
|
+
const actualSisyphus = input.sisyphus === true;
|
|
121
|
+
if (actualSisyphus !== expectedSisyphus) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
message: `propose_goal_draft REJECTED (focus gate): the user invoked ${input.intent.focus === "sisyphus" ? "/sisyphus" : "/goal"} but you passed sisyphus=${actualSisyphus}. Set sisyphus=${expectedSisyphus} to match their choice.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const objective = input.objective.trim();
|
|
129
|
+
if (!objective) {
|
|
130
|
+
return { ok: false, message: "propose_goal_draft REJECTED: objective is empty." };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { ok: true, objective, expectedSisyphus };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function goalDraftingPrompt(topic: string, focus: GoalDraftingFocus, commands?: GoalCommands): string {
|
|
137
|
+
const safeTopic = promptSafeObjective(topic.trim() || "(no topic provided — ask the user what they want to accomplish)");
|
|
138
|
+
const header = focus === "sisyphus"
|
|
139
|
+
? "[GOAL CONFIRMATION focus=sisyphus]\nThe user invoked Sisyphus intent discussion (/sisyphus). Help turn their request into a confirmed goal contract. Do NOT start substantive work yet."
|
|
140
|
+
: "[GOAL CONFIRMATION focus=goal]\nThe user invoked guided goal drafting (/goal). Help turn their request into a confirmed goal contract. Do NOT start substantive work yet.";
|
|
141
|
+
|
|
142
|
+
const commonProtocol = [
|
|
143
|
+
"Confirmation protocol:",
|
|
144
|
+
"- Treat this as a lightweight conversation with the user, not a separate long-running runtime phase.",
|
|
145
|
+
"- If the topic is vague, ask one focused question with a recommended default. Use goal_question or goal_questionnaire when a structured answer would help, but plain conversation is acceptable.",
|
|
146
|
+
"- Targeted read-only research is allowed when it helps define a better goal contract; do not start implementation before confirmation.",
|
|
147
|
+
"- If the topic is already concrete, you may proceed directly to propose_goal_draft.",
|
|
148
|
+
"- The goal contract should make the objective, success criteria, boundaries, constraints, and blocker rule explicit.",
|
|
149
|
+
"- Keep grilling assumptions until the objective, success criteria, boundaries, constraints, and blocker rule are clear enough to confirm.",
|
|
150
|
+
"- If the objective naturally decomposes into trackable milestones, you MUST include the task list in the `tasks` parameter of `propose_goal_draft` so the user can accept both goal and tasks in a single confirmation dialog. Do NOT propose the goal without tasks and then call `propose_task_list` separately.",
|
|
151
|
+
"- For simple single-step goals, no task list is required. The `tasks` parameter can be omitted.",
|
|
152
|
+
"- After goal creation, `propose_task_list` is still available for user-requested task additions or structural changes.",
|
|
153
|
+
"- propose_goal_draft opens the user's Confirm / Continue Chatting dialog. Confirm creates and focuses the goal; Continue Chatting means keep refining through normal proposal cycles.",
|
|
154
|
+
"- Call propose_goal_draft with the complete structured proposal. The tool call renderer is the scrollable, complete human presentation of the objective and task tree — do NOT duplicate the full proposal in a preceding prose message. A brief orientation sentence is fine.",
|
|
155
|
+
"- create_goal is not a shortcut. Direct create_goal calls are rejected so the user keeps explicit say in goal creation.",
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const goalFocusItems = [
|
|
159
|
+
"For /goal, propose a normal goal in this shape when ready:",
|
|
160
|
+
"=== Goal ===",
|
|
161
|
+
"Objective: <one-sentence outcome>",
|
|
162
|
+
"Success criteria: <observable evidence the goal is done>",
|
|
163
|
+
"Boundaries: <in scope / out of scope>",
|
|
164
|
+
"Constraints: <hard rules>",
|
|
165
|
+
"Verification contract: <optional — what verification evidence is required before marking complete, e.g. 'Run npm test (0 failures), grep for remaining references, re-read requirements and confirm every item is addressed'>",
|
|
166
|
+
"If blocked: <default = stop and ask the user>",
|
|
167
|
+
"Call propose_goal_draft with sisyphus=false and autoContinue=true unless the user asked otherwise.",
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const sisyphusFocusItems = [
|
|
171
|
+
"For /sisyphus, remember that Sisyphus is a prompt/criteria style, not a separate step-counter mechanism.",
|
|
172
|
+
"Propose a Sisyphus goal in this shape when ready:",
|
|
173
|
+
"=== Sisyphus Goal ===",
|
|
174
|
+
"Objective: <one-sentence outcome>",
|
|
175
|
+
"Success criteria: <observable evidence the whole ordered goal is done>",
|
|
176
|
+
"Boundaries: <in scope / out of scope>",
|
|
177
|
+
"Constraints: <hard rules, files not to touch, etc.>",
|
|
178
|
+
"Verification contract: <optional — what verification evidence is required before marking complete>",
|
|
179
|
+
"Ordered steps: <preserve the user's requested steps and ordering; do not add preflight or reconnaissance steps they did not ask for>",
|
|
180
|
+
"If blocked / unclear / failing: <default = stop and ask the user>",
|
|
181
|
+
"Sisyphus reminder: Work patiently and sequentially. No rushing, no unrequested preflight steps, no improvising around blockers.",
|
|
182
|
+
"Call propose_goal_draft with sisyphus=true and autoContinue=true unless the user asked otherwise.",
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
return [
|
|
186
|
+
commandInstructions(header, commands),
|
|
187
|
+
"",
|
|
188
|
+
"Topic the user provided:",
|
|
189
|
+
"<goal_topic>",
|
|
190
|
+
safeTopic,
|
|
191
|
+
"</goal_topic>",
|
|
192
|
+
"",
|
|
193
|
+
...commonProtocol,
|
|
194
|
+
"",
|
|
195
|
+
...(focus === "sisyphus" ? sisyphusFocusItems : goalFocusItems).map(line => commandInstructions(line, commands)),
|
|
196
|
+
].join("\n");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
// ── §14 durable proposal summary ─────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
export interface ProposalSummaryArgs {
|
|
203
|
+
objective: string;
|
|
204
|
+
taskList?: GoalTaskList;
|
|
205
|
+
verificationContract?: string;
|
|
206
|
+
autoContinue: boolean;
|
|
207
|
+
auditorEnabled: boolean;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Durable proposal summary written to the terminal transcript (plan §14):
|
|
212
|
+
* proposed objective, proposed plan (numbered top-level tasks), verification,
|
|
213
|
+
* automatic-continuation state, and auditor state. Produced for every proposal
|
|
214
|
+
* outcome (confirm / continue refining / cancel) so the summary is always part
|
|
215
|
+
* of the conversation, not only the confirmation dialog.
|
|
216
|
+
*/
|
|
217
|
+
export function buildProposalSummary(args: ProposalSummaryArgs): string {
|
|
218
|
+
const lines: string[] = ["Proposed objective:", args.objective.trim()];
|
|
219
|
+
if (args.taskList && args.taskList.tasks.length > 0) {
|
|
220
|
+
lines.push("", "Proposed plan:");
|
|
221
|
+
lines.push(...renderProposalPlan(args.taskList.tasks));
|
|
222
|
+
}
|
|
223
|
+
if (args.verificationContract?.trim()) {
|
|
224
|
+
lines.push("", "Verification:", args.verificationContract.trim());
|
|
225
|
+
}
|
|
226
|
+
lines.push("", `Automatic continuation: ${args.autoContinue ? "enabled" : "disabled"}`);
|
|
227
|
+
lines.push(`Independent auditor: ${args.auditorEnabled ? "enabled" : "disabled"}`);
|
|
228
|
+
return lines.join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderProposalPlan(tasks: GoalTask[]): string[] {
|
|
232
|
+
const lines: string[] = [];
|
|
233
|
+
let index = 1;
|
|
234
|
+
for (const t of tasks) {
|
|
235
|
+
lines.push(`${index}. ${t.title}`);
|
|
236
|
+
if (t.subtasks && t.subtasks.length > 0) {
|
|
237
|
+
for (const child of t.subtasks) {
|
|
238
|
+
lines.push(` - ${child.title}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
index++;
|
|
242
|
+
}
|
|
243
|
+
return lines;
|
|
244
|
+
}
|