archondev 0.1.0 → 1.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.
- package/README.md +84 -51
- package/dist/auth-2QIFQZTL.js +12 -0
- package/dist/bug-DXLBBW3U.js +10 -0
- package/dist/{chunk-R6IMTNKV.js → chunk-2CFO5GVH.js} +0 -35
- package/dist/chunk-A7QU6JC6.js +119 -0
- package/dist/chunk-CAYCSBNX.js +202 -0
- package/dist/chunk-EDP55FCI.js +485 -0
- package/dist/chunk-I4ZVNLNO.js +4648 -0
- package/dist/chunk-IMZN36GC.js +159 -0
- package/dist/chunk-JBKFAD4M.js +650 -0
- package/dist/chunk-MOZHC2GX.js +351 -0
- package/dist/chunk-PK3OQVBG.js +91 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/chunk-SMR7JQK6.js +399 -0
- package/dist/chunk-UDBFDXJI.js +696 -0
- package/dist/chunk-UG2ZZ7CM.js +737 -0
- package/dist/chunk-VKM3HAHW.js +832 -0
- package/dist/chunk-WCCBJSNI.js +62 -0
- package/dist/code-review-FSTYDHNG.js +16 -0
- package/dist/execute-LYID2ODD.js +13 -0
- package/dist/index.js +1250 -7206
- package/dist/keys-EL3FUM5O.js +15 -0
- package/dist/list-VXMVEIL5.js +13 -0
- package/dist/{parser-D6PBQUJH.js → parser-M4DI7A24.js} +2 -1
- package/dist/plan-7VSFESVD.js +16 -0
- package/dist/preferences-PL2ON5VY.js +17 -0
- package/dist/review-3R6QXAXQ.js +27 -0
- package/package.json +21 -1
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArchitectureParser
|
|
3
|
+
} from "./chunk-2CFO5GVH.js";
|
|
4
|
+
import {
|
|
5
|
+
ArchitectAgent,
|
|
6
|
+
createAtom,
|
|
7
|
+
validateAtom
|
|
8
|
+
} from "./chunk-MOZHC2GX.js";
|
|
9
|
+
import {
|
|
10
|
+
AnthropicClient,
|
|
11
|
+
getDefaultModel
|
|
12
|
+
} from "./chunk-A7QU6JC6.js";
|
|
13
|
+
import {
|
|
14
|
+
KeyManager
|
|
15
|
+
} from "./chunk-SMR7JQK6.js";
|
|
16
|
+
import {
|
|
17
|
+
isAuthenticated
|
|
18
|
+
} from "./chunk-WCCBJSNI.js";
|
|
19
|
+
|
|
20
|
+
// src/cli/plan.ts
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
import { existsSync } from "fs";
|
|
23
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
24
|
+
import { join } from "path";
|
|
25
|
+
import { createInterface } from "readline";
|
|
26
|
+
|
|
27
|
+
// src/agents/sentinel.ts
|
|
28
|
+
var SYSTEM_PROMPT = `You are the Sentinel, a paranoid and skeptical code reviewer responsible for finding issues in implementation plans.
|
|
29
|
+
|
|
30
|
+
Your role:
|
|
31
|
+
- Critique the Architect's plan with extreme skepticism
|
|
32
|
+
- Identify security vulnerabilities, edge cases, and breaking changes
|
|
33
|
+
- Ensure the plan respects architecture boundaries and invariants
|
|
34
|
+
- Flag any missing steps or incomplete considerations
|
|
35
|
+
- Challenge assumptions and identify risks
|
|
36
|
+
|
|
37
|
+
You are NOT trying to approve plans. You are trying to find problems.
|
|
38
|
+
|
|
39
|
+
Output your validation in the following JSON format:
|
|
40
|
+
{
|
|
41
|
+
"passed": boolean,
|
|
42
|
+
"issues": [
|
|
43
|
+
{
|
|
44
|
+
"severity": "BLOCKER" | "WARNING" | "INFO",
|
|
45
|
+
"type": "SECURITY" | "ARCHITECTURE" | "PERFORMANCE" | "EDGE_CASE" | "BREAKING_CHANGE" | "MISSING_STEP",
|
|
46
|
+
"message": "Description of the issue",
|
|
47
|
+
"suggestion": "How to fix it (optional)"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"suggestions": ["General improvement suggestion 1", ...]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Guidelines:
|
|
54
|
+
- BLOCKER issues must be resolved before implementation
|
|
55
|
+
- WARNING issues should be addressed but won't block
|
|
56
|
+
- INFO issues are nice-to-have improvements
|
|
57
|
+
- Be specific and actionable in your feedback
|
|
58
|
+
- Don't approve plans that violate architecture constraints
|
|
59
|
+
- Consider edge cases, error handling, and rollback scenarios`;
|
|
60
|
+
var SentinelAgent = class {
|
|
61
|
+
client;
|
|
62
|
+
config;
|
|
63
|
+
constructor(config, apiKey) {
|
|
64
|
+
this.config = {
|
|
65
|
+
provider: "anthropic",
|
|
66
|
+
model: config?.model ?? getDefaultModel("sentinel"),
|
|
67
|
+
maxTokens: config?.maxTokens ?? 4096,
|
|
68
|
+
temperature: config?.temperature ?? 0.3
|
|
69
|
+
// Lower temperature for more consistent critiques
|
|
70
|
+
};
|
|
71
|
+
this.client = new AnthropicClient(this.config, apiKey);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate an implementation plan
|
|
75
|
+
*/
|
|
76
|
+
async validatePlan(atom, plan2, architecture) {
|
|
77
|
+
const userMessage = this.buildPrompt(atom, plan2, architecture);
|
|
78
|
+
const response = await this.client.chat(SYSTEM_PROMPT, userMessage, {
|
|
79
|
+
temperature: 0.3,
|
|
80
|
+
maxTokens: 4096
|
|
81
|
+
});
|
|
82
|
+
return this.parseValidationResponse(response.content, response.usage);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the validation prompt
|
|
86
|
+
*/
|
|
87
|
+
buildPrompt(atom, plan2, architecture) {
|
|
88
|
+
const parts = [];
|
|
89
|
+
parts.push("# Atom Being Implemented");
|
|
90
|
+
parts.push(`**Title:** ${atom.title}`);
|
|
91
|
+
parts.push(`**Description:** ${atom.description ?? "No description"}`);
|
|
92
|
+
parts.push("");
|
|
93
|
+
parts.push("**Acceptance Criteria:**");
|
|
94
|
+
atom.acceptanceCriteria.forEach((ac, i) => parts.push(`${i + 1}. ${ac}`));
|
|
95
|
+
parts.push("");
|
|
96
|
+
parts.push("# Implementation Plan to Validate");
|
|
97
|
+
parts.push("");
|
|
98
|
+
parts.push("**Steps:**");
|
|
99
|
+
plan2.steps.forEach((step, i) => parts.push(`${i + 1}. ${step}`));
|
|
100
|
+
parts.push("");
|
|
101
|
+
parts.push("**Files to Modify:**");
|
|
102
|
+
plan2.files_to_modify.forEach((f) => parts.push(`- ${f}`));
|
|
103
|
+
parts.push("");
|
|
104
|
+
parts.push("**Dependencies:**");
|
|
105
|
+
plan2.dependencies.forEach((d) => parts.push(`- ${d}`));
|
|
106
|
+
parts.push("");
|
|
107
|
+
parts.push("**Identified Risks:**");
|
|
108
|
+
plan2.risks.forEach((r) => parts.push(`- ${r}`));
|
|
109
|
+
parts.push("");
|
|
110
|
+
parts.push(`**Estimated Complexity:** ${plan2.estimated_complexity}`);
|
|
111
|
+
parts.push("");
|
|
112
|
+
parts.push("# Architecture Constraints");
|
|
113
|
+
parts.push("");
|
|
114
|
+
if (architecture.invariants.length > 0) {
|
|
115
|
+
parts.push("**Invariants (MUST NOT violate):**");
|
|
116
|
+
architecture.invariants.forEach((inv) => {
|
|
117
|
+
parts.push(`- [${inv.severity}] ${inv.rule}${inv.scope ? ` (scope: ${inv.scope})` : ""}`);
|
|
118
|
+
});
|
|
119
|
+
parts.push("");
|
|
120
|
+
}
|
|
121
|
+
if (architecture.protectedPaths.length > 0) {
|
|
122
|
+
parts.push("**Protected Paths:**");
|
|
123
|
+
architecture.protectedPaths.forEach((p) => {
|
|
124
|
+
parts.push(`- ${p.pattern} (${p.level}): ${p.reason}`);
|
|
125
|
+
});
|
|
126
|
+
parts.push("");
|
|
127
|
+
}
|
|
128
|
+
if (architecture.components.length > 0) {
|
|
129
|
+
parts.push("**Components & Boundaries:**");
|
|
130
|
+
architecture.components.forEach((c) => {
|
|
131
|
+
parts.push(`- ${c.name}: ${c.boundary} (${c.stability})`);
|
|
132
|
+
if (c.forbiddenImports) {
|
|
133
|
+
c.forbiddenImports.forEach((fi) => {
|
|
134
|
+
parts.push(` - Cannot import: ${fi.pattern}`);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
parts.push("");
|
|
139
|
+
}
|
|
140
|
+
parts.push("# Your Task");
|
|
141
|
+
parts.push("");
|
|
142
|
+
parts.push("Critically analyze this plan. Find every possible issue.");
|
|
143
|
+
parts.push("Consider: security, edge cases, error handling, rollback, performance.");
|
|
144
|
+
parts.push("Check that all acceptance criteria can be met by this plan.");
|
|
145
|
+
parts.push("Verify no architecture constraints are violated.");
|
|
146
|
+
parts.push("");
|
|
147
|
+
parts.push("Output your validation as valid JSON.");
|
|
148
|
+
return parts.join("\n");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse validation response from AI
|
|
152
|
+
*/
|
|
153
|
+
parseValidationResponse(content, usage) {
|
|
154
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
155
|
+
if (!jsonMatch) {
|
|
156
|
+
throw new Error("Failed to parse validation: no JSON found in response");
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
160
|
+
const issues = (parsed.issues ?? []).filter((i) => i.message).map((i) => ({
|
|
161
|
+
severity: this.normalizeSeverity(i.severity),
|
|
162
|
+
type: this.normalizeType(i.type),
|
|
163
|
+
message: i.message ?? "",
|
|
164
|
+
suggestion: i.suggestion
|
|
165
|
+
}));
|
|
166
|
+
const hasBlockers = issues.some((i) => i.severity === "BLOCKER");
|
|
167
|
+
return {
|
|
168
|
+
passed: hasBlockers ? false : parsed.passed ?? true,
|
|
169
|
+
issues,
|
|
170
|
+
suggestions: parsed.suggestions ?? [],
|
|
171
|
+
usage
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new Error(`Failed to parse validation JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
normalizeSeverity(value) {
|
|
178
|
+
const normalized = value?.toUpperCase();
|
|
179
|
+
if (normalized === "BLOCKER" || normalized === "WARNING" || normalized === "INFO") {
|
|
180
|
+
return normalized;
|
|
181
|
+
}
|
|
182
|
+
return "WARNING";
|
|
183
|
+
}
|
|
184
|
+
normalizeType(value) {
|
|
185
|
+
const normalized = value?.toUpperCase();
|
|
186
|
+
if (normalized === "SECURITY" || normalized === "ARCHITECTURE" || normalized === "PERFORMANCE" || normalized === "EDGE_CASE" || normalized === "BREAKING_CHANGE" || normalized === "MISSING_STEP") {
|
|
187
|
+
return normalized;
|
|
188
|
+
}
|
|
189
|
+
return "EDGE_CASE";
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/agents/planner.ts
|
|
194
|
+
var DEFAULT_MAX_CYCLES = 3;
|
|
195
|
+
var AdversarialPlanner = class {
|
|
196
|
+
architect;
|
|
197
|
+
sentinel;
|
|
198
|
+
maxCycles;
|
|
199
|
+
constructor(config) {
|
|
200
|
+
this.architect = new ArchitectAgent(config?.architectConfig, config?.apiKey);
|
|
201
|
+
this.sentinel = new SentinelAgent(config?.sentinelConfig, config?.apiKey);
|
|
202
|
+
this.maxCycles = config?.maxCycles ?? DEFAULT_MAX_CYCLES;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Plan an atom through the adversarial loop
|
|
206
|
+
*/
|
|
207
|
+
async planAtom(atom, architecture, context) {
|
|
208
|
+
const iterations = [];
|
|
209
|
+
const totalUsage = {
|
|
210
|
+
inputTokens: 0,
|
|
211
|
+
outputTokens: 0,
|
|
212
|
+
totalTokens: 0,
|
|
213
|
+
baseCost: 0,
|
|
214
|
+
markedUpCost: 0
|
|
215
|
+
};
|
|
216
|
+
let currentPlan = null;
|
|
217
|
+
let approved = false;
|
|
218
|
+
for (let cycle = 1; cycle <= this.maxCycles; cycle++) {
|
|
219
|
+
const architectContext = {
|
|
220
|
+
...context
|
|
221
|
+
};
|
|
222
|
+
if (iterations.length > 0) {
|
|
223
|
+
const lastIteration2 = iterations[iterations.length - 1];
|
|
224
|
+
if (lastIteration2) {
|
|
225
|
+
architectContext.recentLearnings = [
|
|
226
|
+
...context?.recentLearnings ?? [],
|
|
227
|
+
`Previous plan issues (cycle ${cycle - 1}):`,
|
|
228
|
+
...lastIteration2.validation.issues.map(
|
|
229
|
+
(i) => `- [${i.severity}] ${i.message}${i.suggestion ? ` (suggestion: ${i.suggestion})` : ""}`
|
|
230
|
+
),
|
|
231
|
+
...lastIteration2.validation.suggestions.map((s) => `- Suggestion: ${s}`)
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const generatedPlan = await this.architect.generatePlan(atom, architecture, architectContext);
|
|
236
|
+
currentPlan = generatedPlan.plan;
|
|
237
|
+
this.accumulateUsage(totalUsage, generatedPlan.usage);
|
|
238
|
+
const validation = await this.sentinel.validatePlan(atom, currentPlan, architecture);
|
|
239
|
+
this.accumulateUsage(totalUsage, validation.usage);
|
|
240
|
+
iterations.push({
|
|
241
|
+
cycle,
|
|
242
|
+
plan: currentPlan,
|
|
243
|
+
reasoning: generatedPlan.reasoning,
|
|
244
|
+
validation,
|
|
245
|
+
usage: {
|
|
246
|
+
inputTokens: generatedPlan.usage.inputTokens + validation.usage.inputTokens,
|
|
247
|
+
outputTokens: generatedPlan.usage.outputTokens + validation.usage.outputTokens,
|
|
248
|
+
totalTokens: generatedPlan.usage.totalTokens + validation.usage.totalTokens,
|
|
249
|
+
baseCost: generatedPlan.usage.baseCost + validation.usage.baseCost,
|
|
250
|
+
markedUpCost: generatedPlan.usage.markedUpCost + validation.usage.markedUpCost
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
if (validation.passed) {
|
|
254
|
+
approved = true;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
const hasBlockers = validation.issues.some((i) => i.severity === "BLOCKER");
|
|
258
|
+
if (!hasBlockers) {
|
|
259
|
+
approved = true;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const lastIteration = iterations[iterations.length - 1];
|
|
264
|
+
const blockers = lastIteration?.validation.issues.filter((i) => i.severity === "BLOCKER") ?? [];
|
|
265
|
+
return {
|
|
266
|
+
approved,
|
|
267
|
+
finalPlan: approved ? currentPlan : null,
|
|
268
|
+
iterations,
|
|
269
|
+
totalUsage,
|
|
270
|
+
blockers,
|
|
271
|
+
escalationReason: approved ? void 0 : `Failed to resolve ${blockers.length} blocking issues after ${this.maxCycles} cycles`
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
accumulateUsage(total, addition) {
|
|
275
|
+
total.inputTokens += addition.inputTokens;
|
|
276
|
+
total.outputTokens += addition.outputTokens;
|
|
277
|
+
total.totalTokens += addition.totalTokens;
|
|
278
|
+
total.baseCost += addition.baseCost;
|
|
279
|
+
total.markedUpCost += addition.markedUpCost;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/cli/plan.ts
|
|
284
|
+
var ATOMS_DIR = ".archon/atoms";
|
|
285
|
+
function createPrompt() {
|
|
286
|
+
const rl = createInterface({
|
|
287
|
+
input: process.stdin,
|
|
288
|
+
output: process.stdout
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
ask: (question) => new Promise((resolve) => {
|
|
292
|
+
rl.question(question, resolve);
|
|
293
|
+
}),
|
|
294
|
+
close: () => rl.close()
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async function plan(description, options) {
|
|
298
|
+
const prompt = createPrompt();
|
|
299
|
+
try {
|
|
300
|
+
if (!await isAuthenticated()) {
|
|
301
|
+
console.log(chalk.yellow('Not authenticated. Run "archon login" first.'));
|
|
302
|
+
console.log(chalk.dim("For local development, you can continue without authentication."));
|
|
303
|
+
}
|
|
304
|
+
const archPath = join(process.cwd(), "ARCHITECTURE.md");
|
|
305
|
+
if (!existsSync(archPath)) {
|
|
306
|
+
console.error(chalk.red('ARCHITECTURE.md not found. Run "archon init" first.'));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
console.log(chalk.dim("Parsing ARCHITECTURE.md..."));
|
|
310
|
+
const parser = new ArchitectureParser(archPath);
|
|
311
|
+
const parseResult = await parser.parse();
|
|
312
|
+
if (!parseResult.success || !parseResult.schema) {
|
|
313
|
+
console.error(chalk.red("Failed to parse ARCHITECTURE.md:"));
|
|
314
|
+
for (const error of parseResult.errors) {
|
|
315
|
+
console.error(chalk.red(` - ${error.message}`));
|
|
316
|
+
}
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
console.log(chalk.dim("Creating atom from description..."));
|
|
320
|
+
const atomInput = parseAtomDescription(description, options);
|
|
321
|
+
const atom = createAtom(atomInput);
|
|
322
|
+
const validation = validateAtom(atom);
|
|
323
|
+
if (!validation.valid) {
|
|
324
|
+
console.error(chalk.red("Invalid atom:"));
|
|
325
|
+
for (const error of validation.errors) {
|
|
326
|
+
console.error(chalk.red(` - ${error.field}: ${error.message}`));
|
|
327
|
+
}
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
console.log(chalk.blue(`
|
|
331
|
+
Atom created: ${atom.externalId}`));
|
|
332
|
+
console.log(chalk.dim(`Title: ${atom.title}`));
|
|
333
|
+
console.log(chalk.dim(`Acceptance Criteria: ${atom.acceptanceCriteria.length} items`));
|
|
334
|
+
let apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
335
|
+
if (!apiKey) {
|
|
336
|
+
const keyManager = new KeyManager();
|
|
337
|
+
const storedKey = await keyManager.getKey("anthropic");
|
|
338
|
+
if (storedKey) {
|
|
339
|
+
apiKey = storedKey;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!apiKey) {
|
|
343
|
+
console.log(chalk.yellow("\nNo API key configured. Skipping adversarial planning."));
|
|
344
|
+
console.log(chalk.dim('Set ANTHROPIC_API_KEY or use "archon keys add anthropic"'));
|
|
345
|
+
await saveAtom(atom);
|
|
346
|
+
console.log(chalk.green(`
|
|
347
|
+
Atom saved: ${atom.externalId}`));
|
|
348
|
+
console.log(chalk.dim(`Next: Configure API key and run "archon plan ${atom.externalId} --continue"`));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
console.log(chalk.blue("\n\u{1F4CB} Starting adversarial planning..."));
|
|
352
|
+
console.log(chalk.dim("Architect will generate a plan, Sentinel will validate it.\n"));
|
|
353
|
+
const planner = new AdversarialPlanner({ apiKey });
|
|
354
|
+
const planResult = await planner.planAtom(atom, parseResult.schema);
|
|
355
|
+
if (!planResult.approved) {
|
|
356
|
+
console.log(chalk.red("\n\u274C Plan not approved after maximum iterations."));
|
|
357
|
+
console.log(chalk.yellow("Issues found:"));
|
|
358
|
+
for (const iteration of planResult.iterations) {
|
|
359
|
+
if (!iteration.validation.passed) {
|
|
360
|
+
for (const issue of iteration.validation.issues) {
|
|
361
|
+
console.log(chalk.yellow(` - [${issue.severity}] ${issue.message}`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const answer2 = await prompt.ask("\nSave as draft anyway? (y/N): ");
|
|
366
|
+
if (answer2.toLowerCase() !== "y") {
|
|
367
|
+
console.log(chalk.dim("Atom discarded."));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (planResult.finalPlan) {
|
|
372
|
+
console.log(chalk.green("\n\u2705 Plan approved!"));
|
|
373
|
+
displayPlan(planResult.finalPlan);
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.yellow("\nNo approved plan available."));
|
|
376
|
+
}
|
|
377
|
+
console.log(chalk.dim(`
|
|
378
|
+
Token usage: ${planResult.totalUsage.inputTokens} input, ${planResult.totalUsage.outputTokens} output`));
|
|
379
|
+
const answer = await prompt.ask("\nApprove this plan? (y/N/e for edit): ");
|
|
380
|
+
if (answer.toLowerCase() === "e") {
|
|
381
|
+
console.log(chalk.yellow("Edit mode not yet implemented. Saving as draft."));
|
|
382
|
+
atom.status = "DRAFT";
|
|
383
|
+
} else if (answer.toLowerCase() !== "y") {
|
|
384
|
+
console.log(chalk.dim("Atom discarded."));
|
|
385
|
+
return;
|
|
386
|
+
} else {
|
|
387
|
+
atom.plan = planResult.finalPlan;
|
|
388
|
+
atom.status = "READY";
|
|
389
|
+
}
|
|
390
|
+
await saveAtom(atom);
|
|
391
|
+
console.log(chalk.green(`
|
|
392
|
+
\u2705 Atom saved: ${atom.externalId}`));
|
|
393
|
+
console.log(chalk.dim(`Status: ${atom.status}`));
|
|
394
|
+
console.log(chalk.dim(`
|
|
395
|
+
Next steps:`));
|
|
396
|
+
console.log(chalk.dim(` - Execute: archon execute ${atom.externalId}`));
|
|
397
|
+
console.log(chalk.dim(` - View: archon show ${atom.externalId}`));
|
|
398
|
+
} finally {
|
|
399
|
+
prompt.close();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function parseAtomDescription(description, options) {
|
|
403
|
+
const parts = description.split(" - ");
|
|
404
|
+
const title = parts[0]?.trim() ?? description;
|
|
405
|
+
let acceptanceCriteria = [];
|
|
406
|
+
if (parts.length > 1) {
|
|
407
|
+
acceptanceCriteria = (parts[1] ?? "").split(",").map((ac) => ac.trim());
|
|
408
|
+
}
|
|
409
|
+
if (acceptanceCriteria.length === 0) {
|
|
410
|
+
acceptanceCriteria = ["Feature implemented as described", "Typecheck passes", "Tests pass"];
|
|
411
|
+
}
|
|
412
|
+
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
|
|
413
|
+
return {
|
|
414
|
+
title,
|
|
415
|
+
description: description !== title ? description : void 0,
|
|
416
|
+
acceptanceCriteria,
|
|
417
|
+
priority: options.priority ?? 10,
|
|
418
|
+
tags
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function displayPlan(plan2) {
|
|
422
|
+
console.log(chalk.bold("\n\u{1F4DD} Implementation Plan"));
|
|
423
|
+
console.log(chalk.dim("\u2500".repeat(40)));
|
|
424
|
+
console.log(chalk.bold("\nSteps:"));
|
|
425
|
+
plan2.steps.forEach((step, i) => {
|
|
426
|
+
console.log(` ${i + 1}. ${step}`);
|
|
427
|
+
});
|
|
428
|
+
console.log(chalk.bold("\nFiles to modify:"));
|
|
429
|
+
for (const file of plan2.files_to_modify) {
|
|
430
|
+
console.log(` - ${file}`);
|
|
431
|
+
}
|
|
432
|
+
if (plan2.dependencies.length > 0) {
|
|
433
|
+
console.log(chalk.bold("\nDependencies:"));
|
|
434
|
+
for (const dep of plan2.dependencies) {
|
|
435
|
+
console.log(` - ${dep}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (plan2.risks.length > 0) {
|
|
439
|
+
console.log(chalk.bold("\nRisks:"));
|
|
440
|
+
for (const risk of plan2.risks) {
|
|
441
|
+
console.log(chalk.yellow(` \u26A0 ${risk}`));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
console.log(chalk.bold("\nComplexity:"), plan2.estimated_complexity);
|
|
445
|
+
}
|
|
446
|
+
async function saveAtom(atom) {
|
|
447
|
+
const atomsDir = join(process.cwd(), ATOMS_DIR);
|
|
448
|
+
if (!existsSync(atomsDir)) {
|
|
449
|
+
await mkdir(atomsDir, { recursive: true });
|
|
450
|
+
}
|
|
451
|
+
const atomFile = join(atomsDir, `${atom.externalId}.json`);
|
|
452
|
+
await writeFile(atomFile, JSON.stringify(atom, null, 2));
|
|
453
|
+
}
|
|
454
|
+
async function loadAtom(atomId) {
|
|
455
|
+
const atomFile = join(process.cwd(), ATOMS_DIR, `${atomId}.json`);
|
|
456
|
+
if (!existsSync(atomFile)) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const content = await readFile(atomFile, "utf-8");
|
|
460
|
+
return JSON.parse(content);
|
|
461
|
+
}
|
|
462
|
+
async function listLocalAtoms() {
|
|
463
|
+
const atomsDir = join(process.cwd(), ATOMS_DIR);
|
|
464
|
+
if (!existsSync(atomsDir)) {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
const { readdir } = await import("fs/promises");
|
|
468
|
+
const files = await readdir(atomsDir);
|
|
469
|
+
const atoms = [];
|
|
470
|
+
for (const file of files) {
|
|
471
|
+
if (file.endsWith(".json")) {
|
|
472
|
+
const atom = await loadAtom(file.replace(".json", ""));
|
|
473
|
+
if (atom) {
|
|
474
|
+
atoms.push(atom);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return atoms;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export {
|
|
482
|
+
plan,
|
|
483
|
+
loadAtom,
|
|
484
|
+
listLocalAtoms
|
|
485
|
+
};
|