@almightygpt/core 0.9.1 → 0.10.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.
Files changed (60) hide show
  1. package/dist/adapters/claude.d.ts.map +1 -1
  2. package/dist/adapters/claude.js +3 -1
  3. package/dist/adapters/claude.js.map +1 -1
  4. package/dist/adapters/defaults.d.ts +34 -0
  5. package/dist/adapters/defaults.d.ts.map +1 -0
  6. package/dist/adapters/defaults.js +41 -0
  7. package/dist/adapters/defaults.js.map +1 -0
  8. package/dist/adapters/factory.d.ts +12 -0
  9. package/dist/adapters/factory.d.ts.map +1 -0
  10. package/dist/adapters/factory.js +40 -0
  11. package/dist/adapters/factory.js.map +1 -0
  12. package/dist/adapters/gemini.d.ts.map +1 -1
  13. package/dist/adapters/gemini.js +3 -1
  14. package/dist/adapters/gemini.js.map +1 -1
  15. package/dist/adapters/openai.d.ts.map +1 -1
  16. package/dist/adapters/openai.js +3 -1
  17. package/dist/adapters/openai.js.map +1 -1
  18. package/dist/auth/keychain.d.ts +11 -1
  19. package/dist/auth/keychain.d.ts.map +1 -1
  20. package/dist/auth/keychain.js +13 -4
  21. package/dist/auth/keychain.js.map +1 -1
  22. package/dist/auth/resolver.d.ts.map +1 -1
  23. package/dist/auth/resolver.js +19 -3
  24. package/dist/auth/resolver.js.map +1 -1
  25. package/dist/auth/types.d.ts +18 -5
  26. package/dist/auth/types.d.ts.map +1 -1
  27. package/dist/auth/types.js +8 -6
  28. package/dist/auth/types.js.map +1 -1
  29. package/dist/auth/validator.d.ts +23 -3
  30. package/dist/auth/validator.d.ts.map +1 -1
  31. package/dist/auth/validator.js +126 -21
  32. package/dist/auth/validator.js.map +1 -1
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +4 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/plan/run-plan-review.d.ts +40 -0
  38. package/dist/plan/run-plan-review.d.ts.map +1 -0
  39. package/dist/plan/run-plan-review.js +224 -0
  40. package/dist/plan/run-plan-review.js.map +1 -0
  41. package/dist/plan/run-plan.d.ts +42 -0
  42. package/dist/plan/run-plan.d.ts.map +1 -0
  43. package/dist/plan/run-plan.js +193 -0
  44. package/dist/plan/run-plan.js.map +1 -0
  45. package/dist/runs/types.d.ts +1 -1
  46. package/dist/runs/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/adapters/claude.ts +3 -1
  49. package/src/adapters/defaults.ts +44 -0
  50. package/src/adapters/factory.ts +45 -0
  51. package/src/adapters/gemini.ts +3 -1
  52. package/src/adapters/openai.ts +3 -1
  53. package/src/auth/keychain.ts +20 -6
  54. package/src/auth/resolver.ts +23 -3
  55. package/src/auth/types.ts +27 -8
  56. package/src/auth/validator.ts +149 -25
  57. package/src/index.ts +13 -1
  58. package/src/plan/run-plan-review.ts +302 -0
  59. package/src/plan/run-plan.ts +247 -0
  60. package/src/runs/types.ts +3 -1
