agent-enderun 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +4 -4
  2. package/{ENDERUN.md → .enderun/ENDERUN.md} +4 -2
  3. package/.enderun/PROJECT_MEMORY.md +12 -45
  4. package/.enderun/STATUS.md +9 -8
  5. package/.enderun/agents/agent_army_schema.json +29 -7
  6. package/.enderun/agents/analyst.md +3 -5
  7. package/.enderun/agents/backend.md +11 -31
  8. package/.enderun/agents/devops.md +2 -3
  9. package/.enderun/agents/explorer.md +4 -4
  10. package/.enderun/agents/frontend.md +76 -69
  11. package/.enderun/agents/git.md +3 -3
  12. package/.enderun/agents/manager.md +30 -42
  13. package/.enderun/agents/mobile.md +3 -3
  14. package/.enderun/agents/native.md +106 -0
  15. package/.enderun/agents/orchestrator.md +2 -2
  16. package/.enderun/agents/qa.md +7 -10
  17. package/.enderun/agents/schema/agent-lifecycle-schema.json +29 -8
  18. package/.enderun/agents/security.md +2 -2
  19. package/.enderun/cli-commands.json +4 -0
  20. package/.enderun/config.json +2 -2
  21. package/.enderun/knowledge/SHIM_TEMPLATE.md +25 -0
  22. package/.enderun/knowledge/eslint-standards.md +63 -0
  23. package/.enderun/knowledge/hermes_protocol.md +1 -1
  24. package/.enderun/knowledge/manager_authority_audit_enforcement.md +2 -2
  25. package/.enderun/mcp_config.json +9 -0
  26. package/.enderun/memory-graph/agent-contexts/analyst.json +1 -1
  27. package/.enderun/memory-graph/agent-contexts/backend.json +1 -1
  28. package/.enderun/memory-graph/agent-contexts/devops.json +1 -1
  29. package/.enderun/memory-graph/agent-contexts/explorer.json +1 -1
  30. package/.enderun/memory-graph/agent-contexts/frontend.json +1 -1
  31. package/.enderun/memory-graph/agent-contexts/git.json +1 -1
  32. package/.enderun/memory-graph/agent-contexts/manager.json +1 -1
  33. package/.enderun/memory-graph/agent-contexts/mobile.json +1 -1
  34. package/.enderun/memory-graph/agent-contexts/native.json +1 -0
  35. package/.enderun/memory-graph/agent-contexts/orchestrator.json +1 -1
  36. package/.enderun/memory-graph/agent-contexts/qa.json +1 -1
  37. package/.enderun/memory-graph/agent-contexts/security.json +1 -1
  38. package/.enderun/memory-graph/graph.json +123 -15
  39. package/.enderun/memory-graph/shared-facts.json +26 -31
  40. package/README.md +89 -413
  41. package/agent.md +25 -0
  42. package/bin/cli.js +9 -9
  43. package/bin/update-contract.js +34 -37
  44. package/bin/validate-agent-army.js +38 -13
  45. package/claude.md +14 -8
  46. package/docs/README.md +35 -16
  47. package/docs/tech-stack.md +30 -0
  48. package/eslint.config.js +68 -0
  49. package/framework-mcp/dist/index.js +184 -24
  50. package/framework-mcp/package.json +6 -33
  51. package/framework-mcp/tsconfig.json +3 -6
  52. package/gemini.md +12 -6
  53. package/grok.md +25 -0
  54. package/package.json +12 -6
  55. package/panda.config.ts +86 -86
  56. package/tsconfig.json +9 -0
  57. package/.enderun/logs/analyst.json +0 -1
  58. package/.enderun/logs/backend.json +0 -1
  59. package/.enderun/logs/devops.json +0 -1
  60. package/.enderun/logs/explorer.json +0 -1
  61. package/.enderun/logs/frontend.json +0 -1
  62. package/.enderun/logs/git.json +0 -1
  63. package/.enderun/logs/manager.json +0 -1
  64. package/.enderun/logs/mobile.json +0 -1
  65. package/.enderun/logs/native.json +0 -1
  66. package/.enderun/logs/orchestrator.json +0 -1
  67. package/.enderun/logs/qa.json +0 -1
  68. package/.enderun/logs/security.json +0 -1
  69. package/.env.example +0 -10
  70. package/cursor.md +0 -19
  71. package/framework-mcp/README.md +0 -55
  72. package/framework-mcp/dist/schemas.js +0 -84
  73. package/framework-mcp/dist/tools/codebase.js +0 -294
  74. package/framework-mcp/dist/tools/contract.js +0 -197
  75. package/framework-mcp/dist/tools/framework.js +0 -225
  76. package/framework-mcp/dist/tools/git.js +0 -51
  77. package/framework-mcp/dist/tools/governance.js +0 -696
  78. package/framework-mcp/dist/tools/index.js +0 -47
  79. package/framework-mcp/dist/tools/knowledge.js +0 -133
  80. package/framework-mcp/dist/tools/memory.js +0 -217
  81. package/framework-mcp/dist/tools/messages.js +0 -193
  82. package/framework-mcp/dist/tools/monitoring.js +0 -294
  83. package/framework-mcp/dist/tools/orchestration.js +0 -369
  84. package/framework-mcp/dist/tools/pipeline.js +0 -267
  85. package/framework-mcp/dist/tools/security.js +0 -137
  86. package/framework-mcp/dist/utils.js +0 -121
  87. package/framework-mcp/src/index.ts +0 -49
  88. package/framework-mcp/src/schemas.ts +0 -106
  89. package/framework-mcp/src/tools/codebase.ts +0 -284
  90. package/framework-mcp/src/tools/contract.ts +0 -221
  91. package/framework-mcp/src/tools/framework.ts +0 -223
  92. package/framework-mcp/src/tools/git.ts +0 -42
  93. package/framework-mcp/src/tools/governance.ts +0 -891
  94. package/framework-mcp/src/tools/index.ts +0 -50
  95. package/framework-mcp/src/tools/knowledge.ts +0 -141
  96. package/framework-mcp/src/tools/memory.ts +0 -207
  97. package/framework-mcp/src/tools/messages.ts +0 -213
  98. package/framework-mcp/src/tools/monitoring.ts +0 -351
  99. package/framework-mcp/src/tools/orchestration.ts +0 -440
  100. package/framework-mcp/src/tools/pipeline.ts +0 -353
  101. package/framework-mcp/src/tools/security.ts +0 -143
  102. package/framework-mcp/src/utils.ts +0 -130
  103. package/src/cli/index.ts +0 -2188
