archondev 1.2.1 → 1.6.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 +26 -4
- package/dist/{chunk-UG2ZZ7CM.js → chunk-K2VBICW3.js} +61 -40
- package/dist/{chunk-PK3OQVBG.js → chunk-NCJN4X5A.js} +1 -1
- package/dist/{chunk-I4ZVNLNO.js → chunk-S7UXMYWS.js} +212 -27
- package/dist/chunk-TJXGZB6T.js +342 -0
- package/dist/{execute-LYID2ODD.js → execute-6ZGARWT2.js} +3 -3
- package/dist/index.js +781 -525
- package/dist/init-YLAHY4CV.js +9 -0
- package/dist/{list-VXMVEIL5.js → list-UB4MOGRH.js} +3 -3
- package/dist/{plan-7VSFESVD.js → plan-WQUFH2WB.js} +2 -2
- package/dist/{preferences-PL2ON5VY.js → preferences-QEFXVCZN.js} +1 -1
- package/package.json +1 -1
- package/dist/{chunk-EDP55FCI.js → chunk-3AAQEUY6.js} +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
addKey,
|
|
4
|
+
listKeys,
|
|
5
|
+
removeKey,
|
|
6
|
+
setPrimaryKey
|
|
7
|
+
} from "./chunk-IMZN36GC.js";
|
|
8
8
|
import {
|
|
9
9
|
reviewAnalyze,
|
|
10
10
|
reviewExport,
|
|
@@ -18,6 +18,12 @@ import {
|
|
|
18
18
|
reviewUpdate
|
|
19
19
|
} from "./chunk-UDBFDXJI.js";
|
|
20
20
|
import "./chunk-VKM3HAHW.js";
|
|
21
|
+
import {
|
|
22
|
+
listModels,
|
|
23
|
+
resetPreferences,
|
|
24
|
+
setPreference,
|
|
25
|
+
showPreferences
|
|
26
|
+
} from "./chunk-K2VBICW3.js";
|
|
21
27
|
import {
|
|
22
28
|
API_URL,
|
|
23
29
|
SUPABASE_ANON_KEY,
|
|
@@ -27,397 +33,58 @@ import {
|
|
|
27
33
|
status
|
|
28
34
|
} from "./chunk-CAYCSBNX.js";
|
|
29
35
|
import {
|
|
36
|
+
init,
|
|
37
|
+
isInitialized
|
|
38
|
+
} from "./chunk-TJXGZB6T.js";
|
|
39
|
+
import {
|
|
40
|
+
DependencyParser,
|
|
30
41
|
EnvironmentConfigLoader,
|
|
31
42
|
EnvironmentValidator,
|
|
32
43
|
execute
|
|
33
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-S7UXMYWS.js";
|
|
34
45
|
import {
|
|
35
46
|
list
|
|
36
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-NCJN4X5A.js";
|
|
37
48
|
import {
|
|
38
49
|
listLocalAtoms,
|
|
39
50
|
loadAtom,
|
|
40
51
|
plan
|
|
41
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-3AAQEUY6.js";
|
|
53
|
+
import "./chunk-SMR7JQK6.js";
|
|
42
54
|
import "./chunk-2CFO5GVH.js";
|
|
43
55
|
import {
|
|
44
56
|
bugReport
|
|
45
57
|
} from "./chunk-JBKFAD4M.js";
|
|
46
58
|
import "./chunk-MOZHC2GX.js";
|
|
47
59
|
import "./chunk-A7QU6JC6.js";
|
|
48
|
-
import {
|
|
49
|
-
addKey,
|
|
50
|
-
listKeys,
|
|
51
|
-
removeKey,
|
|
52
|
-
setPrimaryKey
|
|
53
|
-
} from "./chunk-IMZN36GC.js";
|
|
54
|
-
import "./chunk-SMR7JQK6.js";
|
|
55
60
|
import {
|
|
56
61
|
loadConfig
|
|
57
62
|
} from "./chunk-WCCBJSNI.js";
|
|
58
63
|
import "./chunk-QGM4M3NI.js";
|
|
59
64
|
|
|
60
65
|
// src/cli/index.ts
|
|
61
|
-
import { Command } from "commander";
|
|
66
|
+
import { Command as Command2 } from "commander";
|
|
62
67
|
import chalk6 from "chalk";
|
|
63
68
|
import "dotenv/config";
|
|
64
69
|
|
|
65
|
-
// src/cli/init.ts
|
|
66
|
-
import { readdir, readFile, writeFile, mkdir } from "fs/promises";
|
|
67
|
-
import { existsSync } from "fs";
|
|
68
|
-
import { join, extname } from "path";
|
|
69
|
-
import { execSync } from "child_process";
|
|
70
|
-
import chalk from "chalk";
|
|
71
|
-
import ora from "ora";
|
|
72
|
-
import readline from "readline";
|
|
73
|
-
function isInitialized(cwd) {
|
|
74
|
-
const archMdPath = join(cwd, "ARCHITECTURE.md");
|
|
75
|
-
const archonDir = join(cwd, ".archon");
|
|
76
|
-
return existsSync(archMdPath) || existsSync(archonDir);
|
|
77
|
-
}
|
|
78
|
-
var LANGUAGE_EXTENSIONS = {
|
|
79
|
-
".ts": "TypeScript",
|
|
80
|
-
".tsx": "TypeScript",
|
|
81
|
-
".js": "JavaScript",
|
|
82
|
-
".jsx": "JavaScript",
|
|
83
|
-
".py": "Python",
|
|
84
|
-
".go": "Go",
|
|
85
|
-
".rs": "Rust",
|
|
86
|
-
".java": "Java",
|
|
87
|
-
".rb": "Ruby",
|
|
88
|
-
".php": "PHP",
|
|
89
|
-
".cs": "C#",
|
|
90
|
-
".cpp": "C++",
|
|
91
|
-
".c": "C",
|
|
92
|
-
".swift": "Swift",
|
|
93
|
-
".kt": "Kotlin",
|
|
94
|
-
".vue": "Vue",
|
|
95
|
-
".svelte": "Svelte"
|
|
96
|
-
};
|
|
97
|
-
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
98
|
-
"node_modules",
|
|
99
|
-
".git",
|
|
100
|
-
"dist",
|
|
101
|
-
"build",
|
|
102
|
-
".next",
|
|
103
|
-
"coverage",
|
|
104
|
-
"__pycache__",
|
|
105
|
-
".venv",
|
|
106
|
-
"venv",
|
|
107
|
-
"target",
|
|
108
|
-
"vendor",
|
|
109
|
-
".archon"
|
|
110
|
-
]);
|
|
111
|
-
async function init(options = {}) {
|
|
112
|
-
const cwd = process.cwd();
|
|
113
|
-
const archMdPath = join(cwd, "ARCHITECTURE.md");
|
|
114
|
-
const archonDir = join(cwd, ".archon");
|
|
115
|
-
console.log(chalk.blue("\n\u{1F3DB}\uFE0F ArchonDev Initialization\n"));
|
|
116
|
-
if (existsSync(archMdPath)) {
|
|
117
|
-
const overwrite = await promptYesNo("ARCHITECTURE.md already exists. Overwrite?", false);
|
|
118
|
-
if (!overwrite) {
|
|
119
|
-
console.log(chalk.yellow("Initialization cancelled."));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const spinner = ora("Detecting project capabilities...").start();
|
|
124
|
-
const capabilities = await detectCapabilities(cwd);
|
|
125
|
-
spinner.succeed("Project capabilities detected");
|
|
126
|
-
spinner.start("Scanning codebase...");
|
|
127
|
-
const stats = await scanRepository(cwd);
|
|
128
|
-
spinner.succeed(`Scanned ${stats.totalFiles} files (${formatLines(stats.totalLines)} lines)`);
|
|
129
|
-
if (options.git !== false && !capabilities.hasGit) {
|
|
130
|
-
spinner.start("Initializing git repository...");
|
|
131
|
-
try {
|
|
132
|
-
execSync("git init", { cwd, stdio: "pipe" });
|
|
133
|
-
spinner.succeed("Git repository initialized");
|
|
134
|
-
} catch {
|
|
135
|
-
spinner.warn("Could not initialize git");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!existsSync(archonDir)) {
|
|
139
|
-
await mkdir(archonDir, { recursive: true });
|
|
140
|
-
}
|
|
141
|
-
spinner.start("Generating ARCHITECTURE.md...");
|
|
142
|
-
const archContent = generateArchitectureMd(capabilities, stats, options.analyze);
|
|
143
|
-
await writeFile(archMdPath, archContent);
|
|
144
|
-
spinner.succeed("ARCHITECTURE.md created");
|
|
145
|
-
printSummary(capabilities, stats, options.analyze);
|
|
146
|
-
}
|
|
147
|
-
function promptYesNo(question, defaultValue) {
|
|
148
|
-
return new Promise((resolve) => {
|
|
149
|
-
const rl = readline.createInterface({
|
|
150
|
-
input: process.stdin,
|
|
151
|
-
output: process.stdout
|
|
152
|
-
});
|
|
153
|
-
const hint = defaultValue ? "(Y/n)" : "(y/N)";
|
|
154
|
-
rl.question(`${question} ${hint} `, (answer) => {
|
|
155
|
-
rl.close();
|
|
156
|
-
if (answer.trim() === "") {
|
|
157
|
-
resolve(defaultValue);
|
|
158
|
-
} else {
|
|
159
|
-
resolve(answer.toLowerCase().startsWith("y"));
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
async function detectCapabilities(cwd) {
|
|
165
|
-
const hasFile = (name) => existsSync(join(cwd, name));
|
|
166
|
-
const hasJsonDep = async (name) => {
|
|
167
|
-
try {
|
|
168
|
-
const pkg = JSON.parse(await readFile(join(cwd, "package.json"), "utf-8"));
|
|
169
|
-
return !!(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
|
170
|
-
} catch {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
let packageManager = null;
|
|
175
|
-
if (hasFile("pnpm-lock.yaml")) packageManager = "pnpm";
|
|
176
|
-
else if (hasFile("yarn.lock")) packageManager = "yarn";
|
|
177
|
-
else if (hasFile("bun.lockb")) packageManager = "bun";
|
|
178
|
-
else if (hasFile("package-lock.json")) packageManager = "npm";
|
|
179
|
-
const hasTypeScript = hasFile("tsconfig.json") || await hasJsonDep("typescript");
|
|
180
|
-
const hasESLint = hasFile(".eslintrc") || hasFile(".eslintrc.js") || hasFile("eslint.config.js") || await hasJsonDep("eslint");
|
|
181
|
-
const hasJest = hasFile("jest.config.js") || hasFile("jest.config.ts") || await hasJsonDep("jest") || await hasJsonDep("vitest");
|
|
182
|
-
const hasPrettier = hasFile(".prettierrc") || hasFile(".prettierrc.js") || await hasJsonDep("prettier");
|
|
183
|
-
const hasGit = hasFile(".git");
|
|
184
|
-
return {
|
|
185
|
-
hasTypeScript,
|
|
186
|
-
hasESLint,
|
|
187
|
-
hasJest,
|
|
188
|
-
hasPrettier,
|
|
189
|
-
hasGit,
|
|
190
|
-
packageManager,
|
|
191
|
-
primaryLanguage: hasTypeScript ? "TypeScript" : "JavaScript"
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
async function scanRepository(cwd) {
|
|
195
|
-
const stats = {
|
|
196
|
-
totalFiles: 0,
|
|
197
|
-
totalLines: 0,
|
|
198
|
-
languageBreakdown: {},
|
|
199
|
-
directories: []
|
|
200
|
-
};
|
|
201
|
-
async function scanDir(dir, depth = 0) {
|
|
202
|
-
if (depth > 10) return;
|
|
203
|
-
try {
|
|
204
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
205
|
-
for (const entry of entries) {
|
|
206
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
207
|
-
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
|
|
208
|
-
const fullPath = join(dir, entry.name);
|
|
209
|
-
if (entry.isDirectory()) {
|
|
210
|
-
if (depth < 2) {
|
|
211
|
-
const relativePath = fullPath.replace(cwd, "").replace(/^\//, "");
|
|
212
|
-
if (relativePath) {
|
|
213
|
-
stats.directories.push(relativePath);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
await scanDir(fullPath, depth + 1);
|
|
217
|
-
} else if (entry.isFile()) {
|
|
218
|
-
const ext = extname(entry.name).toLowerCase();
|
|
219
|
-
const language = LANGUAGE_EXTENSIONS[ext];
|
|
220
|
-
if (language) {
|
|
221
|
-
stats.totalFiles++;
|
|
222
|
-
try {
|
|
223
|
-
const content = await readFile(fullPath, "utf-8");
|
|
224
|
-
const lines = content.split("\n").length;
|
|
225
|
-
stats.totalLines += lines;
|
|
226
|
-
if (!stats.languageBreakdown[language]) {
|
|
227
|
-
stats.languageBreakdown[language] = { files: 0, lines: 0 };
|
|
228
|
-
}
|
|
229
|
-
stats.languageBreakdown[language].files++;
|
|
230
|
-
stats.languageBreakdown[language].lines += lines;
|
|
231
|
-
} catch {
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
} catch {
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
await scanDir(cwd);
|
|
240
|
-
return stats;
|
|
241
|
-
}
|
|
242
|
-
function generateArchitectureMd(capabilities, stats, analyze) {
|
|
243
|
-
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
244
|
-
const suggestedComponents = stats.directories.filter((d) => !d.includes("/")).slice(0, 10).map((dir) => ({
|
|
245
|
-
id: dir.replace(/[^a-zA-Z0-9]/g, "-"),
|
|
246
|
-
name: dir.charAt(0).toUpperCase() + dir.slice(1),
|
|
247
|
-
path: `${dir}/**`
|
|
248
|
-
}));
|
|
249
|
-
const qualityGates = [];
|
|
250
|
-
if (capabilities.hasTypeScript) qualityGates.push("SYNTAX");
|
|
251
|
-
if (capabilities.hasESLint) qualityGates.push("LINT");
|
|
252
|
-
if (capabilities.hasJest) qualityGates.push("UNIT");
|
|
253
|
-
return `---
|
|
254
|
-
version: "1.0"
|
|
255
|
-
updatedAt: "${now}"
|
|
256
|
-
profile: "balanced"
|
|
257
|
-
strictMode: true
|
|
258
|
-
|
|
259
|
-
# System Goals
|
|
260
|
-
systemGoals:
|
|
261
|
-
- id: "GOAL-001"
|
|
262
|
-
title: "Maintain code quality"
|
|
263
|
-
description: "All changes must pass quality gates before merging"
|
|
264
|
-
priority: "HIGH"
|
|
265
|
-
|
|
266
|
-
- id: "GOAL-002"
|
|
267
|
-
title: "Preserve architectural boundaries"
|
|
268
|
-
description: "Components should not violate defined boundaries"
|
|
269
|
-
priority: "HIGH"
|
|
270
|
-
|
|
271
|
-
# Components & Boundaries
|
|
272
|
-
# Detected ${suggestedComponents.length} top-level directories
|
|
273
|
-
components:
|
|
274
|
-
${suggestedComponents.map((c) => ` - id: "${c.id}"
|
|
275
|
-
name: "${c.name}"
|
|
276
|
-
paths:
|
|
277
|
-
- "${c.path}"
|
|
278
|
-
boundary: "INTERNAL"
|
|
279
|
-
stability: "EVOLVING"`).join("\n\n")}
|
|
280
|
-
|
|
281
|
-
# Invariants (Rules to enforce)
|
|
282
|
-
invariants:
|
|
283
|
-
- id: "INV-001"
|
|
284
|
-
severity: "WARN"
|
|
285
|
-
rule: "Avoid console.log in production code"
|
|
286
|
-
match: "console\\\\.log"
|
|
287
|
-
scope: "src"
|
|
288
|
-
reason: "Use proper logging library instead"
|
|
289
|
-
|
|
290
|
-
# Protected Paths
|
|
291
|
-
protectedPaths:
|
|
292
|
-
- pattern: "package.json"
|
|
293
|
-
level: "SOFT"
|
|
294
|
-
reason: "Dependency changes should be reviewed"
|
|
295
|
-
|
|
296
|
-
${capabilities.hasTypeScript ? ` - pattern: "tsconfig.json"
|
|
297
|
-
level: "SOFT"
|
|
298
|
-
reason: "TypeScript config affects entire project"` : ""}
|
|
299
|
-
|
|
300
|
-
# Environment Configuration
|
|
301
|
-
environments:
|
|
302
|
-
development:
|
|
303
|
-
autoApprove: true
|
|
304
|
-
qualityGates:
|
|
305
|
-
- ARCHITECTURE
|
|
306
|
-
${qualityGates.map((g) => ` - ${g}`).join("\n")}
|
|
307
|
-
|
|
308
|
-
production:
|
|
309
|
-
autoApprove: false
|
|
310
|
-
requiresManualPromotion: true
|
|
311
|
-
qualityGates:
|
|
312
|
-
- ARCHITECTURE
|
|
313
|
-
${qualityGates.map((g) => ` - ${g}`).join("\n")}
|
|
314
|
-
- ACCEPTANCE
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
# Project Architecture
|
|
318
|
-
|
|
319
|
-
## Overview
|
|
320
|
-
|
|
321
|
-
This architecture document was auto-generated by ArchonDev on ${now}.
|
|
322
|
-
|
|
323
|
-
${analyze ? `## Codebase Analysis
|
|
324
|
-
|
|
325
|
-
- **Total Files:** ${stats.totalFiles}
|
|
326
|
-
- **Total Lines:** ${formatLines(stats.totalLines)}
|
|
327
|
-
- **Primary Language:** ${capabilities.primaryLanguage}
|
|
328
|
-
|
|
329
|
-
### Language Breakdown
|
|
330
|
-
|
|
331
|
-
${Object.entries(stats.languageBreakdown).sort((a, b) => b[1].lines - a[1].lines).map(([lang, data]) => `- ${lang}: ${data.files} files, ${formatLines(data.lines)} lines`).join("\n")}
|
|
332
|
-
|
|
333
|
-
### Detected Capabilities
|
|
334
|
-
|
|
335
|
-
- TypeScript: ${capabilities.hasTypeScript ? "\u2705" : "\u274C"}
|
|
336
|
-
- ESLint: ${capabilities.hasESLint ? "\u2705" : "\u274C"}
|
|
337
|
-
- Testing: ${capabilities.hasJest ? "\u2705" : "\u274C"}
|
|
338
|
-
- Package Manager: ${capabilities.packageManager ?? "Not detected"}
|
|
339
|
-
` : ""}
|
|
340
|
-
## Components
|
|
341
|
-
|
|
342
|
-
${suggestedComponents.map((c) => `### ${c.name}
|
|
343
|
-
|
|
344
|
-
Path: \`${c.path}\`
|
|
345
|
-
|
|
346
|
-
TODO: Add description for this component.
|
|
347
|
-
`).join("\n")}
|
|
348
|
-
|
|
349
|
-
## Getting Started
|
|
350
|
-
|
|
351
|
-
1. Review and customize the components above
|
|
352
|
-
2. Add invariants for your specific rules
|
|
353
|
-
3. Define protected paths for sensitive files
|
|
354
|
-
4. Run \`archon plan <description>\` to create your first atom
|
|
355
|
-
|
|
356
|
-
## Architecture Decision Records
|
|
357
|
-
|
|
358
|
-
Document important architectural decisions here.
|
|
359
|
-
`;
|
|
360
|
-
}
|
|
361
|
-
function formatLines(n) {
|
|
362
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
363
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
364
|
-
return n.toString();
|
|
365
|
-
}
|
|
366
|
-
function printSummary(capabilities, stats, analyze) {
|
|
367
|
-
console.log();
|
|
368
|
-
console.log(chalk.green("\u2713 ArchonDev initialized successfully!"));
|
|
369
|
-
console.log();
|
|
370
|
-
console.log(chalk.bold("Detected Capabilities:"));
|
|
371
|
-
console.log(` ${capabilities.hasTypeScript ? chalk.green("\u2713") : chalk.gray("\u25CB")} TypeScript`);
|
|
372
|
-
console.log(` ${capabilities.hasESLint ? chalk.green("\u2713") : chalk.gray("\u25CB")} ESLint`);
|
|
373
|
-
console.log(` ${capabilities.hasJest ? chalk.green("\u2713") : chalk.gray("\u25CB")} Testing (Jest/Vitest)`);
|
|
374
|
-
console.log(` ${capabilities.packageManager ? chalk.green("\u2713") : chalk.gray("\u25CB")} Package Manager: ${capabilities.packageManager ?? "none"}`);
|
|
375
|
-
console.log();
|
|
376
|
-
console.log(chalk.bold("Codebase Summary:"));
|
|
377
|
-
console.log(` Files: ${stats.totalFiles}`);
|
|
378
|
-
console.log(` Lines: ${formatLines(stats.totalLines)}`);
|
|
379
|
-
console.log(` Directories: ${stats.directories.length}`);
|
|
380
|
-
console.log();
|
|
381
|
-
if (Object.keys(stats.languageBreakdown).length > 0) {
|
|
382
|
-
console.log(chalk.bold("Languages:"));
|
|
383
|
-
Object.entries(stats.languageBreakdown).sort((a, b) => b[1].lines - a[1].lines).slice(0, 5).forEach(([lang, data]) => {
|
|
384
|
-
const pct = Math.round(data.lines / stats.totalLines * 100);
|
|
385
|
-
console.log(` ${lang}: ${pct}%`);
|
|
386
|
-
});
|
|
387
|
-
console.log();
|
|
388
|
-
}
|
|
389
|
-
if (analyze && stats.directories.length > 0) {
|
|
390
|
-
console.log(chalk.bold("Suggested Boundaries:"));
|
|
391
|
-
stats.directories.filter((d) => !d.includes("/")).slice(0, 5).forEach((dir) => {
|
|
392
|
-
console.log(` \u2022 ${dir}/`);
|
|
393
|
-
});
|
|
394
|
-
console.log();
|
|
395
|
-
}
|
|
396
|
-
console.log(chalk.bold("Next Steps:"));
|
|
397
|
-
console.log(` 1. ${chalk.cyan("Review")} ARCHITECTURE.md and customize components`);
|
|
398
|
-
console.log(` 2. ${chalk.cyan("Run")} ${chalk.dim('archon plan "your first task"')} to create an atom`);
|
|
399
|
-
console.log(` 3. ${chalk.cyan("Execute")} ${chalk.dim("archon execute ATOM-001")} to implement it`);
|
|
400
|
-
console.log();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
70
|
// src/cli/promote.ts
|
|
404
|
-
import
|
|
405
|
-
import { existsSync
|
|
406
|
-
import { readFile
|
|
407
|
-
import { join
|
|
71
|
+
import chalk from "chalk";
|
|
72
|
+
import { existsSync } from "fs";
|
|
73
|
+
import { readFile, writeFile } from "fs/promises";
|
|
74
|
+
import { join } from "path";
|
|
408
75
|
var ATOMS_DIR = ".archon/atoms";
|
|
409
76
|
async function promote(atomId, options) {
|
|
410
77
|
const cwd = process.cwd();
|
|
411
78
|
const atom = await loadAtom(atomId);
|
|
412
79
|
if (!atom) {
|
|
413
|
-
console.error(
|
|
414
|
-
console.log(
|
|
80
|
+
console.error(chalk.red(`Atom ${atomId} not found.`));
|
|
81
|
+
console.log(chalk.dim(`Use "archon list" to see available atoms.`));
|
|
415
82
|
process.exit(1);
|
|
416
83
|
}
|
|
417
84
|
const envLoader = new EnvironmentConfigLoader(cwd);
|
|
418
85
|
if (!envLoader.isValidEnvironment(options.to)) {
|
|
419
|
-
console.error(
|
|
420
|
-
console.log(
|
|
86
|
+
console.error(chalk.red(`Invalid target environment: ${options.to}`));
|
|
87
|
+
console.log(chalk.dim("Valid environments: development, staging, production"));
|
|
421
88
|
process.exit(1);
|
|
422
89
|
}
|
|
423
90
|
const targetEnv = options.to;
|
|
@@ -427,185 +94,185 @@ async function promote(atomId, options) {
|
|
|
427
94
|
const currentEnv = envState?.lastExecutedEnv ?? "development";
|
|
428
95
|
const result = validator.validatePromotion(atom, targetEnv, currentEnv);
|
|
429
96
|
if (!result.success) {
|
|
430
|
-
console.error(
|
|
431
|
-
console.error(
|
|
97
|
+
console.error(chalk.red("Promotion failed:"));
|
|
98
|
+
console.error(chalk.red(result.message));
|
|
432
99
|
process.exit(1);
|
|
433
100
|
}
|
|
434
101
|
await saveAtomEnvironmentState(atomId, cwd, {
|
|
435
102
|
lastExecutedEnv: currentEnv,
|
|
436
103
|
promotedTo: [...envState?.promotedTo ?? [], targetEnv]
|
|
437
104
|
});
|
|
438
|
-
console.log(
|
|
439
|
-
console.log(
|
|
105
|
+
console.log(chalk.green(`\u2713 ${result.message}`));
|
|
106
|
+
console.log(chalk.dim(`
|
|
440
107
|
To execute in ${targetEnv}:`));
|
|
441
|
-
console.log(
|
|
108
|
+
console.log(chalk.cyan(` archon execute ${atomId} --env=${targetEnv}`));
|
|
442
109
|
}
|
|
443
110
|
async function loadAtomEnvironmentState(atomId, cwd) {
|
|
444
|
-
const stateFile =
|
|
445
|
-
if (!
|
|
111
|
+
const stateFile = join(cwd, ATOMS_DIR, `${atomId}.env.json`);
|
|
112
|
+
if (!existsSync(stateFile)) {
|
|
446
113
|
return null;
|
|
447
114
|
}
|
|
448
|
-
const content = await
|
|
115
|
+
const content = await readFile(stateFile, "utf-8");
|
|
449
116
|
return JSON.parse(content);
|
|
450
117
|
}
|
|
451
118
|
async function saveAtomEnvironmentState(atomId, cwd, state) {
|
|
452
|
-
const stateFile =
|
|
453
|
-
await
|
|
119
|
+
const stateFile = join(cwd, ATOMS_DIR, `${atomId}.env.json`);
|
|
120
|
+
await writeFile(stateFile, JSON.stringify(state, null, 2));
|
|
454
121
|
}
|
|
455
122
|
|
|
456
123
|
// src/cli/show.ts
|
|
457
|
-
import
|
|
124
|
+
import chalk2 from "chalk";
|
|
458
125
|
var STATUS_COLORS = {
|
|
459
|
-
DRAFT:
|
|
460
|
-
READY:
|
|
461
|
-
IN_PROGRESS:
|
|
462
|
-
TESTING:
|
|
463
|
-
DONE:
|
|
464
|
-
FAILED:
|
|
465
|
-
BLOCKED:
|
|
126
|
+
DRAFT: chalk2.gray,
|
|
127
|
+
READY: chalk2.blue,
|
|
128
|
+
IN_PROGRESS: chalk2.yellow,
|
|
129
|
+
TESTING: chalk2.cyan,
|
|
130
|
+
DONE: chalk2.green,
|
|
131
|
+
FAILED: chalk2.red,
|
|
132
|
+
BLOCKED: chalk2.magenta
|
|
466
133
|
};
|
|
467
134
|
async function show(atomId) {
|
|
468
135
|
const atom = await loadAtom(atomId);
|
|
469
136
|
if (!atom) {
|
|
470
|
-
console.error(
|
|
471
|
-
console.log(
|
|
137
|
+
console.error(chalk2.red(`Atom ${atomId} not found.`));
|
|
138
|
+
console.log(chalk2.dim('Use "archon list" to see available atoms.'));
|
|
472
139
|
process.exit(1);
|
|
473
140
|
}
|
|
474
|
-
const colorFn = STATUS_COLORS[atom.status] ??
|
|
141
|
+
const colorFn = STATUS_COLORS[atom.status] ?? chalk2.white;
|
|
475
142
|
console.log("");
|
|
476
|
-
console.log(
|
|
477
|
-
console.log(
|
|
143
|
+
console.log(chalk2.bold(`Atom: ${atom.externalId}`));
|
|
144
|
+
console.log(chalk2.dim("\u2550".repeat(60)));
|
|
478
145
|
console.log("");
|
|
479
|
-
console.log(
|
|
146
|
+
console.log(chalk2.bold("Title:"), atom.title);
|
|
480
147
|
if (atom.description) {
|
|
481
|
-
console.log(
|
|
148
|
+
console.log(chalk2.bold("Description:"), atom.description);
|
|
482
149
|
}
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
485
|
-
console.log(
|
|
486
|
-
console.log(
|
|
150
|
+
console.log(chalk2.bold("Status:"), colorFn(atom.status));
|
|
151
|
+
console.log(chalk2.bold("Priority:"), atom.priority);
|
|
152
|
+
console.log(chalk2.bold("Created:"), formatDateTime(atom.createdAt));
|
|
153
|
+
console.log(chalk2.bold("Updated:"), formatDateTime(atom.updatedAt));
|
|
487
154
|
if (atom.goals && atom.goals.length > 0) {
|
|
488
155
|
console.log("");
|
|
489
|
-
console.log(
|
|
156
|
+
console.log(chalk2.bold("Goals:"));
|
|
490
157
|
for (const goal of atom.goals) {
|
|
491
158
|
console.log(` \u2022 ${goal}`);
|
|
492
159
|
}
|
|
493
160
|
}
|
|
494
161
|
if (atom.acceptanceCriteria && atom.acceptanceCriteria.length > 0) {
|
|
495
162
|
console.log("");
|
|
496
|
-
console.log(
|
|
163
|
+
console.log(chalk2.bold("Acceptance Criteria:"));
|
|
497
164
|
for (let i = 0; i < atom.acceptanceCriteria.length; i++) {
|
|
498
165
|
console.log(` ${i + 1}. ${atom.acceptanceCriteria[i]}`);
|
|
499
166
|
}
|
|
500
167
|
}
|
|
501
168
|
if (atom.tags && atom.tags.length > 0) {
|
|
502
169
|
console.log("");
|
|
503
|
-
console.log(
|
|
170
|
+
console.log(chalk2.bold("Tags:"), atom.tags.join(", "));
|
|
504
171
|
}
|
|
505
172
|
if (atom.plan) {
|
|
506
173
|
console.log("");
|
|
507
|
-
console.log(
|
|
508
|
-
console.log(
|
|
509
|
-
console.log(
|
|
174
|
+
console.log(chalk2.bold("Implementation Plan:"));
|
|
175
|
+
console.log(chalk2.dim("\u2500".repeat(40)));
|
|
176
|
+
console.log(chalk2.bold("Steps:"));
|
|
510
177
|
for (let i = 0; i < atom.plan.steps.length; i++) {
|
|
511
178
|
console.log(` ${i + 1}. ${atom.plan.steps[i]}`);
|
|
512
179
|
}
|
|
513
180
|
if (atom.plan.files_to_modify.length > 0) {
|
|
514
181
|
console.log("");
|
|
515
|
-
console.log(
|
|
182
|
+
console.log(chalk2.bold("Files to modify:"));
|
|
516
183
|
for (const file of atom.plan.files_to_modify) {
|
|
517
184
|
console.log(` \u2022 ${file}`);
|
|
518
185
|
}
|
|
519
186
|
}
|
|
520
187
|
if (atom.plan.dependencies.length > 0) {
|
|
521
188
|
console.log("");
|
|
522
|
-
console.log(
|
|
189
|
+
console.log(chalk2.bold("Dependencies:"));
|
|
523
190
|
for (const dep of atom.plan.dependencies) {
|
|
524
191
|
console.log(` \u2022 ${dep}`);
|
|
525
192
|
}
|
|
526
193
|
}
|
|
527
194
|
if (atom.plan.risks.length > 0) {
|
|
528
195
|
console.log("");
|
|
529
|
-
console.log(
|
|
196
|
+
console.log(chalk2.bold("Risks:"));
|
|
530
197
|
for (const risk of atom.plan.risks) {
|
|
531
|
-
console.log(
|
|
198
|
+
console.log(chalk2.yellow(` \u26A0 ${risk}`));
|
|
532
199
|
}
|
|
533
200
|
}
|
|
534
201
|
console.log("");
|
|
535
|
-
console.log(
|
|
202
|
+
console.log(chalk2.bold("Estimated Complexity:"), atom.plan.estimated_complexity);
|
|
536
203
|
}
|
|
537
204
|
if (atom.status === "FAILED" && atom.errorMessage) {
|
|
538
205
|
console.log("");
|
|
539
|
-
console.log(
|
|
540
|
-
console.log(
|
|
206
|
+
console.log(chalk2.bold("Error:"));
|
|
207
|
+
console.log(chalk2.red(` ${atom.errorMessage}`));
|
|
541
208
|
}
|
|
542
209
|
if (atom.retryCount > 0) {
|
|
543
210
|
console.log("");
|
|
544
|
-
console.log(
|
|
211
|
+
console.log(chalk2.bold("Retry Count:"), chalk2.yellow(String(atom.retryCount)));
|
|
545
212
|
}
|
|
546
213
|
if (atom.ownershipPaths && atom.ownershipPaths.length > 0) {
|
|
547
214
|
console.log("");
|
|
548
|
-
console.log(
|
|
215
|
+
console.log(chalk2.bold("Ownership Paths:"));
|
|
549
216
|
for (const path2 of atom.ownershipPaths) {
|
|
550
217
|
console.log(` \u2022 ${path2}`);
|
|
551
218
|
}
|
|
552
219
|
}
|
|
553
220
|
if (atom.diffContract) {
|
|
554
221
|
console.log("");
|
|
555
|
-
console.log(
|
|
222
|
+
console.log(chalk2.bold("Diff Contract:"));
|
|
556
223
|
if (atom.diffContract.allowed_paths.length > 0) {
|
|
557
|
-
console.log(
|
|
224
|
+
console.log(chalk2.dim(" Allowed paths:"));
|
|
558
225
|
for (const path2 of atom.diffContract.allowed_paths) {
|
|
559
226
|
console.log(` \u2713 ${path2}`);
|
|
560
227
|
}
|
|
561
228
|
}
|
|
562
229
|
if (atom.diffContract.forbidden_paths.length > 0) {
|
|
563
|
-
console.log(
|
|
230
|
+
console.log(chalk2.dim(" Forbidden paths:"));
|
|
564
231
|
for (const path2 of atom.diffContract.forbidden_paths) {
|
|
565
|
-
console.log(
|
|
232
|
+
console.log(chalk2.red(` \u2717 ${path2}`));
|
|
566
233
|
}
|
|
567
234
|
}
|
|
568
235
|
}
|
|
569
236
|
if (atom.context && Object.keys(atom.context).length > 0) {
|
|
570
237
|
console.log("");
|
|
571
|
-
console.log(
|
|
238
|
+
console.log(chalk2.bold("Context:"));
|
|
572
239
|
if (atom.context.relevantFiles && atom.context.relevantFiles.length > 0) {
|
|
573
|
-
console.log(
|
|
240
|
+
console.log(chalk2.dim(" Relevant files:"));
|
|
574
241
|
for (const file of atom.context.relevantFiles) {
|
|
575
242
|
console.log(` \u2022 ${file}`);
|
|
576
243
|
}
|
|
577
244
|
}
|
|
578
245
|
if (atom.context.recentLearnings && atom.context.recentLearnings.length > 0) {
|
|
579
|
-
console.log(
|
|
246
|
+
console.log(chalk2.dim(" Recent learnings:"));
|
|
580
247
|
for (const learning of atom.context.recentLearnings) {
|
|
581
248
|
console.log(` \u2022 ${learning}`);
|
|
582
249
|
}
|
|
583
250
|
}
|
|
584
251
|
}
|
|
585
252
|
console.log("");
|
|
586
|
-
console.log(
|
|
587
|
-
console.log(
|
|
253
|
+
console.log(chalk2.dim("\u2500".repeat(60)));
|
|
254
|
+
console.log(chalk2.bold("Next Steps:"));
|
|
588
255
|
switch (atom.status) {
|
|
589
256
|
case "DRAFT":
|
|
590
|
-
console.log(
|
|
257
|
+
console.log(chalk2.dim(` Run "archon plan ${atom.externalId} --continue" to finalize the plan`));
|
|
591
258
|
break;
|
|
592
259
|
case "READY":
|
|
593
|
-
console.log(
|
|
260
|
+
console.log(chalk2.dim(` Run "archon execute ${atom.externalId}" to implement`));
|
|
594
261
|
break;
|
|
595
262
|
case "IN_PROGRESS":
|
|
596
|
-
console.log(
|
|
263
|
+
console.log(chalk2.dim(" Execution is in progress..."));
|
|
597
264
|
break;
|
|
598
265
|
case "TESTING":
|
|
599
|
-
console.log(
|
|
266
|
+
console.log(chalk2.dim(" Waiting for quality gates to complete..."));
|
|
600
267
|
break;
|
|
601
268
|
case "DONE":
|
|
602
|
-
console.log(
|
|
269
|
+
console.log(chalk2.green(" \u2713 Atom completed successfully!"));
|
|
603
270
|
break;
|
|
604
271
|
case "FAILED":
|
|
605
|
-
console.log(
|
|
272
|
+
console.log(chalk2.dim(` Review the error and run "archon execute ${atom.externalId}" to retry`));
|
|
606
273
|
break;
|
|
607
274
|
case "BLOCKED":
|
|
608
|
-
console.log(
|
|
275
|
+
console.log(chalk2.dim(" This atom is blocked. Manual intervention required."));
|
|
609
276
|
break;
|
|
610
277
|
}
|
|
611
278
|
console.log("");
|
|
@@ -616,47 +283,34 @@ function formatDateTime(date) {
|
|
|
616
283
|
}
|
|
617
284
|
|
|
618
285
|
// src/cli/start.ts
|
|
619
|
-
import
|
|
620
|
-
import
|
|
621
|
-
import { existsSync as
|
|
622
|
-
import { join as
|
|
286
|
+
import chalk3 from "chalk";
|
|
287
|
+
import readline from "readline";
|
|
288
|
+
import { existsSync as existsSync2, readFileSync, readdirSync, appendFileSync } from "fs";
|
|
289
|
+
import { join as join2 } from "path";
|
|
623
290
|
async function start() {
|
|
624
291
|
const cwd = process.cwd();
|
|
625
|
-
console.log(
|
|
626
|
-
console.log(
|
|
627
|
-
console.log(
|
|
292
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
293
|
+
console.log(chalk3.bold.white(" ArchonDev - AI-Powered Development Governance"));
|
|
294
|
+
console.log(chalk3.blue("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
|
|
628
295
|
const projectState = detectProjectState(cwd);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
];
|
|
640
|
-
console.log(chalk4.bold("What would you like to do?\n"));
|
|
641
|
-
for (const choice2 of choices) {
|
|
642
|
-
console.log(` ${chalk4.cyan(choice2.key)}) ${choice2.label}`);
|
|
643
|
-
}
|
|
644
|
-
console.log();
|
|
645
|
-
const selected = await prompt("Enter choice");
|
|
646
|
-
const choice = choices.find((c) => c.key === selected.toLowerCase());
|
|
647
|
-
if (choice) {
|
|
648
|
-
await choice.action();
|
|
649
|
-
} else {
|
|
650
|
-
console.log(chalk4.yellow("Invalid choice. Please try again."));
|
|
651
|
-
await start();
|
|
296
|
+
switch (projectState.scenario) {
|
|
297
|
+
case "NEW_PROJECT":
|
|
298
|
+
await handleNewProject(cwd, projectState);
|
|
299
|
+
break;
|
|
300
|
+
case "ADAPT_EXISTING":
|
|
301
|
+
await handleAdaptExisting(cwd, projectState);
|
|
302
|
+
break;
|
|
303
|
+
case "CONTINUE_SESSION":
|
|
304
|
+
await handleContinueSession(cwd, projectState);
|
|
305
|
+
break;
|
|
652
306
|
}
|
|
653
307
|
}
|
|
654
308
|
function detectProjectState(cwd) {
|
|
655
|
-
const sourceDirs = ["src", "lib", "app", "packages", "components"];
|
|
656
|
-
const sourceExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"];
|
|
309
|
+
const sourceDirs = ["src", "lib", "app", "packages", "components", "pages", "api"];
|
|
310
|
+
const sourceExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".rb", ".php"];
|
|
657
311
|
let hasSourceFiles = false;
|
|
658
312
|
for (const dir of sourceDirs) {
|
|
659
|
-
if (
|
|
313
|
+
if (existsSync2(join2(cwd, dir))) {
|
|
660
314
|
hasSourceFiles = true;
|
|
661
315
|
break;
|
|
662
316
|
}
|
|
@@ -670,29 +324,351 @@ function detectProjectState(cwd) {
|
|
|
670
324
|
} catch {
|
|
671
325
|
}
|
|
672
326
|
}
|
|
327
|
+
const projectMarkers = ["package.json", "Cargo.toml", "pyproject.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
328
|
+
if (!hasSourceFiles) {
|
|
329
|
+
hasSourceFiles = projectMarkers.some((marker) => existsSync2(join2(cwd, marker)));
|
|
330
|
+
}
|
|
331
|
+
const hasArchitecture = existsSync2(join2(cwd, "ARCHITECTURE.md"));
|
|
332
|
+
const hasProgress = existsSync2(join2(cwd, "progress.txt"));
|
|
333
|
+
const hasReviewDb = existsSync2(join2(cwd, "docs", "code-review", "review-tasks.db"));
|
|
334
|
+
let hasProgressEntries = false;
|
|
335
|
+
let lastProgressEntry;
|
|
336
|
+
if (hasProgress) {
|
|
337
|
+
try {
|
|
338
|
+
const progressContent = readFileSync(join2(cwd, "progress.txt"), "utf-8");
|
|
339
|
+
const entries = progressContent.match(/^## \d{4}-\d{2}-\d{2}/gm);
|
|
340
|
+
hasProgressEntries = entries !== null && entries.length > 0;
|
|
341
|
+
if (hasProgressEntries) {
|
|
342
|
+
const lastMatch = progressContent.match(/## \d{4}-\d{2}-\d{2}[^\n]*\n([^#]*)/g);
|
|
343
|
+
if (lastMatch && lastMatch.length > 0) {
|
|
344
|
+
const last = lastMatch[lastMatch.length - 1] ?? "";
|
|
345
|
+
lastProgressEntry = last.split("\n").slice(0, 3).join("\n").trim();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let scenario;
|
|
352
|
+
if (hasProgressEntries) {
|
|
353
|
+
scenario = "CONTINUE_SESSION";
|
|
354
|
+
} else if (hasSourceFiles) {
|
|
355
|
+
scenario = "ADAPT_EXISTING";
|
|
356
|
+
} else {
|
|
357
|
+
scenario = "NEW_PROJECT";
|
|
358
|
+
}
|
|
673
359
|
return {
|
|
360
|
+
scenario,
|
|
674
361
|
hasSourceFiles,
|
|
675
|
-
hasArchitecture
|
|
676
|
-
|
|
362
|
+
hasArchitecture,
|
|
363
|
+
hasProgress,
|
|
364
|
+
hasProgressEntries,
|
|
365
|
+
hasReviewDb,
|
|
366
|
+
lastProgressEntry
|
|
677
367
|
};
|
|
678
368
|
}
|
|
679
|
-
async function
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
369
|
+
async function handleNewProject(cwd, state) {
|
|
370
|
+
console.log(chalk3.yellow("\u{1F389}") + chalk3.bold(" Starting a new project? Great!\n"));
|
|
371
|
+
console.log(chalk3.dim("I'll ask you a few quick questions to set things up right."));
|
|
372
|
+
console.log(chalk3.dim("Answer as much or as little as you want \u2014 you can always refine later.\n"));
|
|
373
|
+
console.log(chalk3.bold("What would you like to do?\n"));
|
|
374
|
+
console.log(` ${chalk3.cyan("1")}) ${chalk3.bold("Start interview")} \u2014 I'll ask questions to understand your project`);
|
|
375
|
+
console.log(` ${chalk3.cyan("2")}) ${chalk3.bold("Quick start")} \u2014 Just create basic governance files`);
|
|
376
|
+
console.log(` ${chalk3.cyan("3")}) ${chalk3.bold("Import from template")} \u2014 Use a predefined project template`);
|
|
377
|
+
console.log(` ${chalk3.cyan("q")}) ${chalk3.dim("Quit")}`);
|
|
378
|
+
console.log();
|
|
379
|
+
const choice = await prompt("Enter choice");
|
|
380
|
+
switch (choice.toLowerCase()) {
|
|
381
|
+
case "1":
|
|
382
|
+
await runNewProjectInterview(cwd);
|
|
383
|
+
break;
|
|
384
|
+
case "2":
|
|
385
|
+
await quickStart(cwd);
|
|
386
|
+
break;
|
|
387
|
+
case "3":
|
|
388
|
+
console.log(chalk3.yellow("\nTemplates coming soon! Using quick start for now.\n"));
|
|
389
|
+
await quickStart(cwd);
|
|
390
|
+
break;
|
|
391
|
+
case "q":
|
|
392
|
+
process.exit(0);
|
|
393
|
+
default:
|
|
394
|
+
console.log(chalk3.yellow("Invalid choice. Please try again."));
|
|
395
|
+
await handleNewProject(cwd, state);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async function runNewProjectInterview(cwd) {
|
|
399
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Project Interview \u2501\u2501\u2501\n"));
|
|
400
|
+
console.log(chalk3.bold("Phase 1: The Vision\n"));
|
|
401
|
+
const projectName = await prompt("What's the project name?");
|
|
402
|
+
const projectDescription = await prompt("In one sentence, what does this project do?");
|
|
403
|
+
const audience = await promptChoice("Who is it for?", [
|
|
404
|
+
{ key: "1", label: "Just me (personal project)" },
|
|
405
|
+
{ key: "2", label: "My team (internal tool)" },
|
|
406
|
+
{ key: "3", label: "End users (product)" }
|
|
407
|
+
]);
|
|
408
|
+
const experience = await promptChoice("Your experience level with this tech stack?", [
|
|
409
|
+
{ key: "1", label: "\u{1F7E2} Expert \u2014 I know this well" },
|
|
410
|
+
{ key: "2", label: "\u{1F7E1} Intermediate \u2014 I've done similar work" },
|
|
411
|
+
{ key: "3", label: "\u{1F534} Learning \u2014 This is new to me" }
|
|
412
|
+
]);
|
|
413
|
+
console.log(chalk3.bold("\nPhase 2: Tech Stack\n"));
|
|
414
|
+
const language = await promptChoice("Primary language/framework?", [
|
|
415
|
+
{ key: "1", label: "TypeScript / JavaScript" },
|
|
416
|
+
{ key: "2", label: "Python" },
|
|
417
|
+
{ key: "3", label: "Go" },
|
|
418
|
+
{ key: "4", label: "Rust" },
|
|
419
|
+
{ key: "5", label: "Other" }
|
|
420
|
+
]);
|
|
421
|
+
const projectType = await promptChoice("Project type?", [
|
|
422
|
+
{ key: "1", label: "Frontend only (web UI)" },
|
|
423
|
+
{ key: "2", label: "Backend only (API, CLI, service)" },
|
|
424
|
+
{ key: "3", label: "Full-stack (both)" },
|
|
425
|
+
{ key: "4", label: "Library/package" }
|
|
426
|
+
]);
|
|
427
|
+
console.log(chalk3.bold("\nPhase 3: Preferences ") + chalk3.dim("(press Enter to skip)\n"));
|
|
428
|
+
const protectedFiles = await prompt("Any files AI should NEVER modify without asking? (comma-separated)");
|
|
429
|
+
const noNoPatterns = await prompt('Anything AI should NEVER do? (e.g., "no console.log")');
|
|
430
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Generating Project Files \u2501\u2501\u2501\n"));
|
|
431
|
+
const { init: init2 } = await import("./init-YLAHY4CV.js");
|
|
432
|
+
await init2({ analyze: false, git: true });
|
|
433
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
434
|
+
const progressEntry = `
|
|
435
|
+
## ${today} - Project Initialized via Interview
|
|
436
|
+
|
|
437
|
+
### Vision
|
|
438
|
+
- **Name:** ${projectName || "Unnamed Project"}
|
|
439
|
+
- **Description:** ${projectDescription || "No description provided"}
|
|
440
|
+
- **Audience:** ${["Personal", "Team", "End Users"][parseInt(audience) - 1] || "Not specified"}
|
|
441
|
+
- **Experience Level:** ${["Expert", "Intermediate", "Learning"][parseInt(experience) - 1] || "Not specified"}
|
|
442
|
+
|
|
443
|
+
### Stack
|
|
444
|
+
- **Language:** ${["TypeScript/JavaScript", "Python", "Go", "Rust", "Other"][parseInt(language) - 1] || "Not specified"}
|
|
445
|
+
- **Type:** ${["Frontend", "Backend", "Full-stack", "Library"][parseInt(projectType) - 1] || "Not specified"}
|
|
446
|
+
|
|
447
|
+
### Preferences
|
|
448
|
+
${protectedFiles ? `- **Protected files:** ${protectedFiles}` : "- No protected files specified"}
|
|
449
|
+
${noNoPatterns ? `- **Forbidden patterns:** ${noNoPatterns}` : "- No forbidden patterns specified"}
|
|
450
|
+
|
|
451
|
+
### Files Created
|
|
452
|
+
- ARCHITECTURE.md
|
|
453
|
+
- .archon/config.yaml
|
|
454
|
+
- progress.txt
|
|
455
|
+
`;
|
|
456
|
+
const progressPath = join2(cwd, "progress.txt");
|
|
457
|
+
if (!existsSync2(progressPath)) {
|
|
458
|
+
const { writeFileSync } = await import("fs");
|
|
459
|
+
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
460
|
+
}
|
|
461
|
+
appendFileSync(progressPath, progressEntry);
|
|
462
|
+
console.log(chalk3.green("\n\u2713 Project initialized!\n"));
|
|
463
|
+
console.log(chalk3.bold("Next steps:"));
|
|
464
|
+
console.log(` 1. ${chalk3.cyan("Review")} ARCHITECTURE.md and customize if needed`);
|
|
465
|
+
console.log(` 2. ${chalk3.cyan("Run")} ${chalk3.dim('archon plan "your first task"')} to create an atom`);
|
|
466
|
+
console.log();
|
|
467
|
+
const continueChoice = await promptYesNo("Would you like to plan your first task now?", true);
|
|
468
|
+
if (continueChoice) {
|
|
469
|
+
const description = await prompt("Describe what you want to build first");
|
|
470
|
+
if (description.trim()) {
|
|
471
|
+
const { plan: plan2 } = await import("./plan-WQUFH2WB.js");
|
|
472
|
+
await plan2(description, {});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async function quickStart(cwd) {
|
|
477
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Quick Start \u2501\u2501\u2501\n"));
|
|
478
|
+
const { init: init2 } = await import("./init-YLAHY4CV.js");
|
|
479
|
+
await init2({ analyze: false, git: true });
|
|
480
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
481
|
+
const progressPath = join2(cwd, "progress.txt");
|
|
482
|
+
if (!existsSync2(progressPath)) {
|
|
483
|
+
const { writeFileSync } = await import("fs");
|
|
484
|
+
writeFileSync(progressPath, `# ArchonDev Progress Log
|
|
485
|
+
|
|
486
|
+
This file tracks learnings and decisions across sessions.
|
|
487
|
+
|
|
488
|
+
## ${today} - Project Initialized (Quick Start)
|
|
489
|
+
|
|
490
|
+
### What was done
|
|
491
|
+
- Created ARCHITECTURE.md with default template
|
|
492
|
+
- Initialized .archon directory
|
|
493
|
+
- Created this progress.txt
|
|
494
|
+
|
|
495
|
+
### Next Steps
|
|
496
|
+
1. Customize ARCHITECTURE.md for your project
|
|
497
|
+
2. Run \`archon plan "your first task"\` to create an atom
|
|
498
|
+
`);
|
|
499
|
+
}
|
|
500
|
+
console.log();
|
|
501
|
+
await showMainMenu();
|
|
502
|
+
}
|
|
503
|
+
async function handleAdaptExisting(cwd, state) {
|
|
504
|
+
console.log(chalk3.yellow("\u{1F4C1}") + chalk3.bold(" Existing project detected!\n"));
|
|
505
|
+
console.log(chalk3.dim("I can analyze your codebase and adapt the governance files to match your structure."));
|
|
506
|
+
console.log(chalk3.dim("This helps me understand your architecture without changing any code.\n"));
|
|
507
|
+
console.log(chalk3.bold("What would you like to do?\n"));
|
|
508
|
+
console.log(` ${chalk3.cyan("1")}) ${chalk3.bold("Analyze and adapt")} \u2014 I'll scan your project and update ARCHITECTURE.md`);
|
|
509
|
+
console.log(` ${chalk3.cyan("2")}) ${chalk3.bold("Code review first")} \u2014 Review code for issues before setting up governance`);
|
|
510
|
+
console.log(` ${chalk3.cyan("3")}) ${chalk3.bold("Manual setup")} \u2014 Keep template files, customize manually`);
|
|
511
|
+
console.log(` ${chalk3.cyan("4")}) ${chalk3.bold("Just start working")} \u2014 Skip setup, use defaults`);
|
|
512
|
+
console.log(` ${chalk3.cyan("q")}) ${chalk3.dim("Quit")}`);
|
|
513
|
+
console.log();
|
|
514
|
+
const choice = await prompt("Enter choice");
|
|
515
|
+
switch (choice.toLowerCase()) {
|
|
516
|
+
case "1":
|
|
517
|
+
await analyzeAndAdapt(cwd);
|
|
518
|
+
break;
|
|
519
|
+
case "2":
|
|
520
|
+
await codeReviewFirst(cwd);
|
|
521
|
+
break;
|
|
522
|
+
case "3":
|
|
523
|
+
await manualSetup(cwd);
|
|
524
|
+
break;
|
|
525
|
+
case "4":
|
|
526
|
+
await quickAdapt(cwd);
|
|
527
|
+
break;
|
|
528
|
+
case "q":
|
|
529
|
+
process.exit(0);
|
|
530
|
+
default:
|
|
531
|
+
console.log(chalk3.yellow("Invalid choice. Please try again."));
|
|
532
|
+
await handleAdaptExisting(cwd, state);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function analyzeAndAdapt(cwd) {
|
|
536
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Analyzing Project \u2501\u2501\u2501\n"));
|
|
537
|
+
const { init: init2 } = await import("./init-YLAHY4CV.js");
|
|
538
|
+
await init2({ analyze: true, git: true });
|
|
539
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
540
|
+
const progressPath = join2(cwd, "progress.txt");
|
|
541
|
+
if (!existsSync2(progressPath)) {
|
|
542
|
+
const { writeFileSync } = await import("fs");
|
|
543
|
+
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
544
|
+
}
|
|
545
|
+
appendFileSync(progressPath, `
|
|
546
|
+
## ${today} - ArchonDev Adapted to Existing Project
|
|
547
|
+
|
|
548
|
+
### What was done
|
|
549
|
+
- Analyzed existing codebase structure
|
|
550
|
+
- Generated ARCHITECTURE.md based on detected components
|
|
551
|
+
- Created .archon configuration
|
|
552
|
+
|
|
553
|
+
### Governance Files Created
|
|
554
|
+
- ARCHITECTURE.md - Customized to match project structure
|
|
555
|
+
- .archon/config.yaml - Build commands configured
|
|
556
|
+
- progress.txt - This file
|
|
557
|
+
`);
|
|
558
|
+
console.log(chalk3.green("\n\u2713 Governance files adapted!\n"));
|
|
559
|
+
await showMainMenu();
|
|
560
|
+
}
|
|
561
|
+
async function codeReviewFirst(cwd) {
|
|
562
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Code Review Mode \u2501\u2501\u2501\n"));
|
|
563
|
+
console.log(chalk3.dim("I'll analyze your code for issues without making any changes.\n"));
|
|
564
|
+
const { reviewInit: reviewInit2, reviewAnalyze: reviewAnalyze2, reviewRun: reviewRun2 } = await import("./review-3R6QXAXQ.js");
|
|
565
|
+
const reviewDbPath = join2(cwd, "docs", "code-review", "review-tasks.db");
|
|
566
|
+
if (!existsSync2(reviewDbPath)) {
|
|
567
|
+
await reviewInit2();
|
|
568
|
+
}
|
|
569
|
+
await reviewAnalyze2();
|
|
570
|
+
const runReview = await promptYesNo("Would you like to run the AI-powered review now?", true);
|
|
571
|
+
if (runReview) {
|
|
572
|
+
await reviewRun2({ all: true });
|
|
573
|
+
}
|
|
574
|
+
console.log(chalk3.dim("\nAfter reviewing, you can run ") + chalk3.cyan("archon") + chalk3.dim(" again to set up governance.\n"));
|
|
575
|
+
}
|
|
576
|
+
async function manualSetup(cwd) {
|
|
577
|
+
console.log(chalk3.blue("\n\u2501\u2501\u2501 Manual Setup \u2501\u2501\u2501\n"));
|
|
578
|
+
console.log(chalk3.dim("Creating template files. You can customize them manually.\n"));
|
|
579
|
+
const { init: init2 } = await import("./init-YLAHY4CV.js");
|
|
580
|
+
await init2({ analyze: false, git: true });
|
|
581
|
+
console.log(chalk3.bold("\nWhat to customize:\n"));
|
|
582
|
+
console.log(` ${chalk3.cyan("1. ARCHITECTURE.md")} \u2014 Update components to match your folders`);
|
|
583
|
+
console.log(` ${chalk3.cyan("2. .archon/config.yaml")} \u2014 Change build/test/lint commands`);
|
|
584
|
+
console.log(` ${chalk3.cyan("3. progress.txt")} \u2014 Add project-specific patterns`);
|
|
585
|
+
console.log();
|
|
586
|
+
await showMainMenu();
|
|
587
|
+
}
|
|
588
|
+
async function quickAdapt(cwd) {
|
|
589
|
+
console.log(chalk3.blue("\n\u26A1 Using defaults \u2014 let's go!\n"));
|
|
590
|
+
const { init: init2 } = await import("./init-YLAHY4CV.js");
|
|
591
|
+
await init2({ analyze: true, git: true });
|
|
592
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
593
|
+
const progressPath = join2(cwd, "progress.txt");
|
|
594
|
+
if (!existsSync2(progressPath)) {
|
|
595
|
+
const { writeFileSync } = await import("fs");
|
|
596
|
+
writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
|
|
597
|
+
}
|
|
598
|
+
appendFileSync(progressPath, `
|
|
599
|
+
## ${today} - Quick Adapt (Defaults)
|
|
600
|
+
|
|
601
|
+
### What was done
|
|
602
|
+
- Used default governance rules
|
|
603
|
+
- You can customize later by editing ARCHITECTURE.md
|
|
604
|
+
`);
|
|
605
|
+
await showMainMenu();
|
|
606
|
+
}
|
|
607
|
+
async function handleContinueSession(cwd, state) {
|
|
608
|
+
console.log(chalk3.green("\u{1F44B}") + chalk3.bold(" Welcome back!\n"));
|
|
609
|
+
if (state.lastProgressEntry) {
|
|
610
|
+
console.log(chalk3.dim("Last activity:"));
|
|
611
|
+
console.log(chalk3.dim(" " + state.lastProgressEntry.split("\n")[0]));
|
|
683
612
|
console.log();
|
|
684
613
|
}
|
|
685
|
-
|
|
686
|
-
|
|
614
|
+
const handoff = checkForHandoff(cwd);
|
|
615
|
+
if (handoff) {
|
|
616
|
+
console.log(chalk3.yellow("\u{1F4CB} Found handoff from last session:\n"));
|
|
617
|
+
console.log(chalk3.dim(handoff.nextSteps));
|
|
618
|
+
console.log();
|
|
619
|
+
const continueHandoff = await promptYesNo("Continue from handoff?", true);
|
|
620
|
+
if (continueHandoff) {
|
|
621
|
+
console.log(chalk3.dim("\nPicking up where you left off...\n"));
|
|
622
|
+
}
|
|
687
623
|
}
|
|
688
624
|
if (state.hasReviewDb) {
|
|
689
625
|
await showReviewProgress(cwd);
|
|
690
626
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
627
|
+
await showMainMenu();
|
|
628
|
+
}
|
|
629
|
+
function checkForHandoff(cwd) {
|
|
630
|
+
try {
|
|
631
|
+
const progressPath = join2(cwd, "progress.txt");
|
|
632
|
+
if (!existsSync2(progressPath)) return null;
|
|
633
|
+
const content = readFileSync(progressPath, "utf-8");
|
|
634
|
+
const handoffMatch = content.match(/## Context Handoff[^\n]*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
635
|
+
if (handoffMatch && handoffMatch[1]) {
|
|
636
|
+
const handoffContent = handoffMatch[1];
|
|
637
|
+
const nextStepsMatch = handoffContent.match(/### Next Steps[^\n]*\n([\s\S]*?)(?=\n### |$)/);
|
|
638
|
+
if (nextStepsMatch && nextStepsMatch[1]) {
|
|
639
|
+
return { nextSteps: nextStepsMatch[1].trim() };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
async function showMainMenu() {
|
|
647
|
+
const cwd = process.cwd();
|
|
648
|
+
const state = detectProjectState(cwd);
|
|
649
|
+
console.log(chalk3.bold("What would you like to do?\n"));
|
|
650
|
+
const choices = [
|
|
651
|
+
{ key: "1", label: "Plan a new task", action: () => planTask() },
|
|
652
|
+
{ key: "2", label: "List atoms", action: () => listAtoms() },
|
|
653
|
+
{ key: "3", label: "Execute next atom", action: () => executeNext() },
|
|
654
|
+
{ key: "4", label: "Report a bug", action: () => reportBug() },
|
|
655
|
+
{ key: "5", label: "View status", action: () => viewStatus() },
|
|
656
|
+
{ key: "6", label: "Code Review", action: () => reviewCode() },
|
|
657
|
+
{ key: "7", label: "Settings & Preferences", action: () => settingsMenu() },
|
|
658
|
+
{ key: "q", label: "Quit", action: async () => process.exit(0) }
|
|
659
|
+
];
|
|
660
|
+
for (const choice2 of choices) {
|
|
661
|
+
console.log(` ${chalk3.cyan(choice2.key)}) ${choice2.label}`);
|
|
694
662
|
}
|
|
695
663
|
console.log();
|
|
664
|
+
const selected = await prompt("Enter choice");
|
|
665
|
+
const choice = choices.find((c) => c.key === selected.toLowerCase());
|
|
666
|
+
if (choice) {
|
|
667
|
+
await choice.action();
|
|
668
|
+
} else {
|
|
669
|
+
console.log(chalk3.yellow("Invalid choice. Please try again."));
|
|
670
|
+
await showMainMenu();
|
|
671
|
+
}
|
|
696
672
|
}
|
|
697
673
|
async function showReviewProgress(cwd) {
|
|
698
674
|
try {
|
|
@@ -706,29 +682,30 @@ async function showReviewProgress(cwd) {
|
|
|
706
682
|
const pending = stats.pending + stats.inReview;
|
|
707
683
|
const needsFix = stats.needsFix;
|
|
708
684
|
console.log(
|
|
709
|
-
|
|
685
|
+
chalk3.blue("\u{1F4CA} Review Progress:") + chalk3.dim(` ${completed}/${total} completed`) + (needsFix > 0 ? chalk3.red(` (${needsFix} need fixes)`) : "") + (pending > 0 ? chalk3.yellow(` (${pending} pending)`) : "")
|
|
710
686
|
);
|
|
687
|
+
console.log();
|
|
711
688
|
} catch {
|
|
712
689
|
}
|
|
713
690
|
}
|
|
714
691
|
async function planTask() {
|
|
715
|
-
const { plan: plan2 } = await import("./plan-
|
|
692
|
+
const { plan: plan2 } = await import("./plan-WQUFH2WB.js");
|
|
716
693
|
const description = await prompt("Describe what you want to build");
|
|
717
694
|
if (description.trim()) {
|
|
718
695
|
await plan2(description, {});
|
|
719
696
|
}
|
|
720
697
|
}
|
|
721
698
|
async function listAtoms() {
|
|
722
|
-
const { list: list2 } = await import("./list-
|
|
699
|
+
const { list: list2 } = await import("./list-UB4MOGRH.js");
|
|
723
700
|
await list2({});
|
|
724
701
|
}
|
|
725
702
|
async function executeNext() {
|
|
726
703
|
const atomId = await prompt("Enter atom ID to execute (or press Enter for next planned)");
|
|
727
704
|
if (atomId.trim()) {
|
|
728
|
-
const { execute: execute2 } = await import("./execute-
|
|
705
|
+
const { execute: execute2 } = await import("./execute-6ZGARWT2.js");
|
|
729
706
|
await execute2(atomId.trim(), {});
|
|
730
707
|
} else {
|
|
731
|
-
console.log(
|
|
708
|
+
console.log(chalk3.yellow('No atom ID provided. Use "archon list" to see available atoms.'));
|
|
732
709
|
}
|
|
733
710
|
}
|
|
734
711
|
async function reportBug() {
|
|
@@ -743,25 +720,26 @@ async function viewStatus() {
|
|
|
743
720
|
await status2();
|
|
744
721
|
}
|
|
745
722
|
async function settingsMenu() {
|
|
746
|
-
const { interactiveSettings } = await import("./preferences-
|
|
723
|
+
const { interactiveSettings } = await import("./preferences-QEFXVCZN.js");
|
|
747
724
|
await interactiveSettings();
|
|
748
|
-
await
|
|
725
|
+
await showMainMenu();
|
|
749
726
|
}
|
|
750
727
|
async function reviewCode() {
|
|
751
728
|
const cwd = process.cwd();
|
|
752
|
-
const reviewDbPath =
|
|
753
|
-
if (!
|
|
754
|
-
console.log(
|
|
729
|
+
const reviewDbPath = join2(cwd, "docs", "code-review", "review-tasks.db");
|
|
730
|
+
if (!existsSync2(reviewDbPath)) {
|
|
731
|
+
console.log(chalk3.dim("Code review not initialized. Starting setup...\n"));
|
|
755
732
|
const { reviewInit: reviewInit2 } = await import("./review-3R6QXAXQ.js");
|
|
756
733
|
await reviewInit2();
|
|
757
734
|
console.log();
|
|
758
735
|
}
|
|
759
|
-
console.log(
|
|
760
|
-
console.log(` ${
|
|
761
|
-
console.log(` ${
|
|
762
|
-
console.log(` ${
|
|
763
|
-
console.log(` ${
|
|
764
|
-
console.log(` ${
|
|
736
|
+
console.log(chalk3.bold("\nCode Review Options:\n"));
|
|
737
|
+
console.log(` ${chalk3.cyan("1")}) Analyze project`);
|
|
738
|
+
console.log(` ${chalk3.cyan("2")}) Show review status`);
|
|
739
|
+
console.log(` ${chalk3.cyan("3")}) Review next file`);
|
|
740
|
+
console.log(` ${chalk3.cyan("4")}) List all tasks`);
|
|
741
|
+
console.log(` ${chalk3.cyan("5")}) Run AI review on all pending`);
|
|
742
|
+
console.log(` ${chalk3.cyan("b")}) Back to main menu`);
|
|
765
743
|
console.log();
|
|
766
744
|
const choice = await prompt("Enter choice");
|
|
767
745
|
switch (choice.toLowerCase()) {
|
|
@@ -785,30 +763,68 @@ async function reviewCode() {
|
|
|
785
763
|
await reviewList2({});
|
|
786
764
|
break;
|
|
787
765
|
}
|
|
766
|
+
case "5": {
|
|
767
|
+
const { reviewRun: reviewRun2 } = await import("./review-3R6QXAXQ.js");
|
|
768
|
+
await reviewRun2({ all: true });
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
788
771
|
case "b":
|
|
789
|
-
await
|
|
772
|
+
await showMainMenu();
|
|
790
773
|
return;
|
|
791
774
|
default:
|
|
792
|
-
console.log(
|
|
775
|
+
console.log(chalk3.yellow("Invalid choice."));
|
|
793
776
|
}
|
|
794
777
|
await reviewCode();
|
|
795
778
|
}
|
|
796
779
|
function prompt(question) {
|
|
797
780
|
return new Promise((resolve) => {
|
|
798
|
-
const rl =
|
|
781
|
+
const rl = readline.createInterface({
|
|
799
782
|
input: process.stdin,
|
|
800
783
|
output: process.stdout
|
|
801
784
|
});
|
|
802
|
-
rl.question(`${
|
|
785
|
+
rl.question(`${chalk3.cyan("?")} ${question}: `, (answer) => {
|
|
803
786
|
rl.close();
|
|
804
787
|
resolve(answer);
|
|
805
788
|
});
|
|
806
789
|
});
|
|
807
790
|
}
|
|
791
|
+
function promptYesNo(question, defaultValue) {
|
|
792
|
+
return new Promise((resolve) => {
|
|
793
|
+
const rl = readline.createInterface({
|
|
794
|
+
input: process.stdin,
|
|
795
|
+
output: process.stdout
|
|
796
|
+
});
|
|
797
|
+
const hint = defaultValue ? "(Y/n)" : "(y/N)";
|
|
798
|
+
rl.question(`${chalk3.cyan("?")} ${question} ${hint}: `, (answer) => {
|
|
799
|
+
rl.close();
|
|
800
|
+
if (answer.trim() === "") {
|
|
801
|
+
resolve(defaultValue);
|
|
802
|
+
} else {
|
|
803
|
+
resolve(answer.toLowerCase().startsWith("y"));
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function promptChoice(question, options) {
|
|
809
|
+
return new Promise((resolve) => {
|
|
810
|
+
console.log(`${chalk3.cyan("?")} ${question}`);
|
|
811
|
+
for (const opt of options) {
|
|
812
|
+
console.log(` ${chalk3.dim(opt.key)}) ${opt.label}`);
|
|
813
|
+
}
|
|
814
|
+
const rl = readline.createInterface({
|
|
815
|
+
input: process.stdin,
|
|
816
|
+
output: process.stdout
|
|
817
|
+
});
|
|
818
|
+
rl.question(` ${chalk3.dim("Enter choice")}: `, (answer) => {
|
|
819
|
+
rl.close();
|
|
820
|
+
resolve(answer.trim() || "1");
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
}
|
|
808
824
|
|
|
809
825
|
// src/cli/credits.ts
|
|
810
|
-
import
|
|
811
|
-
import
|
|
826
|
+
import chalk4 from "chalk";
|
|
827
|
+
import ora from "ora";
|
|
812
828
|
import open from "open";
|
|
813
829
|
import { createClient } from "@supabase/supabase-js";
|
|
814
830
|
function getSupabaseClient(accessToken) {
|
|
@@ -817,7 +833,7 @@ function getSupabaseClient(accessToken) {
|
|
|
817
833
|
});
|
|
818
834
|
}
|
|
819
835
|
async function showCredits() {
|
|
820
|
-
const spinner =
|
|
836
|
+
const spinner = ora("Fetching credit balance...").start();
|
|
821
837
|
try {
|
|
822
838
|
const config = await loadConfig();
|
|
823
839
|
if (!config.accessToken || !config.userId) {
|
|
@@ -833,21 +849,21 @@ async function showCredits() {
|
|
|
833
849
|
const profile = data;
|
|
834
850
|
spinner.stop();
|
|
835
851
|
console.log();
|
|
836
|
-
console.log(
|
|
852
|
+
console.log(chalk4.bold("\u{1F4B0} Credit Balance"));
|
|
837
853
|
console.log();
|
|
838
854
|
const balance = (profile.credit_balance_cents || 0) / 100;
|
|
839
855
|
console.log(` Tier: ${formatTier(profile.tier)}`);
|
|
840
|
-
console.log(` Balance: ${
|
|
856
|
+
console.log(` Balance: ${chalk4.green(`$${balance.toFixed(2)}`)}`);
|
|
841
857
|
if (profile.tier === "FREE") {
|
|
842
858
|
console.log(` Atoms: ${profile.atoms_used_this_month}/10,000 this month`);
|
|
843
859
|
console.log();
|
|
844
|
-
console.log(
|
|
860
|
+
console.log(chalk4.dim(" Upgrade to Credits tier: archon credits add"));
|
|
845
861
|
} else if (profile.tier === "CREDITS") {
|
|
846
862
|
console.log();
|
|
847
|
-
console.log(
|
|
863
|
+
console.log(chalk4.dim(" Add more credits: archon credits add"));
|
|
848
864
|
} else if (profile.tier === "BYOK") {
|
|
849
865
|
console.log();
|
|
850
|
-
console.log(
|
|
866
|
+
console.log(chalk4.dim(" Using your own API keys - no credit charges"));
|
|
851
867
|
}
|
|
852
868
|
console.log();
|
|
853
869
|
} catch (err) {
|
|
@@ -856,7 +872,7 @@ async function showCredits() {
|
|
|
856
872
|
}
|
|
857
873
|
}
|
|
858
874
|
async function addCredits(options = {}) {
|
|
859
|
-
const spinner =
|
|
875
|
+
const spinner = ora("Preparing checkout...").start();
|
|
860
876
|
try {
|
|
861
877
|
const config = await loadConfig();
|
|
862
878
|
if (!config.accessToken || !config.userId) {
|
|
@@ -893,18 +909,18 @@ async function addCredits(options = {}) {
|
|
|
893
909
|
}
|
|
894
910
|
spinner.succeed("Checkout ready");
|
|
895
911
|
console.log();
|
|
896
|
-
console.log(
|
|
912
|
+
console.log(chalk4.bold("\u{1F6D2} Add Credits"));
|
|
897
913
|
console.log();
|
|
898
|
-
console.log(` Amount: ${
|
|
914
|
+
console.log(` Amount: ${chalk4.green(`$${amountDollars.toFixed(2)}`)}`);
|
|
899
915
|
console.log();
|
|
900
916
|
console.log(" Opening checkout in browser...");
|
|
901
917
|
console.log();
|
|
902
|
-
console.log(
|
|
918
|
+
console.log(chalk4.dim(` Or visit: ${checkoutUrl}`));
|
|
903
919
|
console.log();
|
|
904
920
|
try {
|
|
905
921
|
await open(checkoutUrl);
|
|
906
922
|
} catch {
|
|
907
|
-
console.log(
|
|
923
|
+
console.log(chalk4.yellow(" Could not open browser. Please visit the URL above."));
|
|
908
924
|
}
|
|
909
925
|
} catch (err) {
|
|
910
926
|
spinner.fail("Error preparing checkout");
|
|
@@ -912,7 +928,7 @@ async function addCredits(options = {}) {
|
|
|
912
928
|
}
|
|
913
929
|
}
|
|
914
930
|
async function showHistory(options = {}) {
|
|
915
|
-
const spinner =
|
|
931
|
+
const spinner = ora("Fetching usage history...").start();
|
|
916
932
|
try {
|
|
917
933
|
const config = await loadConfig();
|
|
918
934
|
if (!config.accessToken || !config.userId) {
|
|
@@ -929,15 +945,15 @@ async function showHistory(options = {}) {
|
|
|
929
945
|
const usage = data;
|
|
930
946
|
spinner.stop();
|
|
931
947
|
console.log();
|
|
932
|
-
console.log(
|
|
948
|
+
console.log(chalk4.bold("\u{1F4CA} Usage History"));
|
|
933
949
|
console.log();
|
|
934
950
|
if (!usage || usage.length === 0) {
|
|
935
|
-
console.log(
|
|
951
|
+
console.log(chalk4.dim(" No usage recorded yet."));
|
|
936
952
|
console.log();
|
|
937
953
|
return;
|
|
938
954
|
}
|
|
939
|
-
console.log(
|
|
940
|
-
console.log(
|
|
955
|
+
console.log(chalk4.dim(" Model Tokens Cost Date"));
|
|
956
|
+
console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
941
957
|
for (const row of usage) {
|
|
942
958
|
const model = row.model.padEnd(30).slice(0, 30);
|
|
943
959
|
const tokens = (row.input_tokens + row.output_tokens).toString().padStart(8);
|
|
@@ -947,9 +963,9 @@ async function showHistory(options = {}) {
|
|
|
947
963
|
}
|
|
948
964
|
const totalCost = usage.reduce((sum, r) => sum + r.base_cost, 0);
|
|
949
965
|
const totalTokens = usage.reduce((sum, r) => sum + r.input_tokens + r.output_tokens, 0);
|
|
950
|
-
console.log(
|
|
966
|
+
console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
951
967
|
console.log(
|
|
952
|
-
` ${"Total".padEnd(30)} ${totalTokens.toString().padStart(8)} ${
|
|
968
|
+
` ${"Total".padEnd(30)} ${totalTokens.toString().padStart(8)} ${chalk4.green(`$${totalCost.toFixed(4)}`.padStart(10))}`
|
|
953
969
|
);
|
|
954
970
|
console.log();
|
|
955
971
|
} catch (err) {
|
|
@@ -958,7 +974,7 @@ async function showHistory(options = {}) {
|
|
|
958
974
|
}
|
|
959
975
|
}
|
|
960
976
|
async function manageBudget(options = {}) {
|
|
961
|
-
const spinner =
|
|
977
|
+
const spinner = ora("Fetching budget settings...").start();
|
|
962
978
|
try {
|
|
963
979
|
const config = await loadConfig();
|
|
964
980
|
if (!config.accessToken || !config.userId) {
|
|
@@ -1015,24 +1031,24 @@ async function manageBudget(options = {}) {
|
|
|
1015
1031
|
}
|
|
1016
1032
|
spinner.stop();
|
|
1017
1033
|
console.log();
|
|
1018
|
-
console.log(
|
|
1034
|
+
console.log(chalk4.bold("\u{1F4CA} Monthly Budget"));
|
|
1019
1035
|
console.log();
|
|
1020
1036
|
if (profile.monthly_budget_cents === null) {
|
|
1021
|
-
console.log(` Budget: ${
|
|
1037
|
+
console.log(` Budget: ${chalk4.dim("No limit set")}`);
|
|
1022
1038
|
} else {
|
|
1023
1039
|
const budget = profile.monthly_budget_cents / 100;
|
|
1024
1040
|
const spend = (profile.monthly_spend_cents || 0) / 100;
|
|
1025
1041
|
const remaining = budget - spend;
|
|
1026
1042
|
const percent = budget > 0 ? Math.round(spend / budget * 100) : 0;
|
|
1027
|
-
console.log(` Budget: ${
|
|
1043
|
+
console.log(` Budget: ${chalk4.green(`$${budget.toFixed(2)}`)} / month`);
|
|
1028
1044
|
console.log(` Spent: $${spend.toFixed(2)} (${percent}%)`);
|
|
1029
|
-
console.log(` Remaining: ${remaining >= 0 ?
|
|
1045
|
+
console.log(` Remaining: ${remaining >= 0 ? chalk4.green(`$${remaining.toFixed(2)}`) : chalk4.red(`-$${Math.abs(remaining).toFixed(2)}`)}`);
|
|
1030
1046
|
}
|
|
1031
1047
|
console.log(` Alert at: ${profile.budget_alert_threshold_percent}% of budget`);
|
|
1032
1048
|
console.log();
|
|
1033
|
-
console.log(
|
|
1034
|
-
console.log(
|
|
1035
|
-
console.log(
|
|
1049
|
+
console.log(chalk4.dim(" Set budget: archon credits budget --set 50"));
|
|
1050
|
+
console.log(chalk4.dim(" Clear budget: archon credits budget --clear"));
|
|
1051
|
+
console.log(chalk4.dim(" Set alert: archon credits budget --alert 80"));
|
|
1036
1052
|
console.log();
|
|
1037
1053
|
} catch (err) {
|
|
1038
1054
|
spinner.fail("Error managing budget");
|
|
@@ -1040,7 +1056,7 @@ async function manageBudget(options = {}) {
|
|
|
1040
1056
|
}
|
|
1041
1057
|
}
|
|
1042
1058
|
async function manageAutoRecharge(options = {}) {
|
|
1043
|
-
const spinner =
|
|
1059
|
+
const spinner = ora("Fetching auto-recharge settings...").start();
|
|
1044
1060
|
try {
|
|
1045
1061
|
const config = await loadConfig();
|
|
1046
1062
|
if (!config.accessToken || !config.userId) {
|
|
@@ -1094,12 +1110,12 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1094
1110
|
}
|
|
1095
1111
|
spinner.stop();
|
|
1096
1112
|
console.log();
|
|
1097
|
-
console.log(
|
|
1113
|
+
console.log(chalk4.bold("\u{1F504} Auto-Recharge"));
|
|
1098
1114
|
console.log();
|
|
1099
1115
|
if (!profile.auto_recharge_enabled) {
|
|
1100
|
-
console.log(` Status: ${
|
|
1116
|
+
console.log(` Status: ${chalk4.dim("Disabled")}`);
|
|
1101
1117
|
} else {
|
|
1102
|
-
console.log(` Status: ${
|
|
1118
|
+
console.log(` Status: ${chalk4.green("Enabled")}`);
|
|
1103
1119
|
if (profile.auto_recharge_threshold_cents !== null) {
|
|
1104
1120
|
console.log(` When: Balance drops below $${(profile.auto_recharge_threshold_cents / 100).toFixed(2)}`);
|
|
1105
1121
|
}
|
|
@@ -1107,10 +1123,10 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1107
1123
|
console.log(` Amount: $${(profile.auto_recharge_amount_cents / 100).toFixed(2)}`);
|
|
1108
1124
|
}
|
|
1109
1125
|
}
|
|
1110
|
-
console.log(` Payment: ${profile.stripe_payment_method_id ?
|
|
1126
|
+
console.log(` Payment: ${profile.stripe_payment_method_id ? chalk4.green("Card saved") : chalk4.dim("No card saved")}`);
|
|
1111
1127
|
console.log();
|
|
1112
|
-
console.log(
|
|
1113
|
-
console.log(
|
|
1128
|
+
console.log(chalk4.dim(" Enable: archon credits auto-recharge --enable --threshold 5 --amount 20"));
|
|
1129
|
+
console.log(chalk4.dim(" Disable: archon credits auto-recharge --disable"));
|
|
1114
1130
|
console.log();
|
|
1115
1131
|
} catch (err) {
|
|
1116
1132
|
spinner.fail("Error managing auto-recharge");
|
|
@@ -1120,11 +1136,11 @@ async function manageAutoRecharge(options = {}) {
|
|
|
1120
1136
|
function formatTier(tier) {
|
|
1121
1137
|
switch (tier) {
|
|
1122
1138
|
case "FREE":
|
|
1123
|
-
return
|
|
1139
|
+
return chalk4.blue("Free (10k atoms/month)");
|
|
1124
1140
|
case "CREDITS":
|
|
1125
|
-
return
|
|
1141
|
+
return chalk4.green("Credits (Pay-as-you-go)");
|
|
1126
1142
|
case "BYOK":
|
|
1127
|
-
return
|
|
1143
|
+
return chalk4.magenta("BYOK (Bring Your Own Key)");
|
|
1128
1144
|
default:
|
|
1129
1145
|
return tier;
|
|
1130
1146
|
}
|
|
@@ -1399,8 +1415,247 @@ async function watch() {
|
|
|
1399
1415
|
await waitUntilExit();
|
|
1400
1416
|
}
|
|
1401
1417
|
|
|
1418
|
+
// src/cli/deps.ts
|
|
1419
|
+
import { Command } from "commander";
|
|
1420
|
+
import chalk5 from "chalk";
|
|
1421
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
1422
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1423
|
+
var DEPENDENCIES_FILENAME = "DEPENDENCIES.md";
|
|
1424
|
+
function createDepsCommand() {
|
|
1425
|
+
const deps = new Command("deps").description("Manage file-level dependencies (DEPENDENCIES.md)").addHelpText(
|
|
1426
|
+
"after",
|
|
1427
|
+
`
|
|
1428
|
+
Examples:
|
|
1429
|
+
archon deps list List all dependency rules
|
|
1430
|
+
archon deps check --files a.ts,b.ts Check if files have downstream impacts
|
|
1431
|
+
archon deps add --source src/api.ts --dependent src/cli/**
|
|
1432
|
+
archon deps graph Generate Mermaid dependency diagram
|
|
1433
|
+
`
|
|
1434
|
+
);
|
|
1435
|
+
deps.command("list").description("List all dependency rules").option("-v, --verbose", "Show detailed information").action(async (options) => {
|
|
1436
|
+
const parser = new DependencyParser();
|
|
1437
|
+
if (!parser.exists()) {
|
|
1438
|
+
console.log(chalk5.yellow("No DEPENDENCIES.md found."));
|
|
1439
|
+
console.log(chalk5.dim("Create one with: archon deps add --source <path> --dependent <path>"));
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const result = await parser.parse();
|
|
1443
|
+
if (!result.success) {
|
|
1444
|
+
console.log(chalk5.red(`Parse error: ${result.error}`));
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
const rules = result.document?.rules ?? [];
|
|
1448
|
+
if (rules.length === 0) {
|
|
1449
|
+
console.log(chalk5.dim("No dependency rules defined."));
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
console.log(chalk5.bold(`
|
|
1453
|
+
\u{1F4E6} Dependency Rules (${rules.length})
|
|
1454
|
+
`));
|
|
1455
|
+
for (const rule of rules) {
|
|
1456
|
+
const severityColor = rule.severity === "BLOCKER" ? chalk5.red : rule.severity === "WARNING" ? chalk5.yellow : chalk5.blue;
|
|
1457
|
+
console.log(`${severityColor(`[${rule.severity}]`)} ${chalk5.bold(rule.id)}`);
|
|
1458
|
+
console.log(` Source: ${chalk5.cyan(rule.source)}`);
|
|
1459
|
+
console.log(` Dependents: ${rule.dependents.map((d) => chalk5.dim(d)).join(", ")}`);
|
|
1460
|
+
if (rule.reason && options.verbose) {
|
|
1461
|
+
console.log(` Reason: ${chalk5.dim(rule.reason)}`);
|
|
1462
|
+
}
|
|
1463
|
+
if (rule.mustTest && options.verbose) {
|
|
1464
|
+
console.log(` Must test: ${rule.mustTest.join(", ")}`);
|
|
1465
|
+
}
|
|
1466
|
+
console.log("");
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
deps.command("check").description("Check if files have downstream dependency impacts").requiredOption("-f, --files <files>", "Comma-separated list of files to check").action(async (options) => {
|
|
1470
|
+
const files = options.files.split(",").map((f) => f.trim());
|
|
1471
|
+
const parser = new DependencyParser();
|
|
1472
|
+
if (!parser.exists()) {
|
|
1473
|
+
console.log(chalk5.dim("No DEPENDENCIES.md found. No dependency checks performed."));
|
|
1474
|
+
process.exit(0);
|
|
1475
|
+
}
|
|
1476
|
+
const result = await parser.checkFiles(files);
|
|
1477
|
+
if (result.impacts.length === 0) {
|
|
1478
|
+
console.log(chalk5.green("\u2705 No downstream dependency impacts found."));
|
|
1479
|
+
process.exit(0);
|
|
1480
|
+
}
|
|
1481
|
+
console.log(chalk5.yellow(`
|
|
1482
|
+
\u26A0\uFE0F Found ${result.impacts.length} dependency impact(s):
|
|
1483
|
+
`));
|
|
1484
|
+
for (const impact of result.impacts) {
|
|
1485
|
+
const severityColor = impact.rule.severity === "BLOCKER" ? chalk5.red : impact.rule.severity === "WARNING" ? chalk5.yellow : chalk5.blue;
|
|
1486
|
+
console.log(severityColor(`[${impact.rule.severity}] ${impact.rule.id}`));
|
|
1487
|
+
console.log(` Changing: ${chalk5.cyan(impact.matchedSource)}`);
|
|
1488
|
+
console.log(` May impact: ${impact.affectedDependents.join(", ")}`);
|
|
1489
|
+
if (impact.rule.reason) {
|
|
1490
|
+
console.log(` Reason: ${chalk5.dim(impact.rule.reason)}`);
|
|
1491
|
+
}
|
|
1492
|
+
console.log("");
|
|
1493
|
+
}
|
|
1494
|
+
if (result.hasBlockers) {
|
|
1495
|
+
console.log(chalk5.red("\u274C BLOCKER-level impacts found. Review before proceeding."));
|
|
1496
|
+
process.exit(1);
|
|
1497
|
+
}
|
|
1498
|
+
process.exit(0);
|
|
1499
|
+
});
|
|
1500
|
+
deps.command("add").description("Add a new dependency rule").requiredOption("-s, --source <path>", "Source file or glob pattern").requiredOption("-d, --dependent <path>", "Dependent file or glob pattern").option("--severity <level>", "Severity: INFO, WARNING, BLOCKER", "WARNING").option("--reason <text>", "Reason for this dependency").option("--id <id>", "Custom rule ID (e.g., DEP-001)").action(async (options) => {
|
|
1501
|
+
const source = options.source;
|
|
1502
|
+
const dependent = options.dependent;
|
|
1503
|
+
const severity = options.severity.toUpperCase();
|
|
1504
|
+
const reason = options.reason || "";
|
|
1505
|
+
const parser = new DependencyParser();
|
|
1506
|
+
let existingRules = [];
|
|
1507
|
+
let markdownBody = "";
|
|
1508
|
+
if (parser.exists()) {
|
|
1509
|
+
const content = await readFile2(DEPENDENCIES_FILENAME, "utf-8");
|
|
1510
|
+
const result = await parser.parse();
|
|
1511
|
+
if (result.success && result.document) {
|
|
1512
|
+
existingRules = result.document.rules;
|
|
1513
|
+
}
|
|
1514
|
+
const parts = content.split("---");
|
|
1515
|
+
if (parts.length >= 3) {
|
|
1516
|
+
markdownBody = parts.slice(2).join("---");
|
|
1517
|
+
}
|
|
1518
|
+
} else {
|
|
1519
|
+
markdownBody = `
|
|
1520
|
+
|
|
1521
|
+
# Dependencies (File & Symbol Impact Map)
|
|
1522
|
+
|
|
1523
|
+
## How to use
|
|
1524
|
+
- When you change a \`source\`, you must review each \`dependent\`.
|
|
1525
|
+
- Keep rules coarse and high-signal. Prefer fewer, accurate rules over exhaustive lists.
|
|
1526
|
+
|
|
1527
|
+
## Conventions
|
|
1528
|
+
- \`source\` and \`dependents\` accept glob patterns.
|
|
1529
|
+
- Use \`symbols\` to document function/class coupling when helpful.
|
|
1530
|
+
`;
|
|
1531
|
+
}
|
|
1532
|
+
const nextId = options.id || `DEP-${String(existingRules.length + 1).padStart(3, "0")}`;
|
|
1533
|
+
const existingRule = existingRules.find(
|
|
1534
|
+
(r) => r.source === source && r.dependents.includes(dependent)
|
|
1535
|
+
);
|
|
1536
|
+
if (existingRule) {
|
|
1537
|
+
console.log(chalk5.yellow(`Rule already exists: ${existingRule.id}`));
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const newRule = {
|
|
1541
|
+
id: nextId,
|
|
1542
|
+
source,
|
|
1543
|
+
dependents: [dependent],
|
|
1544
|
+
severity,
|
|
1545
|
+
reason
|
|
1546
|
+
};
|
|
1547
|
+
existingRules.push(newRule);
|
|
1548
|
+
const yaml = generateYamlFrontmatter(existingRules);
|
|
1549
|
+
await writeFile2(DEPENDENCIES_FILENAME, `---
|
|
1550
|
+
${yaml}---${markdownBody}`, "utf-8");
|
|
1551
|
+
console.log(chalk5.green(`\u2705 Added dependency rule: ${nextId}`));
|
|
1552
|
+
console.log(` Source: ${chalk5.cyan(source)}`);
|
|
1553
|
+
console.log(` Dependent: ${chalk5.dim(dependent)}`);
|
|
1554
|
+
});
|
|
1555
|
+
deps.command("graph").description("Generate Mermaid diagram of dependencies").option("--output <file>", "Write to file instead of stdout").action(async (options) => {
|
|
1556
|
+
const parser = new DependencyParser();
|
|
1557
|
+
if (!parser.exists()) {
|
|
1558
|
+
console.log(chalk5.yellow("No DEPENDENCIES.md found."));
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const mermaid = await parser.generateGraph();
|
|
1562
|
+
if (options.output) {
|
|
1563
|
+
await writeFile2(options.output, mermaid, "utf-8");
|
|
1564
|
+
console.log(chalk5.green(`\u2705 Graph written to ${options.output}`));
|
|
1565
|
+
} else {
|
|
1566
|
+
console.log("\n```mermaid");
|
|
1567
|
+
console.log(mermaid);
|
|
1568
|
+
console.log("```\n");
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
deps.command("init").description("Create a starter DEPENDENCIES.md file").action(async () => {
|
|
1572
|
+
if (existsSync4(DEPENDENCIES_FILENAME)) {
|
|
1573
|
+
console.log(chalk5.yellow("DEPENDENCIES.md already exists."));
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const template = `---
|
|
1577
|
+
version: "1.0"
|
|
1578
|
+
updatedAt: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
|
|
1579
|
+
rules: []
|
|
1580
|
+
---
|
|
1581
|
+
|
|
1582
|
+
# Dependencies (File & Symbol Impact Map)
|
|
1583
|
+
|
|
1584
|
+
## How to use
|
|
1585
|
+
|
|
1586
|
+
This file tracks file-level dependencies to prevent regressions. When you change a \`source\` file, you must review and potentially update each \`dependent\`.
|
|
1587
|
+
|
|
1588
|
+
**Example rule:**
|
|
1589
|
+
\`\`\`yaml
|
|
1590
|
+
rules:
|
|
1591
|
+
- id: "DEP-001"
|
|
1592
|
+
source: "src/api/auth.ts"
|
|
1593
|
+
dependents:
|
|
1594
|
+
- "src/cli/**"
|
|
1595
|
+
- "src/server/routes.ts"
|
|
1596
|
+
severity: "WARNING"
|
|
1597
|
+
reason: "CLI and server depend on auth types; changes may require updates"
|
|
1598
|
+
mustTest:
|
|
1599
|
+
- "pnpm test auth"
|
|
1600
|
+
\`\`\`
|
|
1601
|
+
|
|
1602
|
+
## Severity Levels
|
|
1603
|
+
|
|
1604
|
+
- **INFO** \u2014 FYI, might want to review
|
|
1605
|
+
- **WARNING** \u2014 Should review dependents before merging
|
|
1606
|
+
- **BLOCKER** \u2014 Must update dependents before this change can proceed
|
|
1607
|
+
|
|
1608
|
+
## Conventions
|
|
1609
|
+
|
|
1610
|
+
- Use glob patterns for broad dependencies (\`src/core/**\`)
|
|
1611
|
+
- Keep rules high-signal; don't track every import
|
|
1612
|
+
- Add \`mustTest\` for critical paths
|
|
1613
|
+
- Update \`updatedAt\` when modifying rules
|
|
1614
|
+
|
|
1615
|
+
---
|
|
1616
|
+
|
|
1617
|
+
*Powered by [ArchonDev](https://archondev.io)*
|
|
1618
|
+
`;
|
|
1619
|
+
await writeFile2(DEPENDENCIES_FILENAME, template, "utf-8");
|
|
1620
|
+
console.log(chalk5.green("\u2705 Created DEPENDENCIES.md"));
|
|
1621
|
+
console.log(chalk5.dim("Add your first rule with: archon deps add --source <path> --dependent <path>"));
|
|
1622
|
+
});
|
|
1623
|
+
return deps;
|
|
1624
|
+
}
|
|
1625
|
+
function generateYamlFrontmatter(rules) {
|
|
1626
|
+
const lines = [];
|
|
1627
|
+
lines.push(`version: "1.0"`);
|
|
1628
|
+
lines.push(`updatedAt: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"`);
|
|
1629
|
+
lines.push(`rules:`);
|
|
1630
|
+
for (const rule of rules) {
|
|
1631
|
+
lines.push(` - id: "${rule.id}"`);
|
|
1632
|
+
lines.push(` source: "${rule.source}"`);
|
|
1633
|
+
lines.push(` dependents:`);
|
|
1634
|
+
for (const dep of rule.dependents) {
|
|
1635
|
+
lines.push(` - "${dep}"`);
|
|
1636
|
+
}
|
|
1637
|
+
lines.push(` severity: "${rule.severity}"`);
|
|
1638
|
+
if (rule.reason) {
|
|
1639
|
+
lines.push(` reason: "${rule.reason.replace(/"/g, '\\"')}"`);
|
|
1640
|
+
}
|
|
1641
|
+
if (rule.symbols && rule.symbols.length > 0) {
|
|
1642
|
+
lines.push(` symbols:`);
|
|
1643
|
+
for (const sym of rule.symbols) {
|
|
1644
|
+
lines.push(` - "${sym}"`);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (rule.mustTest && rule.mustTest.length > 0) {
|
|
1648
|
+
lines.push(` mustTest:`);
|
|
1649
|
+
for (const test of rule.mustTest) {
|
|
1650
|
+
lines.push(` - "${test}"`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return lines.join("\n") + "\n";
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1402
1657
|
// src/cli/index.ts
|
|
1403
|
-
var program = new
|
|
1658
|
+
var program = new Command2();
|
|
1404
1659
|
program.name("archon").description("Local-first AI-powered development governance").version("1.1.0").action(async () => {
|
|
1405
1660
|
const cwd = process.cwd();
|
|
1406
1661
|
if (!isInitialized(cwd)) {
|
|
@@ -1531,4 +1786,5 @@ reviewCommand.command("run").description("Run AI-powered review on pending tasks
|
|
|
1531
1786
|
reviewCommand.action(async () => {
|
|
1532
1787
|
await reviewStatus();
|
|
1533
1788
|
});
|
|
1789
|
+
program.addCommand(createDepsCommand());
|
|
1534
1790
|
program.parse();
|