@@ -0,0 +1,247 @@
1
+ /**
2
+ * `almightygpt plan` — Worker AI reads a free-text requirement +
3
+ * project memory, produces a structured plan markdown.
4
+ *
5
+ * Distinct from the review pipeline because the INPUT is a
6
+ * requirement (a sentence or paragraph from the user describing
7
+ * what they want), not a git diff. Output is a plan doc with
8
+ * required sections — same shape on every run so the Reviewer
9
+ * downstream knows what to expect.
10
+ *
11
+ * Plans land at `docs/<worker>-plans/<topic>.md`. The Reviewer step
12
+ * then runs against this plan via the `review --plan <file>` mode
13
+ * (see run-plan-review.ts).
14
+ */
15
+
16
+ import { mkdir, writeFile } from "node:fs/promises";
17
+ import { dirname, join } from "node:path";
18
+ import { loadConfig } from "../config/load.js";
19
+ import { makeAdapter } from "../adapters/factory.js";
20
+ import { AdapterError } from "../adapters/types.js";
21
+ import { assembleMemory } from "../review/memory.js";
22
+ import {
23
+ createRunFolder,
24
+ writeRunMetadata,
25
+ writeRunInput,
26
+ writeAgentOutput,
27
+ collectGitContext,
28
+ } from "../runs/folder.js";
29
+ import { assertSafeToWrite } from "../git/status.js";
30
+
31
+ export interface PlanOptions {
32
+ repoRoot: string;
33
+ topic: string;
34
+ requirement: string;
35
+ worker?: string;
36
+ force?: boolean;
37
+ }
38
+
39
+ export interface PlanResult {
40
+ planPath: string;
41
+ planBytes: number;
42
+ worker: string;
43
+ provider: string;
44
+ modelUsed: string;
45
+ tokensIn: number;
46
+ cachedTokensIn: number;
47
+ tokensOut: number;
48
+ costUsd: number;
49
+ latencyMs: number;
50
+ memorySources: { path: string; bytes: number }[];
51
+ memoryMissing: string[];
52
+ runId: string;
53
+ runFolder: string;
54
+ }
55
+
56
+ const PLAN_SYSTEM_FRAMING = [
57
+ "You are the Worker AI in an AlmightyGPT Plan stage.",
58
+ "",
59
+ "WHAT YOU ARE DOING: turning a free-text requirement (the user message",
60
+ "below) into a structured plan markdown that a Reviewer AI will then",
61
+ "critique. You are NOT writing code — only the plan. The human will",
62
+ "approve / reject / refine the plan, and only then will implementation",
63
+ "happen (in a separate tool).",
64
+ "",
65
+ "WHAT THE ORCHESTRATOR OWNS (do NOT include these in your response):",
66
+ " - The H1 title (the orchestrator prepends `# Plan: <topic>`).",
67
+ " - The header block with model / tokens / cost.",
68
+ "",
69
+ "WHAT YOU MUST PRODUCE — emit exactly these sections in this order:",
70
+ " ## Goal",
71
+ " ## Affected modules / surfaces",
72
+ " ## Step-by-step approach",
73
+ " ## Risks",
74
+ " ## Test plan",
75
+ " ## Open questions",
76
+ "",
77
+ "STYLE:",
78
+ " - Be concrete. Name specific files, classes, functions, env vars.",
79
+ " - Write so the Reviewer can find concrete things to critique.",
80
+ " - Open questions are not a weakness; they're the things you'd ask",
81
+ " the human if you could. Surface them.",
82
+ ].join("\n");
83
+
84
+ function buildPlanUserMessage(topic: string, requirement: string): string {
85
+ return [
86
+ `# Plan request — topic: \`${topic}\``,
87
+ "",
88
+ "## Requirement (from human)",
89
+ "",
90
+ requirement.trim(),
91
+ "",
92
+ "## Your task",
93
+ "",
94
+ "Produce the plan markdown using the structure in your system prompt.",
95
+ "Start your response with `## Goal` (no H1, no preamble).",
96
+ ].join("\n");
97
+ }
98
+
99
+ export async function runWorkerPlan(opts: PlanOptions): Promise<PlanResult> {
100
+ const config = await loadConfig(opts.repoRoot);
101
+
102
+ const workerName = opts.worker ?? config.defaults.worker;
103
+ if (!workerName) {
104
+ throw new Error(
105
+ "No worker specified. Pass --worker <name> or set defaults.worker in .almightygpt/config.yaml.",
106
+ );
107
+ }
108
+ const agentConfig = config.agents[workerName];
109
+ if (!agentConfig) {
110
+ throw new Error(
111
+ `Worker "${workerName}" not found in .almightygpt/config.yaml agents map.`,
112
+ );
113
+ }
114
+ if (!agentConfig.enabled) {
115
+ throw new Error(`Worker "${workerName}" is disabled in .almightygpt/config.yaml.`);
116
+ }
117
+
118
+ const adapter = makeAdapter(workerName, agentConfig.provider);
119
+ if (!(await adapter.isAvailable())) {
120
+ throw new AdapterError(
121
+ `Adapter "${workerName}" (${agentConfig.provider}) is not available. Set the provider's API key.`,
122
+ workerName,
123
+ );
124
+ }
125
+
126
+ // Plans land at docs/<worker>-plans/<topic>.md — symmetric with reviews.
127
+ const plansDir = `docs/${workerName}-plans`;
128
+ const planRel = `${plansDir}/${opts.topic}.md`;
129
+ await assertSafeToWrite(opts.repoRoot, planRel, opts.force ?? false);
130
+
131
+ const runFolder = await createRunFolder({
132
+ repoRoot: opts.repoRoot,
133
+ runsDir: config.runsDir,
134
+ topic: opts.topic,
135
+ type: "plan",
136
+ });
137
+ const createdAt = new Date().toISOString();
138
+
139
+ const memory = await assembleMemory(opts.repoRoot, agentConfig.memoryFile);
140
+ const systemPrompt = PLAN_SYSTEM_FRAMING + "\n\n" + memory.text;
141
+ const userMessage = buildPlanUserMessage(opts.topic, opts.requirement);
142
+
143
+ await writeRunInput(runFolder.absPath, userMessage);
144
+
145
+ const adapterOut = await adapter.execute({
146
+ role: "worker",
147
+ systemPrompt,
148
+ userMessage,
149
+ });
150
+
151
+ await writeAgentOutput(runFolder.absPath, "worker", adapterOut.content);
152
+
153
+ // Compose the final plan file: orchestrator-owned header + worker body.
154
+ const header = [
155
+ `# Plan: ${opts.topic}`,
156
+ "",
157
+ `> Worker: \`${workerName}\` (${adapter.provider}, ${adapterOut.modelUsed})`,
158
+ `> Generated: ${new Date().toISOString()}`,
159
+ `> Tokens: ${adapterOut.tokensIn} in / ${adapterOut.tokensOut} out`,
160
+ `> Cost: $${adapterOut.costUsd.toFixed(4)} USD`,
161
+ `> Run folder: \`${runFolder.relPath}\``,
162
+ "",
163
+ ].join("\n");
164
+
165
+ const footer = [
166
+ "",
167
+ "---",
168
+ "",
169
+ "## How this plan was produced",
170
+ "",
171
+ `The AlmightyGPT Plan stage produced this. Worker (\`${workerName}\`)`,
172
+ `read the requirement above + the project's memory files.`,
173
+ "",
174
+ "**Next stage:** cross-AI review with `almightygpt review --plan",
175
+ `${planRel} --reviewer codex --topic ${opts.topic}\` (Reviewer critiques`,
176
+ "this plan), then human approval, then implementation in your",
177
+ "preferred coding tool.",
178
+ ].join("\n");
179
+
180
+ const planContent = header + adapterOut.content.trim() + footer;
181
+ const planAbs = join(opts.repoRoot, planRel);
182
+ await mkdir(dirname(planAbs), { recursive: true });
183
+ await writeFile(planAbs, planContent, "utf8");
184
+
185
+ const gitContext = await collectGitContext(opts.repoRoot);
186
+ await writeRunMetadata(runFolder.absPath, {
187
+ id: runFolder.id,
188
+ type: "plan",
189
+ createdAt,
190
+ finishedAt: new Date().toISOString(),
191
+ workspacePath: opts.repoRoot,
192
+ topic: opts.topic,
193
+ git: gitContext,
194
+ input: { source: "requirement-file" },
195
+ agents: [
196
+ {
197
+ name: workerName,
198
+ role: "worker",
199
+ provider: agentConfig.provider,
200
+ enabled: true,
201
+ },
202
+ ],
203
+ adapterVersions: [],
204
+ status: "completed",
205
+ metrics: [
206
+ {
207
+ agent: workerName,
208
+ role: "worker",
209
+ provider: adapter.provider,
210
+ model: adapterOut.modelUsed,
211
+ tokensIn: adapterOut.tokensIn,
212
+ cachedTokensIn: adapterOut.cachedTokensIn ?? 0,
213
+ tokensOut: adapterOut.tokensOut,
214
+ costUsd: adapterOut.costUsd,
215
+ latencyMs: adapterOut.latencyMs,
216
+ },
217
+ ],
218
+ totals: {
219
+ tokensIn: adapterOut.tokensIn,
220
+ tokensOut: adapterOut.tokensOut,
221
+ costUsd: adapterOut.costUsd,
222
+ latencyMs: adapterOut.latencyMs,
223
+ },
224
+ reviewPath: planRel,
225
+ budget: {
226
+ maxCostPerRunUsd: config.budget.maxCostPerRunUsd,
227
+ maxTokensPerRun: config.budget.maxTokensPerRun,
228
+ },
229
+ });
230
+
231
+ return {
232
+ planPath: planRel,
233
+ planBytes: planContent.length,
234
+ worker: workerName,
235
+ provider: adapter.provider,
236
+ modelUsed: adapterOut.modelUsed,
237
+ tokensIn: adapterOut.tokensIn,
238
+ cachedTokensIn: adapterOut.cachedTokensIn ?? 0,
239
+ tokensOut: adapterOut.tokensOut,
240
+ costUsd: adapterOut.costUsd,
241
+ latencyMs: adapterOut.latencyMs,
242
+ memorySources: memory.sources,
243
+ memoryMissing: memory.missing,
244
+ runId: runFolder.id,
245
+ runFolder: runFolder.relPath,
246
+ };
247
+ }
package/src/runs/types.ts CHANGED
@@ -14,7 +14,9 @@ export type RunType =
14
14
  | "review-diff"
15
15
  | "review-path"
16
16
  | "review-requirement"
17
- | "review-worker-reviewer";
17
+ | "review-worker-reviewer"
18
+ | "plan"
19
+ | "review-plan";
18
20
 
19
21
  export type RunStatus = "running" | "completed" | "failed" | "aborted_budget";
20
22