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.
@@ -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
+ };