package/src/cli/index.ts DELETED
@@ -1,2188 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "fs";
4
- import path from "path";
5
- import { fileURLToPath } from "url";
6
- import crypto from "crypto";
7
- import { execSync } from "child_process";
8
- import cp from "child_process";
9
-
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
- const sourceDir = path.join(__dirname, "..", "..");
13
- const targetDir = process.cwd();
14
-
15
- // --- CONSTANTS ---
16
- const FRAMEWORK_VERSION = getPackageVersion();
17
-
18
- // --- HELPER FUNCTIONS ---
19
-
20
- function getPackageVersion() {
21
- const pkg = JSON.parse(fs.readFileSync(path.join(sourceDir, "package.json"), "utf8"));
22
- return pkg.version;
23
- }
24
-
25
- function getFrameworkDir() {
26
- const candidates = [".enderun", ".gemini", ".cursor"];
27
- for (const dir of candidates) {
28
- if (fs.existsSync(path.join(targetDir, dir))) {
29
- return dir;
30
- }
31
- }
32
- return ".gemini";
33
- }
34
-
35
- function getMemoryPath() {
36
- return path.join(targetDir, getFrameworkDir(), "PROJECT_MEMORY.md");
37
- }
38
-
39
- export function generateULID(seedTime = Date.now(), seed?: number) {
40
- const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
41
- const ENCODING_LEN = ENCODING.length;
42
- let time = seedTime;
43
- const timeChars = new Array(10);
44
- for (let i = 9; i >= 0; i--) {
45
- timeChars[i] = ENCODING.charAt(time % ENCODING_LEN);
46
- time = Math.floor(time / ENCODING_LEN);
47
- }
48
- const randomChars = new Array(16);
49
- if (seed) {
50
- let pseudoRandom = seed;
51
- for (let i = 0; i < 16; i++) {
52
- pseudoRandom = (pseudoRandom * 16807) % 2147483647;
53
- randomChars[i] = ENCODING.charAt(pseudoRandom % ENCODING_LEN);
54
- }
55
- } else {
56
- for (let i = 0; i < 16; i++) {
57
- randomChars[i] = ENCODING.charAt(Math.floor(Math.random() * ENCODING_LEN));
58
- }
59
- }
60
- return timeChars.join("") + randomChars.join("");
61
- }
62
-
63
- function sleep(ms: number): void {
64
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
65
- }
66
-
67
- function acquireMemoryLock(lockPath: string, maxRetries = 5): boolean {
68
- for (let attempt = 0; attempt < maxRetries; attempt += 1) {
69
- try {
70
- const fd = fs.openSync(lockPath, "wx");
71
- fs.closeSync(fd);
72
- return true;
73
- } catch (error) {
74
- if (error?.code !== "EEXIST") throw error;
75
- if (attempt < maxRetries - 1) sleep(1000);
76
- }
77
- }
78
- return false;
79
- }
80
-
81
- function releaseMemoryLock(lockPath: string): void {
82
- if (fs.existsSync(lockPath)) fs.unlinkSync(lockPath);
83
- }
84
-
85
- function insertTaskRow(memoryContent: string, row: string): string | null {
86
- const sectionHeader = "## ACTIVE TASKS";
87
- const tableDivider = "| :--- | :--- | :--- | :--- | :--- |";
88
- const sectionIndex = memoryContent.indexOf(sectionHeader);
89
- if (sectionIndex === -1) return null;
90
- const dividerIndex = memoryContent.indexOf(tableDivider, sectionIndex);
91
- if (dividerIndex === -1) return null;
92
- const dividerLineEnd = memoryContent.indexOf("\n", dividerIndex);
93
- if (dividerLineEnd === -1) return null;
94
-
95
- return (
96
- memoryContent.slice(0, dividerLineEnd + 1) +
97
- `${row}\n` +
98
- memoryContent.slice(dividerLineEnd + 1)
99
- );
100
- }
101
-
102
- export function sanitizeTableCell(value: unknown): string {
103
- return String(value).replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
104
- }
105
-
106
- export function normalizeAgentName(agent?: unknown): string {
107
- return String(agent || "manager").replace(/^@+/, "").trim() || "manager";
108
- }
109
-
110
- export function normalizePriority(priority?: unknown): string {
111
- const normalized = String(priority || "P2").toUpperCase().trim();
112
- return /^P[0-3]$/.test(normalized) ? normalized : "P2";
113
- }
114
-
115
- function mergePackageJson(targetPath: string, sourcePath: string): void {
116
- let targetPkg = {};
117
- if (fs.existsSync(targetPath)) {
118
- try {
119
- targetPkg = JSON.parse(fs.readFileSync(targetPath, "utf8"));
120
- } catch {
121
- console.warn("⚠️ Could not parse existing package.json, creating a new one.");
122
- }
123
- }
124
-
125
- const sourcePkg = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
126
-
127
- type PackageMap = Record<string, string>;
128
- const sanitizeDeps = (deps: PackageMap | Record<string, unknown> | undefined): Record<string, string> | undefined => {
129
- if (!deps) return deps as undefined;
130
- const cleaned: Record<string, string> = {};
131
- for (const [name, version] of Object.entries(deps as Record<string, unknown>)) {
132
- cleaned[name] = (typeof version === "string" && version.startsWith("workspace:")) ? "*" : String(version || "");
133
- }
134
- return cleaned;
135
- };
136
-
137
- interface PackageJson {
138
- name?: string;
139
- version?: string;
140
- type?: string;
141
- workspaces?: string[];
142
- dependencies?: Record<string, string>;
143
- devDependencies?: Record<string, string>;
144
- peerDependencies?: Record<string, string>;
145
- optionalDependencies?: Record<string, string>;
146
- scripts?: Record<string, string>;
147
- enderun?: Record<string, unknown>;
148
- }
149
-
150
- const actualTargetScope = (targetPkg as PackageJson)?.name && (targetPkg as PackageJson).name!.startsWith("@")
151
- ? (targetPkg as PackageJson).name!.split("/")[0]
152
- : ((targetPkg as PackageJson).name ? `@${(targetPkg as PackageJson).name}` : "");
153
-
154
- // Cleanup potential leftovers from previous bugged runs where agent-enderun was renamed to target name
155
- if (actualTargetScope) {
156
- const scopeName = actualTargetScope.startsWith("@") ? actualTargetScope.slice(1) : actualTargetScope;
157
- const cleanup = (obj: Record<string, unknown> | undefined): void => {
158
- if (!obj) return;
159
- delete obj[scopeName];
160
- delete obj[actualTargetScope];
161
- delete obj["agent-enderun"]; // Will be re-added correctly
162
- };
163
- cleanup((targetPkg as PackageJson).devDependencies);
164
- cleanup((targetPkg as PackageJson).dependencies);
165
- }
166
-
167
- targetPkg.dependencies = sanitizeDeps({
168
- ...targetPkg.dependencies,
169
- ...sourcePkg.dependencies
170
- });
171
-
172
- // Merge scripts
173
- const pkgMgr = getPackageManager();
174
- const runCmd = pkgMgr === "yarn" ? "yarn" : (pkgMgr === "pnpm" ? "pnpm" : "npm run");
175
-
176
- targetPkg.scripts = {
177
- ...targetPkg.scripts,
178
- "enderun:status": "agent-enderun status",
179
- "enderun:trace": "agent-enderun trace:new",
180
- "enderun:verify": "agent-enderun verify-contract",
181
- "enderun:check": "agent-enderun check",
182
- "enderun:test": "vitest run",
183
- "enderun:test:watch": "vitest",
184
- "enderun:build": `${runCmd} build --prefix framework-mcp`,
185
- };
186
-
187
- const sourceDevDeps = sourcePkg.devDependencies || {};
188
- targetPkg.devDependencies = sanitizeDeps({
189
- ...targetPkg.devDependencies,
190
- "agent-enderun": `^${sourcePkg.version}`,
191
- ...(sourceDevDeps["@modelcontextprotocol/sdk"] ? {"@modelcontextprotocol/sdk": sourceDevDeps["@modelcontextprotocol/sdk"]} : {}),
192
- ...(sourceDevDeps["zod"] ? {"zod": sourceDevDeps["zod"]} : {}),
193
- ...(sourceDevDeps["ts-morph"] ? {"ts-morph": sourceDevDeps["ts-morph"]} : {}),
194
- ...(sourceDevDeps["typescript"] ? {"typescript": sourceDevDeps["typescript"]} : {}),
195
- ...(sourceDevDeps["@types/node"] ? {"@types/node": sourceDevDeps["@types/node"]} : {}),
196
- ...(sourceDevDeps["tsx"] ? {"tsx": sourceDevDeps["tsx"]} : {}),
197
- ...(sourceDevDeps["vitest"] ? {"vitest": sourceDevDeps["vitest"]} : {}),
198
- "@pandacss/dev": sourceDevDeps["@pandacss/dev"] || "^1.11.1"
199
- });
200
-
201
- if ((targetPkg as PackageJson).peerDependencies) {
202
- (targetPkg as PackageJson).peerDependencies = sanitizeDeps((targetPkg as PackageJson).peerDependencies) as Record<string,string> | undefined;
203
- }
204
- if ((targetPkg as PackageJson).optionalDependencies) {
205
- (targetPkg as PackageJson).optionalDependencies = sanitizeDeps((targetPkg as PackageJson).optionalDependencies) as Record<string,string> | undefined;
206
- }
207
-
208
- // Ensure basic fields
209
- if (!targetPkg.name) targetPkg.name = path.basename(process.cwd());
210
- if (!targetPkg.version) targetPkg.version = "0.1.0";
211
- if (!targetPkg.type) targetPkg.type = "module";
212
- if (!targetPkg.workspaces) targetPkg.workspaces = ["apps/*", "framework-mcp"];
213
-
214
- // Add metadata
215
- targetPkg.enderun = {
216
- version: sourcePkg.version,
217
- initializedAt: new Date().toISOString(),
218
- };
219
-
220
- fs.writeFileSync(targetPath, JSON.stringify(targetPkg, null, 2));
221
- console.warn("✅ package.json updated with Enderun scripts and dependencies.");
222
- }
223
-
224
- function updateGitIgnore(targetPath, frameworkDir = ".gemini") {
225
- const IGNORE_LINES = [
226
- "# AI-Enderun",
227
- `${frameworkDir}/logs/*.json`,
228
- `${frameworkDir}/*.lock`,
229
- ".env",
230
- ".DS_Store"
231
- ];
232
-
233
- let content = "";
234
- if (fs.existsSync(targetPath)) {
235
- content = fs.readFileSync(targetPath, "utf8");
236
- }
237
-
238
- const lines = content.split("\n").map((l) => l.trim());
239
- let added = false;
240
-
241
- for (const line of IGNORE_LINES) {
242
- if (!lines.includes(line)) {
243
- content += (content.endsWith("\n") || content === "" ? "" : "\n") + line + "\n";
244
- added = true;
245
- }
246
- }
247
-
248
- if (added) {
249
- fs.writeFileSync(targetPath, content);
250
- console.warn("✅ .gitignore updated.");
251
- }
252
- }
253
-
254
- /**
255
- * Finds the Claude config file path (if it exists).
256
- */
257
- function findClaudeConfigPath() {
258
- const home = process.env.HOME || process.env.USERPROFILE;
259
- if (!home) return null;
260
-
261
- const possiblePaths = [
262
- path.join(home, ".config", "claude", "config.json"),
263
- path.join(home, ".claude", "config.json"),
264
- path.join(home, "Library", "Application Support", "Claude", "config.json"), // macOS Claude Desktop
265
- path.join(home, "Library", "Application Support", "Claude Code", "config.json"), // macOS Claude Code
266
- path.join(home, ".config", "Claude", "config.json"), // some Linux setups
267
- ];
268
-
269
- for (const p of possiblePaths) {
270
- if (fs.existsSync(p)) {
271
- return p;
272
- }
273
- }
274
- return null;
275
- }
276
-
277
- /**
278
- * Adds or updates an MCP server entry in Claude's config file.
279
- */
280
- function addMcpServerToClaude(configPath, serverName, serverConfig) {
281
- try {
282
- let config = { mcpServers: {} };
283
-
284
- if (fs.existsSync(configPath)) {
285
- const content = fs.readFileSync(configPath, "utf8");
286
- config = JSON.parse(content);
287
- }
288
-
289
- if (!config.mcpServers) {
290
- config.mcpServers = {};
291
- }
292
-
293
- config.mcpServers[serverName] = serverConfig;
294
-
295
- // Ensure parent directory exists
296
- const dir = path.dirname(configPath);
297
- if (!fs.existsSync(dir)) {
298
- fs.mkdirSync(dir, { recursive: true });
299
- }
300
-
301
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
302
- return true;
303
- } catch {
304
- return false;
305
- }
306
- }
307
-
308
- /**
309
- * Create initial PROJECT_MEMORY.md if missing.
310
- */
311
- function initializeMemory(memoryPath, targetBase) {
312
- if (fs.existsSync(memoryPath)) return;
313
-
314
- const traceId = generateULID();
315
- const date = new Date().toISOString().split("T")[0];
316
- const template = `# PROJECT MEMORY — Agent Enderun
317
-
318
- This file is the Single Source of Truth (SSOT) and the persistent memory of the project.
319
-
320
- ## CURRENT STATUS
321
-
322
- | Active Phase | Profile | Last Update | Active Trace ID | Blockers |
323
- | :----------- | :------ | :---------- | :-------------- | :------- |
324
- | PHASE_0 | Lightweight | ${date} | ${traceId} | NONE |
325
-
326
- ## PROJECT DEFINITION
327
-
328
- | Field | Value |
329
- | :--- | :--- |
330
- | Project Name | ${path.basename(process.cwd())} |
331
- | Platform | Not defined |
332
- | Frontend | React 19 + Vite + Panda CSS |
333
- | Backend | Node.js 20+ + Fastify |
334
- | DB | PostgreSQL |
335
-
336
- ## DOD STATUS
337
-
338
- | Phase | Status | Note |
339
- | :--- | :--- | :--- |
340
- | PHASE_0 | IN_PROGRESS | Initializing project structure |
341
- | PHASE_1 | PENDING | |
342
- | PHASE_2 | PENDING | |
343
- | PHASE_3 | PENDING | |
344
- | PHASE_4 | PENDING | |
345
-
346
- ## CRITICAL DECISIONS
347
-
348
- | Date | Decision | Rationale | Agent |
349
- | :--- | :--- | :--- | :--- |
350
- | ${date} | Project Initialized | Framework setup via CLI | @manager |
351
-
352
- ## DELIVERABLES
353
-
354
- | Module | Status | Agent | Date |
355
- | :--- | :--- | :--- | :--- |
356
-
357
- ## ACTIVE TASKS
358
-
359
- | Trace ID | Task | Agent | Priority | Status |
360
- | :--- | :--- | :--- | :--- | :--- |
361
- | ${traceId} | Framework setup and architecture alignment | @manager | P1 | IN_PROGRESS |
362
-
363
- ## HISTORY (Persistent Memory)
364
-
365
- ### ${date} — Framework Initialization
366
-
367
- - **Agent:** @manager
368
- - **Trace ID:** ${traceId}
369
- - **Action:** Initialized Agent Enderun framework and project structure.
370
- `;
371
-
372
- const finalTemplate = template.replace(/\{\{FRAMEWORK_DIR\}\}/g, targetBase);
373
- fs.writeFileSync(memoryPath, finalTemplate);
374
- console.warn(`✅ PROJECT_MEMORY.md initialized in ${targetBase}`);
375
- }
376
-
377
- // --- COMMANDS ---
378
-
379
- function getPackageManager() {
380
- const override = process.env.AGENT_ENDERUN_PACKAGE_MANAGER || process.env.AGENT_ENDERUN_PM || process.env.AI_ENDERUN_PACKAGE_MANAGER || process.env.AI_ENDERUN_PM;
381
- if (override) return override.toLowerCase();
382
-
383
- const userAgent = process.env.npm_config_user_agent || "";
384
- const npmExecPath = process.env.npm_execpath || "";
385
-
386
- if (userAgent.includes("pnpm") || npmExecPath.includes("pnpm")) return "pnpm";
387
- if (userAgent.includes("yarn") || npmExecPath.includes("yarn")) return "yarn";
388
-
389
- // Check for lockfiles in target directory
390
- if (fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml")) || fs.existsSync(path.join(process.cwd(), "pnpm-workspace.yaml"))) return "pnpm";
391
- if (fs.existsSync(path.join(process.cwd(), "yarn.lock"))) return "yarn";
392
-
393
- return "npm";
394
- }
395
-
396
- /**
397
- * Scaffold the framework into the target project.
398
- */
399
- async function initCommand(adapter = "gemini") {
400
- // Supports gemini, claude, and cursor adapters
401
- const selectedAdapter = ["gemini", "claude", "cursor"].includes(adapter.toLowerCase()) ? adapter.toLowerCase() : "gemini";
402
- const targetBase = `.${selectedAdapter}`;
403
- const targetFrameworkDir = path.join(targetDir, targetBase);
404
- // ... (CORE_FILES and DIRS_TO_CREATE remain the same)
405
- const CORE_FILES = [
406
- ".enderun", // source template folder (remapped to targetBase: .gemini)
407
-
408
- "mcp.json",
409
- ".env.example",
410
- "ENDERUN.md",
411
- "package.json",
412
- "tsconfig.json",
413
- "panda.config.ts",
414
- "vitest.config.ts",
415
- "framework-mcp",
416
- "docs",
417
- ];
418
-
419
- const DIRS_TO_CREATE = [
420
- targetBase,
421
- `${targetBase}/agents`,
422
- `${targetBase}/agents/schema`,
423
- `${targetBase}/skills`,
424
- `${targetBase}/knowledge`,
425
- `${targetBase}/benchmarks`,
426
- `${targetBase}/monitoring`,
427
- `${targetBase}/logs`,
428
- `${targetBase}/messages`,
429
- `${targetBase}/memory-graph`,
430
- `${targetBase}/memory-graph/agent-contexts`,
431
- `${targetBase}/queue`,
432
- `${targetBase}/queue/pending`,
433
- `${targetBase}/queue/processing`,
434
- `${targetBase}/queue/completed`,
435
- `${targetBase}/queue/failed`,
436
- `${targetBase}/queue/pipelines`,
437
- "apps/web",
438
- "apps/backend",
439
- "docs",
440
- "framework-mcp",
441
- "tests",
442
- ];
443
-
444
- console.warn(`🚀 Installing Agent Enderun (Adapter: ${selectedAdapter})...`);
445
-
446
- // Ensure target framework base exists
447
- if (!fs.existsSync(targetFrameworkDir)) {
448
- fs.mkdirSync(targetFrameworkDir, { recursive: true });
449
- }
450
-
451
- // Create subdirectories
452
- for (const dir of DIRS_TO_CREATE) {
453
- const fullPath = path.join(targetDir, dir);
454
- if (!fs.existsSync(fullPath)) {
455
- fs.mkdirSync(fullPath, { recursive: true });
456
- console.warn(`📂 Created directory: ${dir}`);
457
- }
458
- }
459
-
460
- // Process core files and the chosen adapter pointer file
461
- const filesToProcess = [...CORE_FILES, `${selectedAdapter}.md`].filter(f => fs.existsSync(path.join(sourceDir, f)) || CORE_FILES.includes(f) || f === `${selectedAdapter}.md`);
462
-
463
- // Ensure apps/backend structure for contract file
464
- ensureDir(path.join(targetDir, "apps/backend"));
465
- const initContractPath = path.join(targetDir, "apps/backend/contract.version.json");
466
- if (!fs.existsSync(initContractPath)) {
467
- writeJsonFile(initContractPath, {
468
- "contract_hash": "initial_hash_placeholder",
469
- "last_updated": new Date().toISOString()
470
- });
471
- }
472
-
473
- // Create MCP config for Gemini adapter
474
- const mcpConfigPath = path.join(targetFrameworkDir, "mcp_config.json");
475
- if (!fs.existsSync(mcpConfigPath)) {
476
- writeJsonFile(mcpConfigPath, {
477
- "mcpServers": {}
478
- });
479
- console.warn(`📄 Created ${targetBase}/mcp_config.json (MCP format)`);
480
- }
481
-
482
- // Detect target project scope
483
- let targetPkg = {};
484
- try {
485
- const targetPkgPath = path.join(targetDir, "package.json");
486
- if (fs.existsSync(targetPkgPath)) {
487
- targetPkg = JSON.parse(fs.readFileSync(targetPkgPath, "utf8"));
488
- }
489
- } catch {
490
- // ignore parse errors when reading existing package.json
491
- }
492
-
493
- const targetScope = (targetPkg && targetPkg.name)
494
- ? (targetPkg.name.startsWith("@") ? targetPkg.name.split("/")[0] : `@${targetPkg.name}`)
495
- : `@${path.basename(targetDir)}`;
496
-
497
-
498
- for (const item of filesToProcess) {
499
- const src = path.join(sourceDir, item);
500
- let dest = path.join(targetDir, item);
501
-
502
- // FORCED CLEANUP: Delete existing framework engine directory to ensure clean update
503
- if (["framework-mcp"].includes(item)) {
504
- if (fs.existsSync(dest)) {
505
- try {
506
- fs.rmSync(dest, { recursive: true, force: true });
507
- } catch {
508
- // ignore
509
- }
510
- }
511
- }
512
-
513
- // Remap core framework files to targetBase
514
- if (item === ".enderun" || item.startsWith(".enderun/")) {
515
- dest = path.join(targetDir, item.replace(".enderun", targetBase));
516
- }
517
- if (item === "ENDERUN.md") dest = path.join(targetFrameworkDir, "ENDERUN.md");
518
-
519
- // Adapter pointer check
520
- const isAdapterPointer = item === `${selectedAdapter}.md` || item === "gemini.md" || item === "claude.md" || item === "cursor.md";
521
- if (isAdapterPointer) {
522
- dest = path.join(targetDir, item);
523
- }
524
-
525
- if (fs.existsSync(src)) {
526
- if (fs.lstatSync(src).isDirectory()) {
527
- const skipFiles = (item === ".enderun") ? ["logs", "PROJECT_MEMORY.md", "PROJECT_MEMORY.lock"] : [];
528
- const nonDestructive = ["docs", ".enderun"].includes(item); // source name during copy
529
- copyDir(src, dest, new Set(skipFiles), nonDestructive, targetBase, targetScope);
530
- } else {
531
- if (item === "package.json") continue; // We merge it later
532
-
533
- if (fs.existsSync(dest) && !isAdapterPointer) {
534
- console.warn(`ℹ️ Skipping existing file: ${item}`);
535
- continue;
536
- }
537
-
538
- const ext = path.extname(item);
539
- const textExtensions = [".md", ".json", ".js", ".ts", ".txt", ""];
540
- if (textExtensions.includes(ext)) {
541
- let content = fs.readFileSync(src, "utf8");
542
- // Use selected adapter for placeholder replacement
543
- const currentAdapter = selectedAdapter;
544
-
545
- content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, targetBase);
546
- content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
547
- // Fallback: replace any residual hardcoded .enderun/ paths (with or without trailing slash)
548
- content = content.replace(/\.enderun\//g, `${targetBase}/`);
549
- content = content.replace(/`\.enderun`/g, `\`${targetBase}\``);
550
- content = content.replace(/\.enderun(?![\w/-])/g, targetBase); // bare .enderun not followed by word char or / -
551
-
552
- if (ext === ".json") {
553
- try {
554
- const json = JSON.parse(content);
555
- content = JSON.stringify(sanitizeJson(json, targetScope), null, 2);
556
- // Ensure variable replacement even inside JSON strings if any
557
- content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, targetBase);
558
- content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
559
- } catch {
560
- content = content.replace(/workspace:[^"'\s]*/g, "*");
561
- }
562
- }
563
-
564
- fs.writeFileSync(dest, content);
565
- } else {
566
- fs.copyFileSync(src, dest);
567
- }
568
- }
569
- console.warn(`✅ ${item} processed -> ${path.relative(targetDir, dest)}`);
570
- }
571
- }
572
-
573
- // Merge Package JSON
574
- mergePackageJson(path.join(targetDir, "package.json"), path.join(sourceDir, "package.json"));
575
- updateGitIgnore(path.join(targetDir, ".gitignore"), targetBase);
576
-
577
- const finalMemoryPath = path.join(targetDir, targetBase, "PROJECT_MEMORY.md");
578
- initializeMemory(finalMemoryPath, targetBase);
579
-
580
- deepCleanProtocols(targetDir, targetScope);
581
-
582
- // Create initial sample test
583
- const sampleTestPath = path.join(targetDir, "tests/initial.test.ts");
584
- if (!fs.existsSync(sampleTestPath)) {
585
- fs.writeFileSync(sampleTestPath, `import { describe, it, expect } from 'vitest';
586
-
587
- describe('Initial Setup', () => {
588
- it('should verify the testing environment is active', () => {
589
- expect(true).toBe(true);
590
- });
591
- });
592
- `);
593
- }
594
-
595
- if (!fs.existsSync(path.join(targetDir, ".git"))) {
596
- try {
597
- const { execSync } = await import("child_process");
598
- execSync("git init", { cwd: targetDir, stdio: "ignore" });
599
- console.warn("✅ Git repository initialized.");
600
- } catch { /* ignore */ }
601
- }
602
-
603
- console.warn(`\n🛠️ Running ${selectedAdapter} adapter configuration...`);
604
-
605
- console.warn(`♊ ${selectedAdapter.toUpperCase()} CLI: Setup complete in ${targetBase}/ folder.`);
606
- console.warn(` • Root entrypoint: ${selectedAdapter}.md`);
607
- console.warn(` • All framework files and governance live under ${targetBase}/`);
608
-
609
- const pkgMgr = getPackageManager();
610
- const installCmd = pkgMgr === "npm" ? "npm install" : `${pkgMgr} install`;
611
- const buildCmd = pkgMgr === "npm" ? "npm run enderun:build" : `${pkgMgr} run enderun:build`;
612
-
613
- console.warn(`\n✨ Framework scaffolded! (v${FRAMEWORK_VERSION})`);
614
-
615
- // Allow skipping install in test/CI environments
616
- if (process.env.ENDERUN_SKIP_INSTALL === "1") {
617
- console.warn("\n⏭️ Skipping install steps (ENDERUN_SKIP_INSTALL=1).");
618
- return;
619
- }
620
-
621
- try {
622
- const { execSync } = await import("child_process");
623
- console.warn(`\n📦 Step 1/3: Running '${installCmd}'...`);
624
- execSync(installCmd, { stdio: "inherit" });
625
- console.warn(`\n🏗️ Step 2/3: Running '${buildCmd}'...`);
626
- execSync(buildCmd, { stdio: "inherit" });
627
- console.warn(`\n🔍 Step 3/3: Running 'agent-enderun check'...`);
628
- checkCommand();
629
- console.warn("\n🚀 Agent Enderun is fully installed and verified!");
630
- } catch {
631
- console.error("\n❌ Automatic installation failed. Run manually:");
632
- console.warn(`👉 ${installCmd} && ${buildCmd}`);
633
- }
634
- }
635
-
636
- /**
637
- * Recursively scans a directory for package.json files and removes workspace: protocols.
638
- */
639
- function deepCleanProtocols(dir, targetScope = "") {
640
- if (!fs.existsSync(dir)) return;
641
-
642
- const entries = fs.readdirSync(dir, { withFileTypes: true });
643
- for (const entry of entries) {
644
- const fullPath = path.join(dir, entry.name);
645
- if (entry.isDirectory()) {
646
- if (entry.name === "node_modules" || entry.name === ".git") continue;
647
- deepCleanProtocols(fullPath, targetScope);
648
- } else if (entry.name === "package.json") {
649
- try {
650
- const content = fs.readFileSync(fullPath, "utf8");
651
- const json = JSON.parse(content);
652
- const cleaned = JSON.stringify(sanitizeJson(json, targetScope), null, 2);
653
- fs.writeFileSync(fullPath, cleaned);
654
- } catch {
655
- // ignore malformed json
656
- }
657
- } else if (entry.name === "package-lock.json") {
658
- fs.unlinkSync(fullPath);
659
- }
660
- }
661
- }
662
-
663
- /**
664
- * Check framework health and MCP status.
665
- */
666
- function checkCommand() {
667
- console.warn(`🔍 Checking Agent Enderun Health (v${FRAMEWORK_VERSION})...`);
668
- let issues = 0;
669
-
670
- const frameworkDir = getFrameworkDir();
671
-
672
- const constitutionPath = fs.existsSync(path.join(process.cwd(), "ENDERUN.md"))
673
- ? "ENDERUN.md"
674
- : path.join(frameworkDir, "ENDERUN.md");
675
-
676
- const checks = [
677
- { name: "Constitution (ENDERUN.md)", path: constitutionPath },
678
- { name: "Memory (PROJECT_MEMORY.md)", path: path.join(frameworkDir, "PROJECT_MEMORY.md") },
679
- { name: "Command Map (cli-commands.json)", path: path.join(frameworkDir, "cli-commands.json") },
680
- { name: "Framework Config (config.json)", path: path.join(frameworkDir, "config.json") },
681
- { name: "Agent Status (STATUS.md)", path: path.join(frameworkDir, "STATUS.md") },
682
- { name: "MCP Config (mcp.json)", path: "mcp.json" },
683
- { name: "Backend Contract", path: "apps/backend/contract.version.json" },
684
- { name: "MCP Server", path: "framework-mcp/package.json" },
685
- { name: "Panda CSS Config", path: "panda.config.ts" },
686
- { name: "Brain Dashboard", path: path.join(frameworkDir, "BRAIN_DASHBOARD.md") },
687
- { name: "Project Architectural Portal (docs/README.md)", path: "docs/README.md" },
688
- { name: "System Getting Started Guide (docs/getting-started.md)", path: "docs/getting-started.md" },
689
- { name: "Enterprise Approval Flows Protocol", path: "docs/architecture/approval-flows.md" },
690
- { name: "Professional Error Handling Pattern", path: "docs/backend/error-handling.md" },
691
- { name: "Atomic Component Standards", path: "docs/frontend/component-patterns.md" },
692
- ];
693
-
694
- for (const check of checks) {
695
- if (fs.existsSync(path.join(process.cwd(), check.path))) {
696
- console.warn(`✅ ${check.name} found.`);
697
- } else {
698
- console.warn(`❌ ${check.name} MISSING! (${check.path})`);
699
- issues++;
700
- }
701
- }
702
-
703
- // Dependency Check
704
- const mcpNodeModules = path.join(process.cwd(), "framework-mcp/node_modules");
705
- const rootNodeModules = path.join(process.cwd(), "node_modules");
706
- if (!fs.existsSync(mcpNodeModules) && !fs.existsSync(rootNodeModules)) {
707
- console.warn("❌ Dependencies MISSING! (Run 'npm install')");
708
- issues++;
709
- } else {
710
- console.warn("✅ Dependencies found.");
711
- }
712
-
713
- // MCP Build Check
714
- const mcpPath = path.join(process.cwd(), "framework-mcp/dist/index.js");
715
- if (!fs.existsSync(mcpPath)) {
716
- console.warn("❌ MCP Build MISSING! (Run 'npm run enderun:build')");
717
- issues++;
718
- } else {
719
- console.warn("✅ MCP Build found.");
720
- console.warn("⏳ Testing MCP Server syntax...");
721
- try {
722
- execSync(`node --check ${mcpPath}`, { stdio: "pipe" });
723
- console.warn("✅ MCP Server syntax valid.");
724
- } catch {
725
- // If --check fails on ESM, we might skip it or use a better check
726
- console.warn("⚠️ MCP Syntax check skipped (ESM/Environment).");
727
- }
728
- }
729
-
730
- if (issues === 0) {
731
- console.warn("\n🚀 All systems green! Agent Enderun is ready for orchestration.");
732
- } else {
733
- console.warn(`\n⚠️ Found ${issues} issues. Please fix them before starting.`);
734
- process.exit(1);
735
- }
736
- }
737
-
738
- function sanitizeJson(obj, targetScope = "") {
739
- if (typeof obj !== "object" || obj === null) return obj;
740
- if (Array.isArray(obj)) return obj.map(item => sanitizeJson(item, targetScope));
741
- const cleaned = {};
742
- for (const [key, value] of Object.entries(obj)) {
743
- let finalKey = key;
744
- let finalValue = value;
745
-
746
- // Remove UnoCSS related keys or values
747
- if (typeof key === "string" && key.includes("unocss")) continue;
748
- if (typeof value === "string" && value.includes("unocss")) {
749
- continue; // Skip this script/field
750
- }
751
-
752
- // Replace scope if needed
753
- if (targetScope) {
754
- const scopeName = targetScope.startsWith("@") ? targetScope.slice(1) : targetScope;
755
-
756
- // Handle scoped: @ai-enderun/foo -> @target/foo
757
- if (typeof key === "string" && key.startsWith("@ai-enderun/")) {
758
- finalKey = key.replace("@ai-enderun/", `${targetScope}/`);
759
- }
760
- if (typeof value === "string" && value.startsWith("@ai-enderun/")) {
761
- finalValue = value.replace("@ai-enderun/", `${targetScope}/`);
762
- }
763
-
764
- // Handle unscoped: ai-enderun-foo -> target-foo
765
- if (typeof key === "string" && key.startsWith("ai-enderun-")) {
766
- finalKey = key.replace("ai-enderun-", `${scopeName}-`);
767
- }
768
- if (typeof value === "string" && value.startsWith("ai-enderun-")) {
769
- finalValue = value.replace("ai-enderun-", `${scopeName}-`);
770
- }
771
-
772
- // Handle agent-enderun -> target (ONLY for the package name)
773
- if (key === "name" && value === "agent-enderun") {
774
- finalValue = scopeName;
775
- }
776
-
777
- // Preserve agent-enderun in dependencies and bin
778
- // (No action needed as finalKey/finalValue default to original)
779
-
780
- if (typeof value === "string" && value.startsWith("workspace:")) {
781
- finalValue = "*";
782
- }
783
- } else if (typeof value === "string" && value.startsWith("workspace:")) {
784
- finalValue = "*";
785
- }
786
-
787
- cleaned[finalKey] = (typeof finalValue === "object") ? sanitizeJson(finalValue, targetScope) : finalValue;
788
- }
789
- return cleaned;
790
- }
791
-
792
- function copyDir(src, dest, skipSet = new Set(), nonDestructive = false, frameworkDir = ".gemini", targetScope = "") {
793
- const DEFAULT_SKIP = new Set(["node_modules", ".git", ".DS_Store", "package-lock.json"]);
794
- const actualSkip = new Set([...DEFAULT_SKIP, ...skipSet]);
795
-
796
- if (!fs.existsSync(dest)) {
797
- fs.mkdirSync(dest, { recursive: true });
798
- }
799
-
800
- fs.readdirSync(src, { withFileTypes: true }).forEach(entry => {
801
- if (actualSkip.has(entry.name)) return;
802
-
803
- const srcPath = path.join(src, entry.name);
804
- const destPath = path.join(dest, entry.name);
805
-
806
- if (entry.isDirectory()) {
807
- copyDir(srcPath, destPath, skipSet, nonDestructive, frameworkDir, targetScope);
808
- } else {
809
- if (nonDestructive && fs.existsSync(destPath)) {
810
- return;
811
- }
812
-
813
- const ext = path.extname(entry.name);
814
- const textExtensions = [".md", ".json", ".js", ".ts", ".txt", ""];
815
-
816
- if (textExtensions.includes(ext)) {
817
- let content = fs.readFileSync(srcPath, "utf8");
818
- content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
819
- // Also replace any residual hardcoded .enderun/ paths left in source files (with or without trailing slash)
820
- content = content.replace(/\.enderun\//g, `${frameworkDir}/`);
821
- content = content.replace(/`\.enderun`/g, `\`${frameworkDir}\``);
822
- content = content.replace(/\.enderun(?![\w/-])/g, frameworkDir);
823
-
824
- // Sanitize workspace: protocol
825
- if (ext === ".json") {
826
- try {
827
- const json = JSON.parse(content);
828
- content = JSON.stringify(sanitizeJson(json, targetScope), null, 2);
829
- } catch {
830
- content = content.replace(/workspace:[^"'\s]*/g, "*");
831
- }
832
- } else {
833
- content = content.replace(/workspace:[^"'\s]*/g, "*");
834
- }
835
-
836
- const frameworkBase = frameworkDir.startsWith(".") ? frameworkDir.slice(1) : frameworkDir;
837
- let currentAdapter = frameworkBase;
838
- if (entry.name.endsWith(".md") && ["gemini", "claude", "cursor", "codex"].some(a => entry.name.startsWith(a))) {
839
- currentAdapter = ["gemini", "claude", "cursor", "codex"].find(a => entry.name.startsWith(a));
840
- }
841
-
842
- content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
843
- content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
844
- // Fallback: replace any residual hardcoded .enderun/ paths (with or without trailing slash)
845
- content = content.replace(/\.enderun\//g, `${frameworkDir}/`);
846
- content = content.replace(/`\.enderun`/g, `\`${frameworkDir}\``);
847
- content = content.replace(/\.enderun(?![\w/-])/g, frameworkDir);
848
-
849
- fs.writeFileSync(destPath, content);
850
- } else {
851
- fs.copyFileSync(srcPath, destPath);
852
- }
853
- }
854
- });
855
- }
856
-
857
- /**
858
- * Print the current framework status.
859
- */
860
- function statusCommand() {
861
- const memoryPath = getMemoryPath();
862
- const frameworkDir = getFrameworkDir();
863
- if (!fs.existsSync(memoryPath)) {
864
- console.error(`❌ Error: ${frameworkDir}/PROJECT_MEMORY.md not found. Please run 'init' first.`);
865
- return;
866
- }
867
-
868
- const content = fs.readFileSync(memoryPath, "utf8");
869
- const statusMatch = content.match(/\| Active Phase \| Profile \| Last Update \| Active Trace ID \| Blockers \|\n\| :----------- \| :------ \| :---------- \| :-------------- \| :------- \|\n\| (.*?) \| (.*?) \| (.*?) \| (.*?) \| (.*?) \|/);
870
-
871
- console.warn("\n📊 --- PROJECT STATUS ---");
872
- if (statusMatch) {
873
- console.warn(`🔹 Phase: ${statusMatch[1].trim()}`);
874
- console.warn(`🧭 Profile: ${statusMatch[2].trim()}`);
875
- console.warn(`📅 Update: ${statusMatch[3].trim()}`);
876
- console.warn(`🆔 Trace ID: ${statusMatch[4].trim()}`);
877
- console.warn(`⛔ Blockers: ${statusMatch[5].trim()}`);
878
- }
879
-
880
- const tasksSection = content.match(/## ACTIVE TASKS\n\n([\s\S]*?)\n\n##/);
881
- if (tasksSection) {
882
- console.warn("\n📋 Active Tasks:");
883
- console.warn(tasksSection[1].trim());
884
- }
885
-
886
- console.warn("\n-----------------------\n");
887
- }
888
-
889
- /**
890
- * Generate a new Trace ID and add it to project memory.
891
- */
892
- function traceNewCommand(description, agent = "manager", priority = "P2") {
893
- const memoryPath = getMemoryPath();
894
- if (!fs.existsSync(memoryPath)) {
895
- console.error("❌ Error: PROJECT_MEMORY.md not found.");
896
- return;
897
- }
898
-
899
- const traceId = generateULID();
900
- const safeDescription = sanitizeTableCell(description);
901
- const safeAgent = normalizeAgentName(agent);
902
- const safePriority = normalizePriority(priority);
903
- const newTask = `| ${traceId} | ${safeDescription} | @${safeAgent} | ${safePriority} | IN_PROGRESS |`;
904
- const lockPath = `${memoryPath}.lock`;
905
-
906
- if (!acquireMemoryLock(lockPath)) {
907
- console.error("❌ Error: Memory lock timeout (5 retries).");
908
- return;
909
- }
910
-
911
- try {
912
- const content = fs.readFileSync(memoryPath, "utf8");
913
- const updated = insertTaskRow(content, newTask);
914
- if (!updated) {
915
- console.error("❌ Error: ACTIVE TASKS table not found, task could not be added.");
916
- return;
917
- }
918
-
919
- fs.writeFileSync(memoryPath, updated);
920
- console.warn(`\n✅ New Trace ID created: ${traceId}`);
921
- console.warn(`📝 Added to task list: ${description}\n`);
922
- return traceId;
923
- } finally {
924
- releaseMemoryLock(lockPath);
925
- }
926
- }
927
-
928
- /**
929
- * Verify the shared-types contract hash.
930
- */
931
- function verifyContractCommand() {
932
- const sharedDir = path.join(targetDir, "apps/backend/src/types");
933
- const contractPath = path.join(targetDir, "apps/backend/contract.version.json");
934
-
935
- if (!fs.existsSync(sharedDir) || !fs.existsSync(contractPath)) {
936
- console.error("❌ Error: Backend types or contract.version.json not found.");
937
- return;
938
- }
939
-
940
- const walk = (d) => fs.readdirSync(d, { withFileTypes: true }).flatMap((e) => {
941
- const p = path.join(d, e.name);
942
- return e.isDirectory() ? walk(p) : (p.endsWith(".ts") ? [p] : []);
943
- });
944
-
945
- const files = walk(sharedDir).sort();
946
- const h = crypto.createHash("sha256");
947
- for (const f of files) {
948
- h.update(path.relative(targetDir, f));
949
- h.update("\0");
950
- h.update(fs.readFileSync(f));
951
- h.update("\0");
952
- }
953
- const currentHash = h.digest("hex");
954
-
955
- try {
956
- const stored = JSON.parse(fs.readFileSync(contractPath, "utf8")).contract_hash;
957
- if (currentHash === stored) {
958
- console.warn("✅ Contract hash verified! (MATCH)");
959
- } else {
960
- console.error(`❌ HASH MISMATCH!\nExpected: ${stored}\nActual: ${currentHash}`);
961
- process.exit(1);
962
- }
963
- } catch {
964
- console.error("❌ Error reading contract.version.json");
965
- process.exit(1);
966
- }
967
- }
968
-
969
- function securityAuditCommand(targetPath) {
970
- console.warn(`🔍 Running Advanced Security Audit on: ${targetPath}...`);
971
- const scanRules = [
972
- { pattern: /sql`/, message: "Potential Raw SQL usage detected", severity: "HIGH" },
973
- { pattern: /(password|secret|api_?key)\s*[:=]\s*['"][^'"]+['"]/i, message: "Potential hardcoded secret detected", severity: "CRITICAL" },
974
- { pattern: /:\s*any(?!\w)/, message: "Usage of 'any' type detected", severity: "MEDIUM" },
975
- { pattern: /\.innerHTML\s*=/, message: "Unsafe innerHTML assignment detected", severity: "MEDIUM" },
976
- ];
977
- const issues = [];
978
- const files = collectFiles(path.join(targetDir, targetPath), [".ts", ".tsx", ".js", ".jsx"]);
979
- files.forEach(f => {
980
- const content = fs.readFileSync(f, "utf8");
981
- const lines = content.split("\n");
982
- lines.forEach((line, i) => {
983
- scanRules.forEach(rule => {
984
- if (rule.pattern.test(line)) {
985
- issues.push(`[${rule.severity}] ${rule.message} in ${path.relative(targetDir, f)}:${i+1}`);
986
- }
987
- });
988
- });
989
- });
990
- if (issues.length === 0) {
991
- console.warn("✅ No security issues detected.");
992
- } else {
993
- issues.forEach(issue => console.warn(`⚠️ ${issue}`));
994
- }
995
- }
996
-
997
- function complianceCheckCommand(targetPath) {
998
- console.warn(`📜 Checking Constitution Compliance: ${targetPath}...`);
999
- const violations = [];
1000
- const forbidden = ["@shadcn", "mui", "@chakra-ui", "tailwindcss"];
1001
-
1002
- // 1. package.json scan (Blind Spot 5)
1003
- const pkgJsonPath = path.join(targetDir, "package.json");
1004
- if (fs.existsSync(pkgJsonPath)) {
1005
- try {
1006
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
1007
- const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
1008
- forbidden.forEach(lib => {
1009
- if (allDeps[lib] || Object.keys(allDeps).some(d => d.toLowerCase().includes(lib.toLowerCase()))) {
1010
- violations.push(`package.json: Forbidden library dependency '${lib}' found in package.json.`);
1011
- }
1012
- });
1013
- } catch {
1014
- // ignore
1015
- }
1016
- }
1017
-
1018
- // 2. Source files scan
1019
- const files = collectFiles(path.join(targetDir, targetPath), [".ts", ".tsx", ".js", ".jsx", ".md"]);
1020
- files.forEach(f => {
1021
- const isTypesFile = f.includes("brands.ts") || f.includes("brands.js") || f.includes("brands.tsx") || f.includes("src/types/brands");
1022
-
1023
- const content = fs.readFileSync(f, "utf8");
1024
- forbidden.forEach(lib => {
1025
- if (content.includes(lib)) violations.push(`${path.relative(targetDir, f)}: Forbidden library '${lib}' found.`);
1026
- });
1027
-
1028
- // Robust Branded Types validation (Blind Spot 4)
1029
- if ((f.endsWith(".ts") || f.endsWith(".tsx")) && !isTypesFile && content.includes("interface")) {
1030
- const plainIdMatches = content.match(/\b\w*(?:id|Id|ID)\??\s*:\s*(?:string|number)\b/g);
1031
- if (plainIdMatches) {
1032
- plainIdMatches.forEach(match => {
1033
- violations.push(`${path.relative(targetDir, f)}: Potential Branded Types violation (plain identifier '${match.trim()}' found inside interface).`);
1034
- });
1035
- }
1036
- }
1037
- });
1038
-
1039
- if (violations.length === 0) {
1040
- console.warn("✅ All systems compliant with ENDERUN.md.");
1041
- } else {
1042
- violations.forEach(v => console.warn(`❌ ${v}`));
1043
- }
1044
- }
1045
-
1046
- function gitCommitCommand(traceId) {
1047
- try {
1048
- const diff = cp.execSync("git diff --staged", { encoding: "utf8" });
1049
- if (!diff) {
1050
- console.warn("ℹ️ No staged changes found. Use 'git add' first.");
1051
- return;
1052
- }
1053
- const files = cp.execSync("git diff --staged --name-only", { encoding: "utf8" }).split("\n").filter(Boolean);
1054
- let type = "feat"; const scope = "code";
1055
- if (files.some(f => f.includes(".md"))) type = "docs";
1056
- else if (files.some(f => f.includes("apps/backend/src/types"))) type = "arch";
1057
- else if (files.some(f => f.includes("bin/cli.js"))) type = "fix";
1058
-
1059
- const summary = files.length === 1 ? `update ${path.basename(files[0])}` : `update ${files.length} files`;
1060
- console.warn(`\n### SUGGESTED COMMIT MESSAGE ###\n\n[${traceId}] ${type}(${scope}): ${summary}\n`);
1061
- } catch {
1062
- console.warn("❌ Git command failed.");
1063
- }
1064
- }
1065
-
1066
- function explorerGraphCommand(targetPath) {
1067
- console.warn(`🗺️ Generating Dependency Graph for: ${targetPath}...`);
1068
- const files = collectFiles(path.join(targetDir, targetPath), [".ts", ".tsx"]);
1069
- const edges = [];
1070
- files.forEach(f => {
1071
- const content = fs.readFileSync(f, "utf8");
1072
- const name = path.basename(f, path.extname(f));
1073
- const imports = content.match(/from\s+['"]\.\.?\/[^'"]+['"]/g) || [];
1074
- imports.forEach(imp => {
1075
- const target = path.basename(imp.split(/['"]/)[1]);
1076
- edges.push(`${name} --> ${target}`);
1077
- });
1078
- });
1079
- if (edges.length === 0) {
1080
- console.warn("ℹ️ No internal dependencies found.");
1081
- } else {
1082
- console.warn("\n```mermaid\ngraph TD\n" + Array.from(new Set(edges)).join("\n") + "\n```\n");
1083
- }
1084
- }
1085
-
1086
- function explorerAuditCommand(targetPath) {
1087
- console.warn(`🧠 Codebase Intelligence Scan: ${targetPath}...`);
1088
- const files = collectFiles(path.join(targetDir, targetPath), [".ts", ".tsx"]);
1089
- const complexity = [];
1090
- files.forEach(f => {
1091
- const content = fs.readFileSync(f, "utf8");
1092
- const lines = content.split("\n").length;
1093
- if (lines > 300) complexity.push(`${path.relative(targetDir, f)} (${lines} lines)`);
1094
- });
1095
- if (complexity.length > 0) {
1096
- console.warn("\n⚠️ Complexity Spikes:");
1097
- complexity.forEach(c => console.warn(`- ${c}`));
1098
- } else {
1099
- console.warn("✅ Codebase structure looks clean.");
1100
- }
1101
- }
1102
-
1103
- function collectFiles(dir, extensions) {
1104
- let results = [];
1105
- if (!fs.existsSync(dir)) return results;
1106
- const list = fs.readdirSync(dir);
1107
- list.forEach(file => {
1108
- file = path.join(dir, file);
1109
- const stat = fs.statSync(file);
1110
- if (stat && stat.isDirectory()) {
1111
- if (!file.includes("node_modules") && !file.includes(".git")) {
1112
- results = results.concat(collectFiles(file, extensions));
1113
- }
1114
- } else {
1115
- if (extensions.includes(path.extname(file))) {
1116
- results.push(file);
1117
- }
1118
- }
1119
- });
1120
- return results;
1121
- }
1122
-
1123
- function runScriptCommand(script, projectPath) {
1124
- const fullPath = path.join(targetDir, projectPath);
1125
- if (!fs.existsSync(fullPath)) {
1126
- console.warn(`❌ Project path not found: ${projectPath}`);
1127
- return;
1128
- }
1129
- console.warn(`🚀 Running 'npm run ${script}' in ${projectPath}...`);
1130
- try {
1131
- cp.spawnSync("npm", ["run", script], { cwd: fullPath, stdio: "inherit", shell: true });
1132
- } catch {
1133
- console.warn(`❌ Failed to run script: ${e.message}`);
1134
- }
1135
- }
1136
-
1137
- function gitSyncCommand() {
1138
- console.warn("🔄 Syncing with remote repository...");
1139
- try {
1140
- cp.execSync("git pull origin main --rebase", { stdio: "inherit" });
1141
- console.warn("✅ Successfully synced and rebased.");
1142
- } catch {
1143
- console.warn("❌ Sync failed. Please resolve conflicts manually.");
1144
- }
1145
- }
1146
-
1147
- function logAgentActionCommand(data) {
1148
- const frameworkDir = getFrameworkDir();
1149
- const logsDir = path.join(targetDir, frameworkDir, "logs");
1150
- if (!fs.existsSync(logsDir)) {
1151
- fs.mkdirSync(logsDir, { recursive: true });
1152
- }
1153
-
1154
- const agent = normalizeAgentName(data.agent);
1155
- const logPath = path.join(logsDir, `${agent}.json`);
1156
- let logs = [];
1157
-
1158
- if (fs.existsSync(logPath)) {
1159
- try {
1160
- logs = JSON.parse(fs.readFileSync(logPath, "utf8"));
1161
- if (!Array.isArray(logs)) logs = [];
1162
- } catch {
1163
- logs = [];
1164
- }
1165
- }
1166
-
1167
- const newEntry = {
1168
- timestamp: new Date().toISOString(),
1169
- ...data,
1170
- };
1171
-
1172
- logs.push(newEntry);
1173
- fs.writeFileSync(logPath, JSON.stringify(logs, null, 2));
1174
- console.warn(`✅ Logged action to ${frameworkDir}/logs/${agent}.json`);
1175
- }
1176
-
1177
- function updateProjectMemoryCommand(section, content) {
1178
- const memoryPath = getMemoryPath();
1179
- if (!fs.existsSync(memoryPath)) {
1180
- console.error("❌ Error: PROJECT_MEMORY.md not found.");
1181
- return;
1182
- }
1183
-
1184
- const lockPath = `${memoryPath}.lock`;
1185
- if (!acquireMemoryLock(lockPath)) {
1186
- console.error("❌ Error: Memory lock timeout.");
1187
- return;
1188
- }
1189
-
1190
- try {
1191
- let memoryContent = fs.readFileSync(memoryPath, "utf8");
1192
-
1193
- if (section === "HISTORY") {
1194
- const headers = ["## HISTORY (Persistent Memory)", "## HISTORY"];
1195
- let sectionIndex = -1;
1196
- for (const h of headers) {
1197
- sectionIndex = memoryContent.indexOf(h);
1198
- if (sectionIndex !== -1) {
1199
- break;
1200
- }
1201
- }
1202
-
1203
- if (sectionIndex === -1) {
1204
- console.error("❌ Error: HISTORY section not found.");
1205
- return;
1206
- }
1207
- const headerEnd = memoryContent.indexOf("\n", sectionIndex) + 1;
1208
- memoryContent = memoryContent.slice(0, headerEnd) + "\n" + content.trim() + "\n" + memoryContent.slice(headerEnd);
1209
- } else {
1210
- const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1211
- const sectionRegex = new RegExp(`## ${escaped}[\\s\\S]*?(?=\\n## |$)`, "m");
1212
- if (!sectionRegex.test(memoryContent)) {
1213
- console.error(`❌ Error: Section not found: ${section}`);
1214
- return;
1215
- }
1216
- memoryContent = memoryContent.replace(sectionRegex, `## ${section}\n\n${content.trim()}\n`);
1217
- }
1218
-
1219
- fs.writeFileSync(memoryPath, memoryContent);
1220
- console.warn(`✅ Section ${section} updated in PROJECT_MEMORY.md`);
1221
- } finally {
1222
- releaseMemoryLock(lockPath);
1223
- }
1224
- }
1225
-
1226
- function ensureDir(dirPath) {
1227
- if (!fs.existsSync(dirPath)) {
1228
- fs.mkdirSync(dirPath, { recursive: true });
1229
- }
1230
- }
1231
-
1232
- function writeTextFile(filePath, content) {
1233
- ensureDir(path.dirname(filePath));
1234
- fs.writeFileSync(filePath, content.endsWith("\n") ? content : `${content}\n`);
1235
- }
1236
-
1237
- function writeJsonFile(filePath, value) {
1238
- writeTextFile(filePath, JSON.stringify(value, null, 2));
1239
- }
1240
-
1241
- function slugifyName(value) {
1242
- const slug = String(value || "enderun-app")
1243
- .toLowerCase()
1244
- .replace(/[^a-z0-9]+/g, "-")
1245
- .replace(/^-+|-+$/g, "");
1246
- return slug || "enderun-app";
1247
- }
1248
-
1249
- function titleCase(value) {
1250
- return String(value || "Enderun App")
1251
- .replace(/[-_]+/g, " ")
1252
- .replace(/\s+/g, " ")
1253
- .trim()
1254
- .replace(/\b\w/g, (char) => char.toUpperCase());
1255
- }
1256
-
1257
- function inferAppSpec(description) {
1258
- const normalized = String(description || "").toLowerCase();
1259
- const isCrm = /\bcrm\b|customer|musteri|müşteri/.test(normalized);
1260
- const hasAuth = /auth|login|giris|giriş|signin|sign in|user|kullanici|kullanıcı|role|rol/.test(normalized);
1261
- const hasRoles = /role|rol|permission|yetki|admin/.test(normalized);
1262
- const hasReports = /report|rapor|analytics|dashboard|chart|metric/.test(normalized);
1263
- const appName = isCrm ? "crm-dashboard" : slugifyName(description).split("-").slice(0, 4).join("-");
1264
-
1265
- return {
1266
- rawDescription: description,
1267
- appName,
1268
- title: isCrm ? "CRM Dashboard" : titleCase(appName),
1269
- domain: isCrm ? "CRM" : "Business",
1270
- modules: {
1271
- auth: hasAuth || isCrm,
1272
- users: hasAuth || hasRoles || isCrm,
1273
- roles: hasRoles || isCrm,
1274
- reports: hasReports || isCrm,
1275
- },
1276
- };
1277
- }
1278
-
1279
- function buildSharedTypesContent(existingContent) {
1280
- const marker = "// --- Generated Application Contract ---";
1281
- const generated = [
1282
- marker,
1283
- 'export type RoleID = Brand<string, "RoleID">;',
1284
- 'export type ReportID = Brand<string, "ReportID">;',
1285
- 'export type CustomerID = Brand<string, "CustomerID">;',
1286
- "",
1287
- "export interface AuthSession {",
1288
- " user: User;",
1289
- " token: string;",
1290
- " expiresAt: string;",
1291
- "}",
1292
- "",
1293
- "export interface Role {",
1294
- " id: RoleID;",
1295
- " name: string;",
1296
- " permissions: string[];",
1297
- "}",
1298
- "",
1299
- "export interface Customer {",
1300
- " id: CustomerID;",
1301
- " name: string;",
1302
- " ownerId: UserID;",
1303
- " status: \"LEAD\" | \"ACTIVE\" | \"AT_RISK\";",
1304
- " annualValue: number;",
1305
- " createdAt: string;",
1306
- "}",
1307
- "",
1308
- "export interface ReportMetric {",
1309
- " id: ReportID;",
1310
- " label: string;",
1311
- " value: number;",
1312
- " trend: \"UP\" | \"DOWN\" | \"FLAT\";",
1313
- "}",
1314
- "",
1315
- "export interface DashboardSummary {",
1316
- " customers: Customer[];",
1317
- " users: User[];",
1318
- " roles: Role[];",
1319
- " reports: ReportMetric[];",
1320
- "}",
1321
- ].join("\n");
1322
-
1323
- if (existingContent.includes(marker)) return existingContent;
1324
- return `${existingContent.trim()}\n\n${generated}\n`;
1325
- }
1326
-
1327
- function updateContractHashFile() {
1328
- const sharedDir = path.join(targetDir, "apps/backend/src/types");
1329
- const contractPath = path.join(targetDir, "apps/backend/contract.version.json");
1330
- if (!fs.existsSync(sharedDir) || !fs.existsSync(contractPath)) return;
1331
-
1332
- const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
1333
- const fullPath = path.join(dir, entry.name);
1334
- return entry.isDirectory() ? walk(fullPath) : (entry.name.endsWith(".ts") ? [fullPath] : []);
1335
- });
1336
-
1337
- const hash = crypto.createHash("sha256");
1338
- for (const filePath of walk(sharedDir).sort()) {
1339
- hash.update(path.relative(targetDir, filePath));
1340
- hash.update("\0");
1341
- hash.update(fs.readFileSync(filePath));
1342
- hash.update("\0");
1343
- }
1344
-
1345
- const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
1346
- contract.contract_hash = hash.digest("hex");
1347
- contract.last_updated = new Date().toISOString();
1348
- fs.writeFileSync(contractPath, JSON.stringify(contract, null, 2));
1349
- }
1350
-
1351
- function createBaseTypeFiles(baseDir) {
1352
- const typesDir = path.join(baseDir, "types");
1353
- ensureDir(typesDir);
1354
-
1355
- writeTextFile(path.join(typesDir, "api.ts"), `import { TraceID } from "./brands.js";
1356
-
1357
- /**
1358
- * API Response Wrappers
1359
- */
1360
- export interface ApiResponse<T> {
1361
- data: T;
1362
- meta?: {
1363
- requestId: TraceID;
1364
- timestamp: string;
1365
- };
1366
- }
1367
-
1368
- export interface ApiError {
1369
- error: {
1370
- code: string;
1371
- message: string;
1372
- details?: unknown;
1373
- };
1374
- }
1375
- `);
1376
-
1377
- writeTextFile(path.join(typesDir, "brands.ts"), `/**
1378
- * Branded Type Utility
1379
- */
1380
- export type Brand<K, T> = K & { __brand: T };
1381
-
1382
- /**
1383
- * Entity IDs (Branded)
1384
- */
1385
- export type TraceID = Brand<string, "TraceID">;
1386
- export type AgentID = Brand<string, "AgentID">;
1387
- export type ProjectID = Brand<string, "ProjectID">;
1388
- export type UserID = Brand<string, "UserID">;
1389
- `);
1390
-
1391
- writeTextFile(path.join(typesDir, "constants.ts"), `import { TraceID } from "./brands.js";
1392
-
1393
- /**
1394
- * Project Phases
1395
- */
1396
- export const PROJECT_PHASES = ["PHASE_0", "PHASE_1", "PHASE_2", "PHASE_3", "PHASE_4"] as const;
1397
- export type ProjectPhase = (typeof PROJECT_PHASES)[number];
1398
-
1399
- /**
1400
- * Execution Profiles
1401
- */
1402
- export const EXECUTION_PROFILES = ["Lightweight", "Full"] as const;
1403
- export type ExecutionProfile = (typeof EXECUTION_PROFILES)[number];
1404
-
1405
- /**
1406
- * Task Priorities
1407
- */
1408
- export const TASK_PRIORITIES = ["P0", "P1", "P2", "P3"] as const;
1409
- export type TaskPriority = (typeof TASK_PRIORITIES)[number];
1410
-
1411
- /**
1412
- * Task Statuses
1413
- */
1414
- export const TASK_STATUSES = ["PENDING", "IN_PROGRESS", "BLOCKED", "COMPLETED", "FAILED"] as const;
1415
- export type TaskStatus = (typeof TASK_STATUSES)[number];
1416
-
1417
- /**
1418
- * Action Types & Status
1419
- */
1420
- export const ACTION_TYPES = ["CREATE", "MODIFY", "DELETE", "RESEARCH", "ORCHESTRATE"] as const;
1421
- export type ActionType = (typeof ACTION_TYPES)[number];
1422
-
1423
- export const ACTION_STATUSES = ["SUCCESS", "FAILURE", "WAITING"] as const;
1424
- export type ActionStatus = (typeof ACTION_STATUSES)[number];
1425
- `);
1426
-
1427
- writeTextFile(path.join(typesDir, "index.ts"), `/**
1428
- * Agent Enderun — App-Local Types (Modular)
1429
- */
1430
-
1431
- export * from "./brands.js";
1432
- export * from "./constants.js";
1433
- export * from "./models.js";
1434
- export * from "./api.js";
1435
- export * from "./logs.js";
1436
- `);
1437
-
1438
- writeTextFile(path.join(typesDir, "logs.ts"), `import { TraceID, AgentID } from "./brands.js";
1439
- import { ActionType, ActionStatus } from "./constants.js";
1440
-
1441
- /**
1442
- * Audit & Agent Logging Types
1443
- */
1444
- export interface AgentActionLog {
1445
- timestamp: string;
1446
- agent: AgentID;
1447
- action: ActionType;
1448
- requestId: TraceID | "—";
1449
- status: ActionStatus;
1450
- summary: string;
1451
- files?: string[];
1452
- details?: Record<string, unknown>;
1453
- }
1454
- `);
1455
-
1456
- writeTextFile(path.join(typesDir, "models.ts"), `import { UserID, TraceID, AgentID } from "./brands.js";
1457
- import { ProjectPhase, ExecutionProfile, TaskPriority, TaskStatus } from "./constants.js";
1458
-
1459
- /**
1460
- * Base Entity Fields
1461
- */
1462
- export interface BaseEntity {
1463
- id: string; // Usually ULID
1464
- createdAt: string;
1465
- updatedAt: string;
1466
- deletedAt?: string | null;
1467
- }
1468
-
1469
- /**
1470
- * Audit Log Model
1471
- */
1472
- export interface AuditLog extends BaseEntity {
1473
- entityName: string;
1474
- entityId: string;
1475
- action: "CREATE" | "UPDATE" | "DELETE" | "RESTORE";
1476
- userId: UserID;
1477
- previousState?: Record<string, unknown> | null;
1478
- newState?: Record<string, unknown> | null;
1479
- traceId: TraceID;
1480
- }
1481
-
1482
- /**
1483
- * User Model
1484
- */
1485
- export interface User extends BaseEntity {
1486
- id: UserID;
1487
- email: string;
1488
- fullName: string;
1489
- role: "ADMIN" | "DEVELOPER" | "VIEWER";
1490
- }
1491
-
1492
- export interface UserProfile extends User {
1493
- avatarUrl?: string;
1494
- bio?: string;
1495
- preferences: Record<string, unknown>;
1496
- }
1497
-
1498
- /**
1499
- * Project Status
1500
- */
1501
- export interface ProjectStatus {
1502
- phase: ProjectPhase;
1503
- profile: ExecutionProfile;
1504
- lastUpdate: string;
1505
- activeTraceId: TraceID | null;
1506
- blockers: string[];
1507
- }
1508
-
1509
- /**
1510
- * Task Model
1511
- */
1512
- export interface Task {
1513
- id: TraceID;
1514
- description: string;
1515
- agent: AgentID;
1516
- priority: TaskPriority;
1517
- status: TaskStatus;
1518
- createdAt: string;
1519
- updatedAt: string;
1520
- }
1521
- `);
1522
- console.warn(`✅ Base types created in ${path.relative(targetDir, typesDir)}`);
1523
- }
1524
-
1525
- function createBackendFiles(spec) {
1526
- createBaseTypeFiles(path.join(targetDir, "apps/backend/src"));
1527
- writeJsonFile(path.join(targetDir, "apps/backend/contract.version.json"), {
1528
- "contract_hash": "initial_hash_placeholder",
1529
- "last_updated": new Date().toISOString()
1530
- });
1531
- writeJsonFile(path.join(targetDir, "apps/backend/package.json"), {
1532
- name: "@agent-enderun/backend",
1533
- version: "0.1.0",
1534
- private: true,
1535
- type: "module",
1536
- scripts: {
1537
- dev: "tsx src/server.ts",
1538
- build: "tsc -p tsconfig.json",
1539
- start: "node dist/server.js",
1540
- test: "vitest run",
1541
- },
1542
- dependencies: {
1543
- "@fastify/cors": "^11.0.0",
1544
- fastify: "^5.0.0",
1545
- zod: "^3.24.2",
1546
- },
1547
- devDependencies: {
1548
- "@types/node": "^22.13.4",
1549
- tsx: "^4.19.4",
1550
- typescript: "^5.9.3",
1551
- vitest: "^3.0.5",
1552
- },
1553
- });
1554
-
1555
- writeJsonFile(path.join(targetDir, "apps/backend/tsconfig.json"), {
1556
- extends: "../../tsconfig.json",
1557
- compilerOptions: {
1558
- outDir: "dist",
1559
- rootDir: "src",
1560
- module: "NodeNext",
1561
- moduleResolution: "NodeNext",
1562
- target: "ES2022",
1563
- strict: true,
1564
- skipLibCheck: true,
1565
- },
1566
- include: ["src/**/*.ts"],
1567
- });
1568
-
1569
- writeTextFile(path.join(targetDir, "apps/backend/src/data.ts"), [
1570
- 'import type { Customer, DashboardSummary, ReportMetric, Role, User } from "./types/index.js";',
1571
- "",
1572
- 'const now = new Date().toISOString();',
1573
- "",
1574
- "export const roles: Role[] = [",
1575
- ' { id: "role_admin" as Role["id"], name: "Admin", permissions: ["users:manage", "reports:view", "customers:manage"] },',
1576
- ' { id: "role_manager" as Role["id"], name: "Manager", permissions: ["reports:view", "customers:manage"] },',
1577
- ' { id: "role_viewer" as Role["id"], name: "Viewer", permissions: ["reports:view"] },',
1578
- "];",
1579
- "",
1580
- "export const users: User[] = [",
1581
- ' { id: "user_1" as User["id"], email: "admin@example.com", fullName: "Admin User", role: "ADMIN", createdAt: now },',
1582
- ' { id: "user_2" as User["id"], email: "manager@example.com", fullName: "Sales Manager", role: "DEVELOPER", createdAt: now },',
1583
- "];",
1584
- "",
1585
- "export const customers: Customer[] = [",
1586
- ' { id: "customer_1" as Customer["id"], name: "Northwind", ownerId: users[1].id, status: "ACTIVE", annualValue: 125000, createdAt: now },',
1587
- ' { id: "customer_2" as Customer["id"], name: "Acme Corp", ownerId: users[1].id, status: "LEAD", annualValue: 82000, createdAt: now },',
1588
- ' { id: "customer_3" as Customer["id"], name: "Globex", ownerId: users[0].id, status: "AT_RISK", annualValue: 54000, createdAt: now },',
1589
- "];",
1590
- "",
1591
- "export const reports: ReportMetric[] = [",
1592
- ' { id: "report_pipeline" as ReportMetric["id"], label: "Pipeline", value: 261000, trend: "UP" },',
1593
- ' { id: "report_active_customers" as ReportMetric["id"], label: "Active Customers", value: 1, trend: "FLAT" },',
1594
- ' { id: "report_risk" as ReportMetric["id"], label: "At Risk", value: 1, trend: "DOWN" },',
1595
- "];",
1596
- "",
1597
- "export function getDashboardSummary(): DashboardSummary {",
1598
- " return { customers, users, roles, reports };",
1599
- "}",
1600
- ].join("\n"));
1601
-
1602
- writeTextFile(path.join(targetDir, "apps/backend/src/server.ts"), [
1603
- 'import Fastify from "fastify";',
1604
- 'import cors from "@fastify/cors";',
1605
- 'import { z } from "zod";',
1606
- 'import { customers, getDashboardSummary, reports, roles, users } from "./data.js";',
1607
- "",
1608
- "const app = Fastify({ logger: true });",
1609
- "await app.register(cors, { origin: true });",
1610
- "",
1611
- 'app.get("/health", async () => ({ ok: true, service: "agent-enderun-backend" }));',
1612
- 'app.get("/api/v1/dashboard", async () => ({ data: getDashboardSummary() }));',
1613
- 'app.get("/api/v1/users", async () => ({ data: users }));',
1614
- 'app.get("/api/v1/roles", async () => ({ data: roles }));',
1615
- 'app.get("/api/v1/customers", async () => ({ data: customers }));',
1616
- 'app.get("/api/v1/reports", async () => ({ data: reports }));',
1617
- "",
1618
- 'app.post("/api/v1/auth/login", async (request, reply) => {',
1619
- " const body = z.object({ email: z.string().email(), password: z.string().min(1) }).safeParse(request.body);",
1620
- " if (!body.success) return reply.code(400).send({ error: { code: \"VALIDATION_ERROR\", message: \"Invalid login payload\" } });",
1621
- "",
1622
- " const user = users.find((item) => item.email === body.data.email) || users[0];",
1623
- " return { data: { user, token: \"demo-token\", expiresAt: new Date(Date.now() + 3600000).toISOString() } };",
1624
- "});",
1625
- "",
1626
- "const port = Number(process.env.PORT || 4000);",
1627
- "await app.listen({ port, host: \"0.0.0.0\" });",
1628
- ].join("\n"));
1629
-
1630
- writeTextFile(path.join(targetDir, "apps/backend/README.md"), [
1631
- `# ${spec.title} Backend`,
1632
- "",
1633
- "Fastify API generated by Agent Enderun.",
1634
- "",
1635
- "## Commands",
1636
- "",
1637
- "- `npm run dev`",
1638
- "- `npm run build`",
1639
- "- `npm run test`",
1640
- ].join("\n"));
1641
- }
1642
-
1643
- function createWebFiles(spec) {
1644
- createBaseTypeFiles(path.join(targetDir, "apps/web/src"));
1645
- writeJsonFile(path.join(targetDir, "apps/web/package.json"), {
1646
- name: "@agent-enderun/web",
1647
- version: "0.1.0",
1648
- private: true,
1649
- type: "module",
1650
- scripts: {
1651
- dev: "vite --host 0.0.0.0",
1652
- build: "tsc -p tsconfig.json && vite build",
1653
- preview: "vite preview",
1654
- test: "vitest run",
1655
- },
1656
- dependencies: {
1657
- "@vitejs/plugin-react": "^5.0.0",
1658
- vite: "^7.0.0",
1659
- react: "^19.0.0",
1660
- "react-dom": "^19.0.0",
1661
- "lucide-react": "^0.468.0",
1662
- },
1663
- devDependencies: {
1664
- "@types/react": "^19.0.0",
1665
- "@types/react-dom": "^19.0.0",
1666
- typescript: "^5.9.3",
1667
- vitest: "^3.0.5",
1668
- },
1669
- });
1670
-
1671
- writeJsonFile(path.join(targetDir, "apps/web/tsconfig.json"), {
1672
- extends: "../../tsconfig.json",
1673
- compilerOptions: {
1674
- jsx: "react-jsx",
1675
- module: "NodeNext",
1676
- moduleResolution: "NodeNext",
1677
- target: "ES2022",
1678
- strict: true,
1679
- skipLibCheck: true,
1680
- },
1681
- include: ["src/**/*.ts", "src/**/*.tsx"],
1682
- });
1683
-
1684
- writeTextFile(path.join(targetDir, "apps/web/index.html"), [
1685
- '<div id="root"></div>',
1686
- '<script type="module" src="/src/main.tsx"></script>',
1687
- ].join("\n"));
1688
-
1689
- writeTextFile(path.join(targetDir, "apps/web/src/main.tsx"), [
1690
- 'import React from "react";',
1691
- 'import { createRoot } from "react-dom/client";',
1692
- 'import { App } from "./App.js";',
1693
- 'import "./styles.css";',
1694
- "",
1695
- 'createRoot(document.getElementById("root") as HTMLElement).render(',
1696
- " <React.StrictMode>",
1697
- " <App />",
1698
- " </React.StrictMode>,",
1699
- ");",
1700
- ].join("\n"));
1701
-
1702
- writeTextFile(path.join(targetDir, "apps/web/src/App.tsx"), [
1703
- 'import { BarChart3, ShieldCheck, UsersRound } from "lucide-react";',
1704
- "",
1705
- "const metrics = [",
1706
- ' { label: "Pipeline", value: "$261K", tone: "green" },',
1707
- ' { label: "Active customers", value: "18", tone: "blue" },',
1708
- ' { label: "At risk", value: "3", tone: "red" },',
1709
- "];",
1710
- "",
1711
- "const customers = [",
1712
- ' { name: "Northwind", status: "Active", owner: "Sales Manager", value: "$125K" },',
1713
- ' { name: "Acme Corp", status: "Lead", owner: "Sales Manager", value: "$82K" },',
1714
- ' { name: "Globex", status: "At risk", owner: "Admin User", value: "$54K" },',
1715
- "];",
1716
- "",
1717
- "export function App() {",
1718
- " return (",
1719
- ' <main className="shell">',
1720
- ' <aside className="sidebar" aria-label="Primary navigation">',
1721
- ' <div className="brand">AE</div>',
1722
- ' <nav>',
1723
- ' <a className="active" href="#dashboard"><BarChart3 size={18} /> Dashboard</a>',
1724
- ' <a href="#users"><UsersRound size={18} /> Users</a>',
1725
- ' <a href="#roles"><ShieldCheck size={18} /> Roles</a>',
1726
- " </nav>",
1727
- " </aside>",
1728
- "",
1729
- ' <section className="workspace">',
1730
- ' <header className="topbar">',
1731
- " <div>",
1732
- ` <p>${spec.domain}</p>`,
1733
- ` <h1>${spec.title}</h1>`,
1734
- " </div>",
1735
- ' <button type="button">New customer</button>',
1736
- " </header>",
1737
- "",
1738
- ' <section className="metrics" aria-label="Report metrics">',
1739
- " {metrics.map((metric) => (",
1740
- ' <article className={`metric ${metric.tone}`} key={metric.label}>',
1741
- " <span>{metric.label}</span>",
1742
- " <strong>{metric.value}</strong>",
1743
- " </article>",
1744
- " ))}",
1745
- " </section>",
1746
- "",
1747
- ' <section className="panel">',
1748
- " <div>",
1749
- " <h2>Customers</h2>",
1750
- " <p>Ownership, value and status at a glance.</p>",
1751
- " </div>",
1752
- ' <div className="table">',
1753
- " {customers.map((customer) => (",
1754
- ' <div className="row" key={customer.name}>',
1755
- " <strong>{customer.name}</strong>",
1756
- " <span>{customer.status}</span>",
1757
- " <span>{customer.owner}</span>",
1758
- " <b>{customer.value}</b>",
1759
- " </div>",
1760
- " ))}",
1761
- " </div>",
1762
- " </section>",
1763
- " </section>",
1764
- " </main>",
1765
- " );",
1766
- "}",
1767
- ].join("\n"));
1768
-
1769
- writeTextFile(path.join(targetDir, "apps/web/src/styles.css"), [
1770
- ":root {",
1771
- " color: #172026;",
1772
- " background: #f4f7f6;",
1773
- " font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;",
1774
- "}",
1775
- "",
1776
- "* { box-sizing: border-box; }",
1777
- "body { margin: 0; }",
1778
- "button { font: inherit; }",
1779
- "",
1780
- ".shell {",
1781
- " min-height: 100vh;",
1782
- " display: grid;",
1783
- " grid-template-columns: 240px 1fr;",
1784
- "}",
1785
- "",
1786
- ".sidebar {",
1787
- " background: #102022;",
1788
- " color: #eef6f2;",
1789
- " padding: 24px;",
1790
- "}",
1791
- "",
1792
- ".brand {",
1793
- " width: 40px;",
1794
- " height: 40px;",
1795
- " display: grid;",
1796
- " place-items: center;",
1797
- " background: #d8f36a;",
1798
- " color: #102022;",
1799
- " font-weight: 800;",
1800
- " border-radius: 8px;",
1801
- " margin-bottom: 32px;",
1802
- "}",
1803
- "",
1804
- "nav { display: grid; gap: 8px; }",
1805
- "nav a {",
1806
- " color: inherit;",
1807
- " text-decoration: none;",
1808
- " display: flex;",
1809
- " gap: 10px;",
1810
- " align-items: center;",
1811
- " padding: 10px 12px;",
1812
- " border-radius: 8px;",
1813
- "}",
1814
- "nav a.active, nav a:hover { background: rgba(255,255,255,0.12); }",
1815
- "",
1816
- ".workspace { padding: 32px; }",
1817
- ".topbar {",
1818
- " display: flex;",
1819
- " justify-content: space-between;",
1820
- " align-items: center;",
1821
- " gap: 24px;",
1822
- " margin-bottom: 24px;",
1823
- "}",
1824
- ".topbar p { margin: 0 0 4px; color: #58666a; font-size: 14px; }",
1825
- ".topbar h1 { margin: 0; font-size: 32px; letter-spacing: 0; }",
1826
- ".topbar button {",
1827
- " border: 0;",
1828
- " border-radius: 8px;",
1829
- " background: #176b5d;",
1830
- " color: white;",
1831
- " padding: 10px 14px;",
1832
- "}",
1833
- "",
1834
- ".metrics {",
1835
- " display: grid;",
1836
- " grid-template-columns: repeat(3, minmax(0, 1fr));",
1837
- " gap: 16px;",
1838
- " margin-bottom: 24px;",
1839
- "}",
1840
- ".metric, .panel {",
1841
- " background: white;",
1842
- " border: 1px solid #d9e3e0;",
1843
- " border-radius: 8px;",
1844
- "}",
1845
- ".metric { padding: 18px; }",
1846
- ".metric span { display: block; color: #58666a; margin-bottom: 8px; }",
1847
- ".metric strong { font-size: 28px; }",
1848
- ".metric.green { border-top: 4px solid #49a078; }",
1849
- ".metric.blue { border-top: 4px solid #3f7cac; }",
1850
- ".metric.red { border-top: 4px solid #d95d39; }",
1851
- "",
1852
- ".panel { padding: 20px; }",
1853
- ".panel h2 { margin: 0 0 4px; font-size: 20px; }",
1854
- ".panel p { margin: 0 0 18px; color: #58666a; }",
1855
- ".table { display: grid; gap: 8px; }",
1856
- ".row {",
1857
- " display: grid;",
1858
- " grid-template-columns: 1.4fr 0.8fr 1fr 0.6fr;",
1859
- " gap: 16px;",
1860
- " align-items: center;",
1861
- " padding: 12px;",
1862
- " border-radius: 8px;",
1863
- " background: #f7faf9;",
1864
- "}",
1865
- "",
1866
- "@media (max-width: 760px) {",
1867
- " .shell { grid-template-columns: 1fr; }",
1868
- " .sidebar { position: static; }",
1869
- " .metrics { grid-template-columns: 1fr; }",
1870
- " .topbar { align-items: flex-start; flex-direction: column; }",
1871
- " .row { grid-template-columns: 1fr; }",
1872
- "}",
1873
- ].join("\n"));
1874
-
1875
- writeTextFile(path.join(targetDir, "apps/web/README.md"), [
1876
- `# ${spec.title} Web`,
1877
- "",
1878
- "React dashboard generated by Agent Enderun.",
1879
- "",
1880
- "## Commands",
1881
- "",
1882
- "- `npm run dev`",
1883
- "- `npm run build`",
1884
- "- `npm run test`",
1885
- ].join("\n"));
1886
- }
1887
-
1888
- function updateProjectDocs(spec) {
1889
- const frameworkDir = getFrameworkDir();
1890
- const docsDir = path.join(targetDir, frameworkDir, "docs");
1891
- const apiDir = path.join(docsDir, "api");
1892
- ensureDir(apiDir);
1893
-
1894
- writeTextFile(path.join(docsDir, "project-docs.md"), [
1895
- `# ${spec.title} Requirements`,
1896
- "",
1897
- "## Request",
1898
- "",
1899
- spec.rawDescription,
1900
- "",
1901
- "## Generated Scope",
1902
- "",
1903
- `- Domain: ${spec.domain}`,
1904
- `- Auth: ${spec.modules.auth ? "yes" : "no"}`,
1905
- `- Users: ${spec.modules.users ? "yes" : "no"}`,
1906
- `- Roles: ${spec.modules.roles ? "yes" : "no"}`,
1907
- `- Reports: ${spec.modules.reports ? "yes" : "no"}`,
1908
- "",
1909
- "## Architecture",
1910
- "",
1911
- "- `apps/backend`: Fastify API",
1912
- "- `apps/web`: React dashboard",
1913
- "- `apps/backend/src/types`: Contract-first backend TypeScript types",
1914
- ].join("\n"));
1915
-
1916
- writeTextFile(path.join(apiDir, "README.md"), [
1917
- "# API Registry",
1918
- "",
1919
- "- `POST /api/v1/auth/login`",
1920
- "- `GET /api/v1/dashboard`",
1921
- "- `GET /api/v1/users`",
1922
- "- `GET /api/v1/roles`",
1923
- "- `GET /api/v1/customers`",
1924
- "- `GET /api/v1/reports`",
1925
- ].join("\n"));
1926
- }
1927
-
1928
- function updateMemoryForGeneratedApp(spec, traceId) {
1929
- const memoryPath = getMemoryPath();
1930
- if (!fs.existsSync(memoryPath)) return;
1931
-
1932
- const today = new Date().toISOString().split("T")[0];
1933
- const history = [
1934
- `### ${today} — Generated ${spec.title}`,
1935
- "",
1936
- "- **Agent:** @manager",
1937
- `- **Trace ID:** ${traceId}`,
1938
- "- **Action:** Created full-stack starter from natural language request.",
1939
- "- **Files:** apps/backend, apps/web, project docs",
1940
- ].join("\n");
1941
-
1942
- updateProjectMemoryCommand("HISTORY", history);
1943
- }
1944
-
1945
- async function collectCreateAppDescription(args) {
1946
- const initial = args.join(" ").trim();
1947
- if (initial) return initial;
1948
-
1949
- const readline = await import("readline/promises");
1950
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1951
- try {
1952
- const idea = await rl.question("What do you want to build? ");
1953
- const platform = await rl.question("Platform? (full-stack/web/backend) ");
1954
- const auth = await rl.question("Auth and roles? (yes/no) ");
1955
- const reports = await rl.question("Reports/dashboard? (yes/no) ");
1956
- return [idea, platform, auth.includes("y") ? "with auth and roles" : "", reports.includes("y") ? "with reports dashboard" : ""].filter(Boolean).join(" ");
1957
- } finally {
1958
- rl.close();
1959
- }
1960
- }
1961
-
1962
- async function createAppCommand(args) {
1963
- const description = await collectCreateAppDescription(args);
1964
- const spec = inferAppSpec(description);
1965
- const traceId = generateULID();
1966
-
1967
- ensureDir(path.join(targetDir, "apps/backend"));
1968
- ensureDir(path.join(targetDir, "apps/web"));
1969
-
1970
- createBackendFiles(spec);
1971
- createWebFiles(spec);
1972
- updateProjectDocs(spec);
1973
-
1974
- const backendTypesPath = path.join(targetDir, "apps/backend/src/types/index.ts");
1975
- if (fs.existsSync(backendTypesPath)) {
1976
- const existing = fs.readFileSync(backendTypesPath, "utf8");
1977
- fs.writeFileSync(backendTypesPath, buildSharedTypesContent(existing));
1978
- updateContractHashFile();
1979
- }
1980
-
1981
- const activeTraceId = traceNewCommand(`Generate ${spec.title} from natural language request`, "manager", "P1") || traceId;
1982
- updateMemoryForGeneratedApp(spec, activeTraceId);
1983
-
1984
- console.warn(`\n✅ Created ${spec.title}`);
1985
- console.warn("📁 Generated apps/backend and apps/web");
1986
- console.warn("📜 Updated project docs and app-local types contract");
1987
- console.warn("\nNext commands:");
1988
- console.warn(" npm install");
1989
- console.warn(" npm run enderun:build");
1990
- console.warn(" agent-enderun frontend:dev\n");
1991
- }
1992
-
1993
- // --- MAIN DISPATCHER ---
1994
-
1995
- async function main() {
1996
- const [command, ...args] = process.argv.slice(2);
1997
-
1998
- // Skip header for version and mcp commands to keep output clean
1999
- if (command !== "version" && command !== "-v" && command !== "--version" && command !== "mcp") {
2000
- console.warn("🤖 Agent Enderun CLI — Initializing...");
2001
- }
2002
-
2003
- switch (command) {
2004
- case "init":
2005
- await initCommand(args[0]);
2006
- break;
2007
- case "status":
2008
- statusCommand();
2009
- break;
2010
- case "trace:new":
2011
- if (!args[0]) {
2012
- console.error("❌ Usage: agent-enderun trace:new <description> [agent] [priority]");
2013
- } else {
2014
- traceNewCommand(args[0], args[1], args[2]);
2015
- }
2016
- break;
2017
- case "create-app":
2018
- case "new":
2019
- case "start":
2020
- case "build-app":
2021
- await createAppCommand(args);
2022
- break;
2023
- case "verify-contract":
2024
- verifyContractCommand();
2025
- break;
2026
- case "mcp": {
2027
- const mcpServerPath = path.join(sourceDir, "framework-mcp/dist/index.js");
2028
- if (fs.existsSync(mcpServerPath)) {
2029
- const { spawn } = await import("child_process");
2030
- // Use node to execute the built MCP server
2031
- const child = spawn("node", [mcpServerPath], { stdio: "inherit" });
2032
- child.on("exit", (code) => process.exit(code || 0));
2033
- } else {
2034
- console.error("❌ MCP Server not built. Run 'npm run enderun:build' first.");
2035
- process.exit(1);
2036
- }
2037
- break;
2038
- }
2039
- case "log_agent_action": {
2040
- // Handle both structured JSON and positional args
2041
- let data = {};
2042
- try {
2043
- if (args[0] && args[0].startsWith("{")) {
2044
- data = JSON.parse(args.join(" "));
2045
- } else {
2046
- data = {
2047
- agent: args[0],
2048
- action: args[1],
2049
- requestId: args[2],
2050
- status: args[3] || "SUCCESS",
2051
- summary: args[4] || "",
2052
- };
2053
- }
2054
- } catch {
2055
- console.error("❌ Error parsing arguments for log_agent_action");
2056
- process.exit(1);
2057
- }
2058
- logAgentActionCommand(data);
2059
- break;
2060
- }
2061
- case "update_project_memory": {
2062
- let section, content;
2063
- try {
2064
- if (args[0] && args[0].startsWith("{")) {
2065
- const data = JSON.parse(args.join(" "));
2066
- section = data.section;
2067
- content = data.content;
2068
- } else {
2069
- section = args[0];
2070
- content = args.slice(1).join(" ");
2071
- }
2072
- } catch {
2073
- console.error("❌ Error parsing arguments for update_project_memory");
2074
- process.exit(1);
2075
- }
2076
- updateProjectMemoryCommand(section, content);
2077
- break;
2078
- }
2079
- case "update_knowledge_base": {
2080
- const topic = args[0];
2081
- const content = args.slice(1).join(" ");
2082
- if (!topic || !content) {
2083
- console.error("❌ Usage: agent-enderun update_knowledge_base <topic> <content>");
2084
- process.exit(1);
2085
- }
2086
- const frameworkDir = getFrameworkDir();
2087
- const kbDir = path.join(targetDir, frameworkDir, "knowledge");
2088
- if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
2089
- const fileName = topic.replace(/[^a-z0-9]/gi, "_").toLowerCase() + ".md";
2090
- fs.writeFileSync(path.join(kbDir, fileName), content);
2091
- console.warn(`✅ Knowledge base updated: ${topic}`);
2092
- break;
2093
- }
2094
- case "search_knowledge_base": {
2095
- const query = args[0];
2096
- if (!query) {
2097
- console.error("❌ Usage: agent-enderun search_knowledge_base <query>");
2098
- process.exit(1);
2099
- }
2100
- const frameworkDir = getFrameworkDir();
2101
- const kbDir = path.join(targetDir, frameworkDir, "knowledge");
2102
- if (!fs.existsSync(kbDir)) {
2103
- console.warn("ℹ️ Knowledge base is empty.");
2104
- break;
2105
- }
2106
- const files = fs.readdirSync(kbDir).filter(f => f.endsWith(".md"));
2107
- let found = false;
2108
- for (const file of files) {
2109
- const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
2110
- if (content.toLowerCase().includes(query.toLowerCase()) || file.toLowerCase().includes(query.toLowerCase())) {
2111
- console.warn(`\n### ${file.replace(".md", "")}\n${content.slice(0, 300)}...`);
2112
- found = true;
2113
- }
2114
- }
2115
- if (!found) console.warn("ℹ️ No matching entries found.");
2116
- break;
2117
- }
2118
- case "check":
2119
- checkCommand();
2120
- break;
2121
- case "check:security":
2122
- securityAuditCommand(args[0] || ".");
2123
- break;
2124
- case "check:compliance":
2125
- complianceCheckCommand(args[0] || ".");
2126
- break;
2127
- case "git:commit":
2128
- gitCommitCommand(args[0] || "TRACE-ID-MISSING");
2129
- break;
2130
- case "explorer:graph":
2131
- explorerGraphCommand(args[0] || ".");
2132
- break;
2133
- case "explorer:audit":
2134
- explorerAuditCommand(args[0] || ".");
2135
- break;
2136
- case "frontend:dev":
2137
- runScriptCommand("dev", "apps/web");
2138
- break;
2139
- case "frontend:build":
2140
- runScriptCommand("build", "apps/web");
2141
- break;
2142
- case "mobile:dev":
2143
- runScriptCommand("start", "apps/mobile");
2144
- break;
2145
- case "git:sync":
2146
- gitSyncCommand();
2147
- break;
2148
- case "version":
2149
- case "-v":
2150
- case "--version":
2151
- console.warn(`v${FRAMEWORK_VERSION}`);
2152
- break;
2153
- default:
2154
- if (command && (command.includes(" ") || args.length > 0)) {
2155
- await createAppCommand([command, ...args]);
2156
- break;
2157
- }
2158
- console.warn(`
2159
- 🤖 Agent Enderun CLI (v${FRAMEWORK_VERSION})
2160
-
2161
- Available Commands:
2162
- init [adapter] Initialize (gemini only - installs to .enderun/)
2163
- create-app <idea> Generate a full-stack starter from natural language
2164
- check Full health check
2165
- check:security Run security audit scan
2166
- check:compliance Run constitution compliance check
2167
- status Show current phase and task status
2168
- trace:new <desc> Generate a new Trace ID
2169
- verify-contract Check shared types integrity
2170
- explorer:graph Generate dependency graph
2171
- explorer:audit Codebase intelligence scan
2172
- git:commit <id> Suggest semantic commit message
2173
- mcp Start the MCP server
2174
- version Show version information
2175
-
2176
- Supported adapter:
2177
- - gemini → Creates .enderun/ + gemini.md for Gemini CLI users
2178
-
2179
- Example:
2180
- agent-enderun init
2181
- agent-enderun init gemini
2182
- agent-enderun trace:new "Auth module design" backend P1
2183
- `);
2184
- break;
2185
- }
2186
- }
2187
-
2188
- main().catch(console.error);