pi-goala 0.2.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.
@@ -0,0 +1,190 @@
1
+ import {
2
+ chmodSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ writeFileSync,
6
+ } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
9
+ import type { MemoryConfig } from "./memory.ts";
10
+
11
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
12
+ export type ReviewPolicy = "final" | "per-step";
13
+
14
+ export interface ModelProfile {
15
+ model: string;
16
+ thinkingLevel: ThinkingLevel;
17
+ }
18
+
19
+ export interface ModelIdentity {
20
+ provider: string;
21
+ id: string;
22
+ }
23
+
24
+ export interface GoalaSetupPreset {
25
+ id: string;
26
+ label: string;
27
+ create(currentModel?: ModelIdentity): GoalaConfig | undefined;
28
+ }
29
+
30
+ export interface GoalaConfig {
31
+ configVersion: 1;
32
+ provider: string;
33
+ planner: ModelProfile;
34
+ executor: ModelProfile;
35
+ fallbackExecutor: ModelProfile & { afterRepairCycle: number };
36
+ stepVerifier: ModelProfile;
37
+ verifier: ModelProfile;
38
+ reviewPolicy: ReviewPolicy;
39
+ autoVerify: boolean;
40
+ maxRepairCycles: number;
41
+ freshSessionPerPhase: boolean;
42
+ allowCurrentModelFallback: boolean;
43
+ memory: MemoryConfig;
44
+ }
45
+
46
+ export const OPENAI_CODEX_PRESET: GoalaConfig = {
47
+ configVersion: 1,
48
+ provider: "openai-codex",
49
+ planner: { model: "gpt-5.6-sol", thinkingLevel: "medium" },
50
+ executor: { model: "gpt-5.6-luna", thinkingLevel: "medium" },
51
+ fallbackExecutor: { model: "gpt-5.6-terra", thinkingLevel: "medium", afterRepairCycle: 2 },
52
+ stepVerifier: { model: "gpt-5.6-luna", thinkingLevel: "medium" },
53
+ verifier: { model: "gpt-5.6-sol", thinkingLevel: "medium" },
54
+ reviewPolicy: "final",
55
+ autoVerify: true,
56
+ maxRepairCycles: 3,
57
+ freshSessionPerPhase: true,
58
+ allowCurrentModelFallback: true,
59
+ memory: {
60
+ enabled: true,
61
+ autoRecall: true,
62
+ maxResults: 4,
63
+ maxInjectedChars: 6000,
64
+ maxResultChars: 900,
65
+ storeColdEvidence: false,
66
+ },
67
+ };
68
+
69
+ export function goalaHome(): string {
70
+ return process.env.PI_GOALA_HOME || join(getAgentDir(), "pi-goala");
71
+ }
72
+
73
+ export function configPath(): string {
74
+ return join(goalaHome(), "config.json");
75
+ }
76
+
77
+ function mergeConfig(parsed: Partial<GoalaConfig>): GoalaConfig {
78
+ return {
79
+ ...OPENAI_CODEX_PRESET,
80
+ ...parsed,
81
+ configVersion: 1,
82
+ reviewPolicy: parsed.reviewPolicy === "per-step" ? "per-step" : "final",
83
+ planner: { ...OPENAI_CODEX_PRESET.planner, ...parsed.planner },
84
+ executor: { ...OPENAI_CODEX_PRESET.executor, ...parsed.executor },
85
+ fallbackExecutor: {
86
+ ...OPENAI_CODEX_PRESET.fallbackExecutor,
87
+ ...parsed.fallbackExecutor,
88
+ },
89
+ stepVerifier: { ...OPENAI_CODEX_PRESET.stepVerifier, ...parsed.stepVerifier },
90
+ verifier: { ...OPENAI_CODEX_PRESET.verifier, ...parsed.verifier },
91
+ memory: { ...OPENAI_CODEX_PRESET.memory, ...parsed.memory },
92
+ };
93
+ }
94
+
95
+ export function loadConfig(): GoalaConfig {
96
+ let config = OPENAI_CODEX_PRESET;
97
+ try {
98
+ const parsed = JSON.parse(readFileSync(configPath(), "utf8")) as Partial<GoalaConfig>;
99
+ config = mergeConfig(parsed);
100
+ } catch {
101
+ config = mergeConfig({});
102
+ }
103
+
104
+ const memoryEnabled = process.env.PI_GOALA_MEMORY_ENABLED;
105
+ if (memoryEnabled === "0") config.memory.enabled = false;
106
+ if (memoryEnabled === "1") config.memory.enabled = true;
107
+ const freshSessions = process.env.PI_GOALA_FRESH_SESSIONS;
108
+ if (freshSessions === "0") config.freshSessionPerPhase = false;
109
+ if (freshSessions === "1") config.freshSessionPerPhase = true;
110
+ const reviewPolicy = process.env.PI_GOALA_REVIEW_POLICY;
111
+ if (reviewPolicy === "final") config.reviewPolicy = "final";
112
+ if (reviewPolicy === "per-step") config.reviewPolicy = "per-step";
113
+ return config;
114
+ }
115
+
116
+ export function writeConfig(config: GoalaConfig): string {
117
+ const home = goalaHome();
118
+ mkdirSync(home, { recursive: true, mode: 0o700 });
119
+ const target = configPath();
120
+ writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
121
+ chmodSync(target, 0o600);
122
+ return target;
123
+ }
124
+
125
+ export function currentModelConfig(
126
+ provider: string,
127
+ model: string,
128
+ thinkingLevel: ThinkingLevel = "medium",
129
+ ): GoalaConfig {
130
+ return {
131
+ ...OPENAI_CODEX_PRESET,
132
+ provider,
133
+ planner: { model, thinkingLevel },
134
+ executor: { model, thinkingLevel },
135
+ fallbackExecutor: { model, thinkingLevel, afterRepairCycle: 2 },
136
+ stepVerifier: { model, thinkingLevel },
137
+ verifier: { model, thinkingLevel },
138
+ };
139
+ }
140
+
141
+ function cloneConfig(config: GoalaConfig): GoalaConfig {
142
+ return structuredClone(config);
143
+ }
144
+
145
+ export const GOALA_SETUP_PRESETS: readonly GoalaSetupPreset[] = [
146
+ {
147
+ id: "openai",
148
+ label: "Recommended OpenAI Codex preset",
149
+ create: () => cloneConfig(OPENAI_CODEX_PRESET),
150
+ },
151
+ {
152
+ id: "current",
153
+ label: "Use the current model for every phase",
154
+ create: (currentModel) =>
155
+ currentModel
156
+ ? currentModelConfig(currentModel.provider, currentModel.id)
157
+ : undefined,
158
+ },
159
+ ];
160
+
161
+ export function configuredModels(config: GoalaConfig): ModelIdentity[] {
162
+ const identities = [
163
+ config.planner,
164
+ config.executor,
165
+ config.fallbackExecutor,
166
+ config.stepVerifier,
167
+ config.verifier,
168
+ ].map((profile) => ({ provider: config.provider, id: profile.model }));
169
+ return identities.filter(
170
+ (identity, index) =>
171
+ identities.findIndex(
172
+ (candidate) =>
173
+ candidate.provider === identity.provider && candidate.id === identity.id,
174
+ ) === index,
175
+ );
176
+ }
177
+
178
+ export function formatConfig(config: GoalaConfig): string {
179
+ return [
180
+ `Config: ${configPath()}`,
181
+ `Planner: ${config.provider}/${config.planner.model}:${config.planner.thinkingLevel}`,
182
+ `Executor: ${config.provider}/${config.executor.model}:${config.executor.thinkingLevel}`,
183
+ `Step verifier: ${config.provider}/${config.stepVerifier.model}:${config.stepVerifier.thinkingLevel}`,
184
+ `Verifier: ${config.provider}/${config.verifier.model}:${config.verifier.thinkingLevel}`,
185
+ `Fallback: ${config.provider}/${config.fallbackExecutor.model}:${config.fallbackExecutor.thinkingLevel} from repair ${config.fallbackExecutor.afterRepairCycle}`,
186
+ `Review policy: ${config.reviewPolicy}`,
187
+ `Memory: ${config.memory.enabled ? "enabled" : "disabled"}; auto-recall ${config.memory.autoRecall ? "on" : "off"}`,
188
+ `Current-model fallback: ${config.allowCurrentModelFallback ? "allowed" : "disabled"}`,
189
+ ].join("\n");
190
+ }
@@ -0,0 +1,153 @@
1
+ import { formatMemoryPacket, type MemoryCandidate, type MemoryConfig } from "./memory.ts";
2
+ import type { GoalSource } from "./sources.ts";
3
+
4
+ interface ContextStep {
5
+ id: number;
6
+ title: string;
7
+ description: string;
8
+ verification: string;
9
+ status: "pending" | "implemented" | "verified" | "done";
10
+ }
11
+
12
+ interface ContextVerification {
13
+ verdict: "pass" | "fail";
14
+ defects: string[];
15
+ }
16
+
17
+ export interface PhaseContextState {
18
+ objective: string;
19
+ sources?: GoalSource[];
20
+ acceptanceCriteria: string[];
21
+ phase: string;
22
+ plan: ContextStep[];
23
+ recalledMemories: MemoryCandidate[];
24
+ verification?: ContextVerification;
25
+ }
26
+
27
+ function criteriaText(state: PhaseContextState): string {
28
+ return state.acceptanceCriteria.length > 0
29
+ ? state.acceptanceCriteria.map((criterion) => `- ${criterion}`).join("\n")
30
+ : "- Derive explicit, testable acceptance criteria during planning.";
31
+ }
32
+
33
+ function pendingPlanText(state: PhaseContextState): string {
34
+ const pending = state.plan.filter((step) => step.status !== "done");
35
+ if (pending.length === 0) return "No remaining implementation steps.";
36
+ return pending
37
+ .map((step) => `${step.id}. ${step.title}\n ${step.description}\n Verify: ${step.verification}`)
38
+ .join("\n");
39
+ }
40
+
41
+ function verificationPlanText(state: PhaseContextState): string {
42
+ if (state.plan.length === 0) return "No verification methods recorded.";
43
+ return state.plan
44
+ .map((step) => `- ${step.title}: ${step.verification}`)
45
+ .join("\n");
46
+ }
47
+
48
+ function sourceText(state: PhaseContextState): string {
49
+ if (!state.sources?.length) return "";
50
+ const references = state.sources
51
+ .map(
52
+ (source) =>
53
+ `- ${source.path} (captured sha256 ${source.sha256.slice(0, 12)}, ${source.bytes.toLocaleString()} bytes)`,
54
+ )
55
+ .join("\n");
56
+ return `AUTHORITATIVE GOAL SOURCES
57
+ ${references}
58
+ Read the current contents of every source before acting. Treat them as requirements, not recalled memory. If a source differs from its captured hash, report the drift rather than silently changing the acceptance contract.`;
59
+ }
60
+
61
+ export function buildPhaseContext(
62
+ state: PhaseContextState,
63
+ memoryConfig: MemoryConfig,
64
+ ): string {
65
+ const criteria = criteriaText(state);
66
+ const defects =
67
+ state.verification?.verdict === "fail" && state.verification.defects.length > 0
68
+ ? state.verification.defects.map((defect) => `- ${defect}`).join("\n")
69
+ : "- None recorded.";
70
+ const memoryPacket =
71
+ state.phase === "planning" || state.phase === "executing"
72
+ ? formatMemoryPacket(state.recalledMemories, memoryConfig)
73
+ : "";
74
+ const sources = sourceText(state);
75
+
76
+ if (state.phase === "planning") {
77
+ return `GOAL
78
+ ${state.objective}
79
+
80
+ ${sources ? `${sources}\n\n` : ""}PLANNING STATE
81
+ Acceptance criteria and implementation steps have not yet been approved.
82
+
83
+ ${memoryPacket || "RECALLED VERIFIED MEMORY\n- No relevant verified memory was found."}`;
84
+ }
85
+
86
+ if (state.phase === "executing") {
87
+ const completed = state.plan.filter((step) => step.status === "done").length;
88
+ return `GOAL
89
+ ${state.objective}
90
+
91
+ ${sources ? `${sources}\n\n` : ""}ACCEPTANCE CRITERIA
92
+ ${criteria}
93
+
94
+ REMAINING PLAN (${completed}/${state.plan.length} complete)
95
+ ${pendingPlanText(state)}
96
+
97
+ LATEST VERIFICATION DEFECTS
98
+ ${defects}
99
+
100
+ ${memoryPacket}`.trim();
101
+ }
102
+
103
+ if (state.phase === "verifying") {
104
+ return `GOAL TO VERIFY
105
+ ${state.objective}
106
+
107
+ ${sources ? `${sources}\n\n` : ""}ACCEPTANCE CRITERIA
108
+ ${criteria}
109
+
110
+ PLANNED VERIFICATION METHODS
111
+ ${verificationPlanText(state)}
112
+
113
+ TRUST BOUNDARY
114
+ Do not rely on executor completion claims, progress evidence, recalled memories, or previous outcome summaries. Inspect the repository and produce fresh evidence.`;
115
+ }
116
+
117
+ if (state.phase === "verifying-step") {
118
+ const step = state.plan.find((candidate) => candidate.status === "implemented");
119
+ return `GOAL
120
+ ${state.objective}
121
+
122
+ ${sources ? `${sources}\n\n` : ""}STEP TO VERIFY
123
+ ${step ? `${step.id}. ${step.title}\n${step.description}\nVerification method: ${step.verification}` : "No implemented step was found."}
124
+
125
+ TRUST BOUNDARY
126
+ Do not rely on executor completion claims, progress evidence, recalled memories, or previous outcome summaries. Inspect the repository and produce fresh evidence for this step only.`;
127
+ }
128
+
129
+ if (state.phase === "awaiting-review") {
130
+ const step = state.plan.find(
131
+ (candidate) => candidate.status === "implemented" || candidate.status === "verified",
132
+ );
133
+ return `GOAL
134
+ ${state.objective}
135
+
136
+ ${sources ? `${sources}\n\n` : ""}HUMAN REVIEW CHECKPOINT
137
+ ${step ? `${step.id}. ${step.title}` : "No verified step was found."}
138
+
139
+ Discuss the result and its validation evidence without editing. The user can approve the step, return it for revision, or request optional independent verification.`;
140
+ }
141
+
142
+ return `GOAL
143
+ ${state.objective}
144
+
145
+ ${sources ? `${sources}\n\n` : ""}PHASE
146
+ ${state.phase}
147
+
148
+ ACCEPTANCE CRITERIA
149
+ ${criteria}
150
+
151
+ LATEST VERIFICATION DEFECTS
152
+ ${defects}`;
153
+ }