agent-enderun 1.1.6 → 1.1.8

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 (254) hide show
  1. package/ENDERUN.md +12 -9
  2. package/README.md +12 -29
  3. package/bin/run-multi-test.js +77 -0
  4. package/dist/framework-mcp/src/tools/dashboard/start_dashboard.d.ts +5 -0
  5. package/dist/framework-mcp/src/tools/dashboard/start_dashboard.js +30 -0
  6. package/dist/framework-mcp/src/tools/dashboard/start_dashboard.js.map +1 -0
  7. package/dist/framework-mcp/src/tools/definitions.js +118 -0
  8. package/dist/framework-mcp/src/tools/definitions.js.map +1 -1
  9. package/dist/framework-mcp/src/tools/file_system/batch_surgical_edit.d.ts +5 -0
  10. package/dist/framework-mcp/src/tools/file_system/batch_surgical_edit.js +51 -0
  11. package/dist/framework-mcp/src/tools/file_system/batch_surgical_edit.js.map +1 -0
  12. package/dist/framework-mcp/src/tools/file_system/replace_text.js +8 -4
  13. package/dist/framework-mcp/src/tools/file_system/replace_text.js.map +1 -1
  14. package/dist/framework-mcp/src/tools/file_system/write_file.js +3 -0
  15. package/dist/framework-mcp/src/tools/file_system/write_file.js.map +1 -1
  16. package/dist/framework-mcp/src/tools/framework/audit_deps.d.ts +6 -0
  17. package/dist/framework-mcp/src/tools/framework/audit_deps.js +42 -0
  18. package/dist/framework-mcp/src/tools/framework/audit_deps.js.map +1 -0
  19. package/dist/framework-mcp/src/tools/framework/run_tests.d.ts +5 -0
  20. package/dist/framework-mcp/src/tools/framework/run_tests.js +26 -0
  21. package/dist/framework-mcp/src/tools/framework/run_tests.js.map +1 -0
  22. package/dist/framework-mcp/src/tools/index.js +24 -0
  23. package/dist/framework-mcp/src/tools/index.js.map +1 -1
  24. package/dist/framework-mcp/src/tools/memory/get_insights.d.ts +6 -0
  25. package/dist/framework-mcp/src/tools/memory/get_insights.js +35 -0
  26. package/dist/framework-mcp/src/tools/memory/get_insights.js.map +1 -0
  27. package/dist/framework-mcp/src/tools/memory/read_memory.d.ts +6 -0
  28. package/dist/framework-mcp/src/tools/memory/read_memory.js +29 -0
  29. package/dist/framework-mcp/src/tools/memory/read_memory.js.map +1 -0
  30. package/dist/framework-mcp/src/tools/messaging/send_message.js +1 -1
  31. package/dist/framework-mcp/src/tools/messaging/send_message.js.map +1 -1
  32. package/dist/framework-mcp/src/tools/observability/check_ports.d.ts +5 -0
  33. package/dist/framework-mcp/src/tools/observability/check_ports.js +27 -0
  34. package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -0
  35. package/dist/framework-mcp/src/tools/observability/get_health.d.ts +5 -0
  36. package/dist/framework-mcp/src/tools/observability/get_health.js +20 -0
  37. package/dist/framework-mcp/src/tools/observability/get_health.js.map +1 -0
  38. package/dist/framework-mcp/src/tools/search/get_gaps.d.ts +6 -0
  39. package/dist/framework-mcp/src/tools/search/get_gaps.js +49 -0
  40. package/dist/framework-mcp/src/tools/search/get_gaps.js.map +1 -0
  41. package/dist/framework-mcp/src/tools/search/get_map.d.ts +6 -0
  42. package/dist/framework-mcp/src/tools/search/get_map.js +44 -0
  43. package/dist/framework-mcp/src/tools/search/get_map.js.map +1 -0
  44. package/dist/framework-mcp/src/tools/search/grep_search.d.ts +5 -0
  45. package/dist/framework-mcp/src/tools/search/grep_search.js +60 -0
  46. package/dist/framework-mcp/src/tools/search/grep_search.js.map +1 -0
  47. package/dist/framework-mcp/src/tools/search/list_dir.d.ts +5 -0
  48. package/dist/framework-mcp/src/tools/search/list_dir.js +29 -0
  49. package/dist/framework-mcp/src/tools/search/list_dir.js.map +1 -0
  50. package/dist/framework-mcp/src/tools/types.d.ts +10 -5
  51. package/dist/framework-mcp/src/utils/compliance.d.ts +5 -0
  52. package/dist/framework-mcp/src/utils/compliance.js +30 -0
  53. package/dist/framework-mcp/src/utils/compliance.js.map +1 -0
  54. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +5 -5
  55. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
  56. package/dist/src/cli/adapters/types.d.ts +2 -2
  57. package/dist/src/cli/adapters.d.ts +3 -3
  58. package/dist/src/cli/adapters.js +53 -79
  59. package/dist/src/cli/adapters.js.map +1 -1
  60. package/dist/src/cli/commands/check.js +52 -6
  61. package/dist/src/cli/commands/check.js.map +1 -1
  62. package/dist/src/cli/commands/init.d.ts +5 -1
  63. package/dist/src/cli/commands/init.js +186 -172
  64. package/dist/src/cli/commands/init.js.map +1 -1
  65. package/dist/src/cli/commands/orchestrate.d.ts +3 -3
  66. package/dist/src/cli/commands/orchestrate.js +14 -8
  67. package/dist/src/cli/commands/orchestrate.js.map +1 -1
  68. package/dist/src/cli/commands/status.js +3 -2
  69. package/dist/src/cli/commands/status.js.map +1 -1
  70. package/dist/src/cli/index.js +4 -3
  71. package/dist/src/cli/index.js.map +1 -1
  72. package/dist/src/cli/shims.js +6 -6
  73. package/dist/src/cli/utils/app-backend.d.ts +2 -0
  74. package/dist/src/cli/utils/app-backend.js +236 -0
  75. package/dist/src/cli/utils/app-backend.js.map +1 -0
  76. package/dist/src/cli/utils/app-docs.d.ts +3 -0
  77. package/dist/src/cli/utils/app-docs.js +59 -0
  78. package/dist/src/cli/utils/app-docs.js.map +1 -0
  79. package/dist/src/cli/utils/app-frontend.d.ts +2 -0
  80. package/dist/src/cli/utils/app-frontend.js +248 -0
  81. package/dist/src/cli/utils/app-frontend.js.map +1 -0
  82. package/dist/src/cli/utils/app-inferrer.d.ts +14 -0
  83. package/dist/src/cli/utils/app-inferrer.js +39 -0
  84. package/dist/src/cli/utils/app-inferrer.js.map +1 -0
  85. package/dist/src/cli/utils/app-types.d.ts +3 -0
  86. package/dist/src/cli/utils/app-types.js +233 -0
  87. package/dist/src/cli/utils/app-types.js.map +1 -0
  88. package/dist/src/cli/utils/app.d.ts +5 -32
  89. package/dist/src/cli/utils/app.js +5 -794
  90. package/dist/src/cli/utils/app.js.map +1 -1
  91. package/dist/src/cli/utils/memory.js +8 -4
  92. package/dist/src/cli/utils/memory.js.map +1 -1
  93. package/dist/src/modules/adapters/antigravity-cli.js +3 -3
  94. package/dist/src/modules/adapters/antigravity-cli.js.map +1 -1
  95. package/dist/src/modules/adapters/claude.js +1 -1
  96. package/dist/src/modules/adapters/claude.js.map +1 -1
  97. package/dist/src/modules/adapters/codex.js +2 -2
  98. package/dist/src/modules/adapters/codex.js.map +1 -1
  99. package/dist/src/modules/adapters/cursor.js +1 -1
  100. package/dist/src/modules/adapters/cursor.js.map +1 -1
  101. package/dist/src/modules/adapters/definitions.d.ts +9 -0
  102. package/dist/src/modules/adapters/definitions.js +124 -0
  103. package/dist/src/modules/adapters/definitions.js.map +1 -0
  104. package/dist/src/modules/adapters/gemini.js +2 -2
  105. package/dist/src/modules/adapters/gemini.js.map +1 -1
  106. package/dist/src/modules/adapters/grok.js +1 -1
  107. package/dist/src/modules/adapters/grok.js.map +1 -1
  108. package/dist/src/modules/agents/definitions.js +26 -27
  109. package/dist/src/modules/agents/definitions.js.map +1 -1
  110. package/dist/src/shared/config.d.ts +3 -3
  111. package/dist/src/shared/logger.js +8 -5
  112. package/dist/src/shared/logger.js.map +1 -1
  113. package/dist/tests/adapter.test.d.ts +1 -0
  114. package/dist/tests/adapter.test.js +128 -0
  115. package/dist/tests/adapter.test.js.map +1 -0
  116. package/dist/tests/agents-definitions.test.d.ts +1 -0
  117. package/dist/tests/agents-definitions.test.js +54 -0
  118. package/dist/tests/agents-definitions.test.js.map +1 -0
  119. package/dist/tests/approve.test.d.ts +1 -0
  120. package/dist/tests/approve.test.js +88 -0
  121. package/dist/tests/approve.test.js.map +1 -0
  122. package/dist/tests/config.test.d.ts +1 -0
  123. package/dist/tests/config.test.js +58 -0
  124. package/dist/tests/config.test.js.map +1 -0
  125. package/dist/tests/container.runner.d.ts +1 -0
  126. package/dist/tests/container.runner.js +68 -0
  127. package/dist/tests/container.runner.js.map +1 -0
  128. package/dist/tests/container.test.d.ts +1 -0
  129. package/dist/tests/container.test.js +73 -0
  130. package/dist/tests/container.test.js.map +1 -0
  131. package/dist/tests/errors.test.d.ts +1 -0
  132. package/dist/tests/errors.test.js +64 -0
  133. package/dist/tests/errors.test.js.map +1 -0
  134. package/dist/tests/fs-utils.test.d.ts +1 -0
  135. package/dist/tests/fs-utils.test.js +101 -0
  136. package/dist/tests/fs-utils.test.js.map +1 -0
  137. package/dist/tests/logger.test.d.ts +1 -0
  138. package/dist/tests/logger.test.js +81 -0
  139. package/dist/tests/logger.test.js.map +1 -0
  140. package/dist/tests/memory-utils.test.d.ts +1 -0
  141. package/dist/tests/memory-utils.test.js +173 -0
  142. package/dist/tests/memory-utils.test.js.map +1 -0
  143. package/dist/tests/orchestrate.test.d.ts +1 -0
  144. package/dist/tests/orchestrate.test.js +131 -0
  145. package/dist/tests/orchestrate.test.js.map +1 -0
  146. package/dist/tests/skills-definitions.test.d.ts +1 -0
  147. package/dist/tests/skills-definitions.test.js +34 -0
  148. package/dist/tests/skills-definitions.test.js.map +1 -0
  149. package/dist/tests/status.test.d.ts +1 -0
  150. package/dist/tests/status.test.js +105 -0
  151. package/dist/tests/status.test.js.map +1 -0
  152. package/dist/tests/string.test.d.ts +1 -0
  153. package/dist/tests/string.test.js +89 -0
  154. package/dist/tests/string.test.js.map +1 -0
  155. package/dist/tests/time.test.d.ts +1 -0
  156. package/dist/tests/time.test.js +48 -0
  157. package/dist/tests/time.test.js.map +1 -0
  158. package/dist/tests/trace.test.d.ts +1 -0
  159. package/dist/tests/trace.test.js +61 -0
  160. package/dist/tests/trace.test.js.map +1 -0
  161. package/dist/vitest.config.js +1 -0
  162. package/dist/vitest.config.js.map +1 -1
  163. package/docs/getting-started.md +4 -0
  164. package/docs/user/corporate-governance.md +25 -0
  165. package/eslint.config.js +25 -3
  166. package/framework-mcp/dist/tools/dashboard/start_dashboard.js +29 -0
  167. package/framework-mcp/dist/tools/definitions.js +118 -0
  168. package/framework-mcp/dist/tools/file_system/batch_surgical_edit.js +50 -0
  169. package/framework-mcp/dist/tools/file_system/replace_text.js +8 -4
  170. package/framework-mcp/dist/tools/file_system/write_file.js +3 -0
  171. package/framework-mcp/dist/tools/framework/audit_deps.js +41 -0
  172. package/framework-mcp/dist/tools/framework/run_tests.js +25 -0
  173. package/framework-mcp/dist/tools/index.js +24 -0
  174. package/framework-mcp/dist/tools/memory/get_insights.js +34 -0
  175. package/framework-mcp/dist/tools/memory/read_memory.js +28 -0
  176. package/framework-mcp/dist/tools/messaging/send_message.js +1 -1
  177. package/framework-mcp/dist/tools/observability/check_ports.js +26 -0
  178. package/framework-mcp/dist/tools/observability/get_health.js +19 -0
  179. package/framework-mcp/dist/tools/search/get_gaps.js +48 -0
  180. package/framework-mcp/dist/tools/search/get_map.js +43 -0
  181. package/framework-mcp/dist/tools/search/grep_search.js +59 -0
  182. package/framework-mcp/dist/tools/search/list_dir.js +28 -0
  183. package/framework-mcp/dist/utils/compliance.js +29 -0
  184. package/framework-mcp/package.json +2 -2
  185. package/framework-mcp/src/tools/dashboard/start_dashboard.ts +33 -0
  186. package/framework-mcp/src/tools/definitions.ts +118 -0
  187. package/framework-mcp/src/tools/file_system/batch_surgical_edit.ts +70 -0
  188. package/framework-mcp/src/tools/file_system/replace_text.ts +10 -4
  189. package/framework-mcp/src/tools/file_system/write_file.ts +5 -0
  190. package/framework-mcp/src/tools/framework/audit_deps.ts +49 -0
  191. package/framework-mcp/src/tools/framework/run_tests.ts +28 -0
  192. package/framework-mcp/src/tools/index.ts +24 -0
  193. package/framework-mcp/src/tools/memory/get_insights.ts +41 -0
  194. package/framework-mcp/src/tools/memory/read_memory.ts +31 -0
  195. package/framework-mcp/src/tools/messaging/send_message.ts +1 -1
  196. package/framework-mcp/src/tools/observability/check_ports.ts +30 -0
  197. package/framework-mcp/src/tools/observability/get_health.ts +24 -0
  198. package/framework-mcp/src/tools/search/get_gaps.ts +54 -0
  199. package/framework-mcp/src/tools/search/get_map.ts +48 -0
  200. package/framework-mcp/src/tools/search/grep_search.ts +65 -0
  201. package/framework-mcp/src/tools/search/list_dir.ts +34 -0
  202. package/framework-mcp/src/tools/types.ts +11 -1
  203. package/framework-mcp/src/utils/compliance.ts +37 -0
  204. package/framework-mcp/tests/tools/messaging/send_message.test.ts +5 -5
  205. package/package.json +3 -3
  206. package/src/cli/adapters/types.ts +2 -2
  207. package/src/cli/adapters.ts +57 -86
  208. package/src/cli/commands/check.ts +52 -6
  209. package/src/cli/commands/init.ts +202 -193
  210. package/src/cli/commands/orchestrate.ts +14 -8
  211. package/src/cli/commands/status.ts +3 -2
  212. package/src/cli/index.ts +4 -3
  213. package/src/cli/shims.ts +6 -6
  214. package/src/cli/utils/app-backend.ts +249 -0
  215. package/src/cli/utils/app-docs.ts +65 -0
  216. package/src/cli/utils/app-frontend.ts +257 -0
  217. package/src/cli/utils/app-inferrer.ts +53 -0
  218. package/src/cli/utils/app-types.ts +243 -0
  219. package/src/cli/utils/app.ts +5 -849
  220. package/src/cli/utils/memory.ts +8 -4
  221. package/src/modules/adapters/definitions.ts +125 -0
  222. package/src/modules/agents/definitions.ts +26 -27
  223. package/src/shared/logger.ts +8 -5
  224. package/templates/prompts/bug-fix-recipe.md +20 -0
  225. package/templates/prompts/new-feature-recipe.md +19 -0
  226. package/templates/prompts/refactoring-recipe.md +21 -0
  227. package/templates/standards/architecture-standards.md +23 -0
  228. package/templates/standards/crud-governance.md +21 -0
  229. package/templates/standards/frontend-standards.md +38 -0
  230. package/templates/standards/i18n-standards.md +17 -0
  231. package/templates/standards/logging-and-secrets.md +29 -0
  232. package/templates/standards/mobile-standards.md +24 -0
  233. package/templates/standards/quality-standards.md +31 -0
  234. package/templates/standards/security-standards.md +21 -0
  235. package/templates/standards/tailwind-standards.md +20 -0
  236. package/templates/standards/testing-standards.md +31 -0
  237. package/src/modules/adapters/antigravity-cli.ts +0 -25
  238. package/src/modules/adapters/claude.ts +0 -36
  239. package/src/modules/adapters/codex.ts +0 -22
  240. package/src/modules/adapters/cursor.ts +0 -22
  241. package/src/modules/adapters/gemini.ts +0 -27
  242. package/src/modules/adapters/grok.ts +0 -20
  243. package/templates/architecture/agents-manifest.md +0 -79
  244. package/templates/architecture/approval-flows.md +0 -61
  245. package/templates/architecture/enterprise-architecture.md +0 -69
  246. package/templates/architecture/standards/crud-governance.md +0 -46
  247. package/templates/architecture/standards/data-fetching-patterns.md +0 -13
  248. package/templates/architecture/standards/design-system.md +0 -31
  249. package/templates/architecture/standards/documentation-ownership.md +0 -21
  250. package/templates/architecture/standards/logging.md +0 -7
  251. package/templates/architecture/standards/mobile-standards.md +0 -48
  252. package/templates/architecture/standards/tech-stack.md +0 -9
  253. package/templates/backend/error-handling.md +0 -74
  254. package/templates/frontend/component-patterns.md +0 -91
@@ -3,6 +3,7 @@ import path from "path";
3
3
  import chalk from "chalk";
4
4
 
5
5
  import { getMemoryPath, getFrameworkDir } from "../utils/memory.js";
6
+ import { logger } from "../../shared/logger.js";
6
7
 
7
8
  /**
8
9
  * Print the current framework status.
@@ -77,8 +78,8 @@ export function statusCommand() {
77
78
  }
78
79
  });
79
80
  }
80
- } catch {
81
- // Ignore status read errors
81
+ } catch (err) {
82
+ logger.debug("Failed to read army status file", err);
82
83
  }
83
84
  }
84
85
 
package/src/cli/index.ts CHANGED
@@ -62,9 +62,10 @@ async function main() {
62
62
  switch (command) {
63
63
  case "init": {
64
64
  const dryRun = args.includes("--dry-run");
65
- const isInteractive = args.includes("--interactive");
66
- const adapter = args.find(a => !a.startsWith("-") && a !== "init");
67
- await initCommand(adapter, dryRun, isInteractive);
65
+ const isUnified = args.includes("--unified");
66
+ const forceYes = args.includes("--yes") || args.includes("-y");
67
+ const adapter = args.find(a => !a.startsWith("-") && a !== "init") || "gemini";
68
+ await initCommand(adapter, { dryRun, unified: isUnified, yes: forceYes });
68
69
  break;
69
70
  }
70
71
 
package/src/cli/shims.ts CHANGED
@@ -4,7 +4,7 @@ export const SHIM_TEMPLATES: Record<string, string> = {
4
4
  You are the **Gemini Commander** (Strategic Decision Center).
5
5
 
6
6
  ## 🏛️ Directives
7
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
7
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
8
8
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
9
9
  - **Zero-Request Logging:** Write logs to \`{{FRAMEWORK_DIR}}/logs/\` at the end of every turn.
10
10
  `,
@@ -13,7 +13,7 @@ You are the **Gemini Commander** (Strategic Decision Center).
13
13
  You are the **Antigravity Specialist** (Preserve Coding Standards).
14
14
 
15
15
  ## 🏛️ Directives
16
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
16
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
17
17
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
18
18
  - **Standard Check:** Run health checks regularly to preserve framework integrity.
19
19
  `,
@@ -22,7 +22,7 @@ You are the **Antigravity Specialist** (Preserve Coding Standards).
22
22
  You are the **Claude Field Engineer** (Surgical Operations).
23
23
 
24
24
  ## 🏛️ Directives
25
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
25
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
26
26
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
27
27
  - **Surgical Precision:** Do not rewrite files. Execute surgical edits.
28
28
  `,
@@ -31,7 +31,7 @@ You are the **Claude Field Engineer** (Surgical Operations).
31
31
  You are the **Grok Explorer** (Experimental Protocols).
32
32
 
33
33
  ## 🏛️ Directives
34
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
34
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
35
35
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
36
36
  `,
37
37
  cursor: `# 🎖️ Enderun Core — CURSOR Strategy
@@ -39,7 +39,7 @@ You are the **Grok Explorer** (Experimental Protocols).
39
39
  You are the **Cursor Implementer** (IDE Integration).
40
40
 
41
41
  ## 🏛️ Directives
42
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
42
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
43
43
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
44
44
  `,
45
45
  codex: `# 🎖️ Enderun Core — COPILOT Strategy
@@ -47,7 +47,7 @@ You are the **Cursor Implementer** (IDE Integration).
47
47
  You are the **Copilot Implementer** (GitHub Copilot Integration).
48
48
 
49
49
  ## 🏛️ Directives
50
- - **Constitutional Supremacy:** Read \`ENDERUN.md\` first.
50
+ - **Constitutional Supremacy:** Read \`{{FRAMEWORK_DIR}}/ENDERUN.md\` first.
51
51
  - **State Persistence:** Read \`{{FRAMEWORK_DIR}}/memory/PROJECT_MEMORY.md\` to restore session memory.
52
52
  `
53
53
  };
@@ -0,0 +1,249 @@
1
+ import path from "path";
2
+ import type { AppSpec } from "./app-inferrer.js";
3
+ import { createBaseTypeFiles } from "./app-types.js";
4
+ import { ensureDir, writeJsonFile, writeTextFile } from "./fs.js";
5
+ import { getConfiguredPaths } from "./memory.js";
6
+ import { getDependencyVersions } from "./pkg.js";
7
+
8
+ const targetDir = process.cwd();
9
+
10
+ export function createBackendFiles(spec: AppSpec): void {
11
+ const pathsMap = getConfiguredPaths();
12
+ const backendDir = pathsMap.backend;
13
+ const { fastify, "@fastify/cors": fastifyCors, zod, "@types/node": typesNode, tsx, typescript, "vitest-backend": vitest } = getDependencyVersions();
14
+
15
+ createBaseTypeFiles(path.join(targetDir, backendDir, "src"));
16
+ writeJsonFile(path.join(targetDir, backendDir, "contract.version.json"), {
17
+ "contract_hash": "initial_hash_placeholder",
18
+ "last_updated": new Date().toISOString()
19
+ });
20
+ writeJsonFile(path.join(targetDir, backendDir, "package.json"), {
21
+ name: "@agent-enderun/backend",
22
+ version: "0.1.0",
23
+ private: true,
24
+ type: "module",
25
+ scripts: {
26
+ dev: "tsx src/server.ts",
27
+ build: "tsc -p tsconfig.json",
28
+ start: "node dist/server.js",
29
+ test: "vitest run",
30
+ },
31
+ dependencies: {
32
+ "@fastify/cors": fastifyCors,
33
+ fastify: fastify,
34
+ zod: zod,
35
+ },
36
+ devDependencies: {
37
+ "@types/node": typesNode,
38
+ tsx: tsx,
39
+ typescript: typescript,
40
+ vitest: vitest,
41
+ },
42
+ });
43
+
44
+ writeJsonFile(path.join(targetDir, backendDir, "tsconfig.json"), {
45
+ extends: "../../tsconfig.json",
46
+ compilerOptions: {
47
+ outDir: "dist",
48
+ rootDir: "src",
49
+ module: "NodeNext",
50
+ moduleResolution: "NodeNext",
51
+ target: "ES2022",
52
+ strict: true,
53
+ skipLibCheck: true,
54
+ },
55
+ include: ["src/**/*.ts"],
56
+ });
57
+
58
+ const srcDir = path.join(targetDir, backendDir, "src");
59
+ ensureDir(path.join(srcDir, "database"));
60
+ ensureDir(path.join(srcDir, "repositories"));
61
+ ensureDir(path.join(srcDir, "services"));
62
+
63
+ writeTextFile(path.join(srcDir, "database/memoryDb.ts"), [
64
+ "import type { Customer, ReportMetric, Role, User } from \"../types/index.js\";",
65
+ "",
66
+ "const now = new Date().toISOString();",
67
+ "",
68
+ "export const rolesDb: Role[] = [",
69
+ " { id: \"role_admin\" as Role[\"id\"], name: \"Admin\", permissions: [\"users:manage\", \"reports:view\", \"customers:manage\"] },",
70
+ " { id: \"role_manager\" as Role[\"id\"], name: \"Manager\", permissions: [\"reports:view\", \"customers:manage\"] },",
71
+ " { id: \"role_viewer\" as Role[\"id\"], name: \"Viewer\", permissions: [\"reports:view\"] },",
72
+ "];",
73
+ "",
74
+ "export const usersDb: User[] = [",
75
+ " { id: \"user_1\" as User[\"id\"], email: \"admin@example.com\", fullName: \"Admin User\", role: \"ADMIN\", createdAt: now, updatedAt: now },",
76
+ " { id: \"user_2\" as User[\"id\"], email: \"manager@example.com\", fullName: \"Sales Manager\", role: \"DEVELOPER\", createdAt: now, updatedAt: now },",
77
+ "];",
78
+ "",
79
+ "export const customersDb: Customer[] = [",
80
+ " { id: \"customer_1\" as Customer[\"id\"], name: \"Northwind\", ownerId: usersDb[1].id, status: \"ACTIVE\", annualValue: 125000, createdAt: now, updatedAt: now },",
81
+ " { id: \"customer_2\" as Customer[\"id\"], name: \"Acme Corp\", ownerId: usersDb[1].id, status: \"LEAD\", annualValue: 82000, createdAt: now, updatedAt: now },",
82
+ " { id: \"customer_3\" as Customer[\"id\"], name: \"Globex\", ownerId: usersDb[0].id, status: \"AT_RISK\", annualValue: 54000, createdAt: now, updatedAt: now },",
83
+ "];",
84
+ "",
85
+ "export const reportsDb: ReportMetric[] = [",
86
+ " { id: \"report_pipeline\" as ReportMetric[\"id\"], label: \"Pipeline\", value: 261000, trend: \"UP\" },",
87
+ " { id: \"report_active_customers\" as ReportMetric[\"id\"], label: \"Active Customers\", value: 1, trend: \"FLAT\" },",
88
+ " { id: \"report_risk\" as ReportMetric[\"id\"], label: \"At Risk\", value: 1, trend: \"DOWN\" },",
89
+ "];",
90
+ ].join("\n"));
91
+
92
+ writeTextFile(path.join(srcDir, "repositories/user.repository.ts"), [
93
+ "import { usersDb } from \"../database/memoryDb.js\";",
94
+ "import type { User } from \"../types/index.js\";",
95
+ "",
96
+ "export class UserRepository {",
97
+ " async findAll(): Promise<User[]> {",
98
+ " return usersDb.filter(u => !u.deletedAt);",
99
+ " }",
100
+ "",
101
+ " async findByEmail(email: string): Promise<User | null> {",
102
+ " return usersDb.find(u => u.email === email && !u.deletedAt) || null;",
103
+ " }",
104
+ "}",
105
+ ].join("\n"));
106
+
107
+ writeTextFile(path.join(srcDir, "repositories/customer.repository.ts"), [
108
+ "import { customersDb } from \"../database/memoryDb.js\";",
109
+ "import type { Customer } from \"../types/index.js\";",
110
+ "",
111
+ "export class CustomerRepository {",
112
+ " async findAll(): Promise<Customer[]> {",
113
+ " return customersDb.filter(c => !c.deletedAt);",
114
+ " }",
115
+ "}",
116
+ ].join("\n"));
117
+
118
+ writeTextFile(path.join(srcDir, "repositories/role.repository.ts"), [
119
+ "import { rolesDb } from \"../database/memoryDb.js\";",
120
+ "import type { Role } from \"../types/index.js\";",
121
+ "",
122
+ "export class RoleRepository {",
123
+ " async findAll(): Promise<Role[]> {",
124
+ " return rolesDb;",
125
+ " }",
126
+ "}",
127
+ ].join("\n"));
128
+
129
+ writeTextFile(path.join(srcDir, "repositories/report.repository.ts"), [
130
+ "import { reportsDb } from \"../database/memoryDb.js\";",
131
+ "import type { ReportMetric } from \"../types/index.js\";",
132
+ "",
133
+ "export class ReportRepository {",
134
+ " async findAll(): Promise<ReportMetric[]> {",
135
+ " return reportsDb;",
136
+ " }",
137
+ "}",
138
+ ].join("\n"));
139
+
140
+ writeTextFile(path.join(srcDir, "services/dashboard.service.ts"), [
141
+ "import { UserRepository } from \"../repositories/user.repository.js\";",
142
+ "import { CustomerRepository } from \"../repositories/customer.repository.js\";",
143
+ "import { RoleRepository } from \"../repositories/role.repository.js\";",
144
+ "import { ReportRepository } from \"../repositories/report.repository.js\";",
145
+ "import type { DashboardSummary } from \"../types/index.js\";",
146
+ "",
147
+ "export class DashboardService {",
148
+ " private userRepo = new UserRepository();",
149
+ " private customerRepo = new CustomerRepository();",
150
+ " private roleRepo = new RoleRepository();",
151
+ " private reportRepo = new ReportRepository();",
152
+ "",
153
+ " async getSummary(): Promise<DashboardSummary> {",
154
+ " const [users, customers, roles, reports] = await Promise.all([",
155
+ " this.userRepo.findAll(),",
156
+ " this.customerRepo.findAll(),",
157
+ " this.roleRepo.findAll(),",
158
+ " this.reportRepo.findAll(),",
159
+ " ]);",
160
+ "",
161
+ " return { users, customers, roles, reports };",
162
+ " }",
163
+ "}",
164
+ ].join("\n"));
165
+
166
+ writeTextFile(path.join(targetDir, backendDir, "src/server.ts"), [
167
+ "import Fastify from \"fastify\";",
168
+ "import cors from \"@fastify/cors\";",
169
+ "import { z } from \"zod\";",
170
+ "import { UserRepository } from \"./repositories/user.repository.js\";",
171
+ "import { CustomerRepository } from \"./repositories/customer.repository.js\";",
172
+ "import { RoleRepository } from \"./repositories/role.repository.js\";",
173
+ "import { ReportRepository } from \"./repositories/report.repository.js\";",
174
+ "import { DashboardService } from \"./services/dashboard.service.js\";",
175
+ "",
176
+ "const app = Fastify({ logger: true });",
177
+ "await app.register(cors, { origin: true });",
178
+ "",
179
+ "const userRepo = new UserRepository();",
180
+ "const customerRepo = new CustomerRepository();",
181
+ "const roleRepo = new RoleRepository();",
182
+ "const reportRepo = new ReportRepository();",
183
+ "const dashboardService = new DashboardService();",
184
+ "",
185
+ "app.get(\"/health\", async () => ({ ok: true, service: \"agent-enderun-backend\" }));",
186
+ "app.get(\"/api/v1/dashboard\", async () => {",
187
+ " const summary = await dashboardService.getSummary();",
188
+ " return { data: summary };",
189
+ "});",
190
+ "app.get(\"/api/v1/users\", async () => {",
191
+ " const users = await userRepo.findAll();",
192
+ " return { data: users };",
193
+ "});",
194
+ "app.get(\"/api/v1/roles\", async () => {",
195
+ " const roles = await roleRepo.findAll();",
196
+ " return { data: roles };",
197
+ "});",
198
+ "app.get(\"/api/v1/customers\", async () => {",
199
+ " const customers = await customerRepo.findAll();",
200
+ " return { data: customers };",
201
+ "});",
202
+ "app.get(\"/api/v1/reports\", async (request, reply) => {",
203
+ " interface QueryParams {",
204
+ " page?: number;",
205
+ " limit?: number;",
206
+ " }",
207
+ " const { page = 1, limit = 10 } = request.query as QueryParams;",
208
+ " const reports = await reportRepo.findAll();",
209
+ " const startIndex = (Number(page) - 1) * Number(limit);",
210
+ " const endIndex = startIndex + Number(limit);",
211
+ " const paginatedReports = reports.slice(startIndex, endIndex);",
212
+ " reply.send({",
213
+ " data: paginatedReports,",
214
+ " meta: {",
215
+ " total: reports.length,",
216
+ " page: Number(page),",
217
+ " limit: Number(limit),",
218
+ " },",
219
+ " });",
220
+ "});",
221
+ "",
222
+ "app.post(\"/api/v1/auth/login\", async (request, reply) => {",
223
+ " const body = z.object({ email: z.string().email(), password: z.string().min(1) }).safeParse(request.body);",
224
+ " if (!body.success) return reply.code(400).send({ error: { code: \"VALIDATION_ERROR\", message: \"Invalid login payload\" } });",
225
+ "",
226
+ " const user = await userRepo.findByEmail(body.data.email);",
227
+ " if (!user) {",
228
+ " const allUsers = await userRepo.findAll();",
229
+ " return { data: { user: allUsers[0], token: \"demo-token\", expiresAt: new Date(Date.now() + 3600000).toISOString() } };",
230
+ " }",
231
+ " return { data: { user, token: \"demo-token\", expiresAt: new Date(Date.now() + 3600000).toISOString() } };",
232
+ "});",
233
+ "",
234
+ "const port = Number(process.env.PORT || 4000);",
235
+ "await app.listen({ port, host: \"0.0.0.0\" });",
236
+ ].join("\n"));
237
+
238
+ writeTextFile(path.join(targetDir, backendDir, "README.md"), [
239
+ `# ${spec.title} Backend`,
240
+ "",
241
+ "Fastify API generated by Agent Enderun.",
242
+ "",
243
+ "## Commands",
244
+ "",
245
+ "- `npm run dev` strings",
246
+ "- `npm run build` strings",
247
+ "- `npm run test` strings",
248
+ ].join("\n"));
249
+ }
@@ -0,0 +1,65 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureDir, writeTextFile } from "./fs.js";
4
+ import { getConfiguredPaths, getMemoryPath, updateProjectMemory } from "./memory.js";
5
+ import type { AppSpec } from "./app-inferrer.js";
6
+
7
+ const targetDir = process.cwd();
8
+
9
+ export function updateProjectDocs(spec: AppSpec): void {
10
+ const pathsMap = getConfiguredPaths();
11
+ const docsDir = path.join(targetDir, pathsMap.docs);
12
+ const apiDir = path.join(docsDir, "api");
13
+ ensureDir(apiDir);
14
+
15
+ writeTextFile(path.join(docsDir, "project-docs.md"), [
16
+ `# ${spec.title} Requirements`,
17
+ "",
18
+ "## Request",
19
+ "",
20
+ spec.rawDescription,
21
+ "",
22
+ "## Generated Scope",
23
+ "",
24
+ `- Domain: ${spec.domain}`,
25
+ `- Auth: ${spec.modules.auth ? "yes" : "no"}`,
26
+ `- Users: ${spec.modules.users ? "yes" : "no"}`,
27
+ `- Roles: ${spec.modules.roles ? "yes" : "no"}`,
28
+ `- Reports: ${spec.modules.reports ? "yes" : "no"}`,
29
+ "",
30
+ "## Architecture",
31
+ "",
32
+ `- \`${pathsMap.backend}\`: Fastify API`,
33
+ `- \`${pathsMap.frontend}\`: React dashboard`,
34
+ `- \`${pathsMap.backend}/src/types\`: Contract-first backend TypeScript types`,
35
+ ].join("\n"));
36
+
37
+ writeTextFile(path.join(apiDir, "README.md"), [
38
+ "# API Registry",
39
+ "",
40
+ "- `POST /api/v1/auth/login`",
41
+ "- `GET /api/v1/dashboard`",
42
+ "- `GET /api/v1/users`",
43
+ "- `GET /api/v1/roles`",
44
+ "- `GET /api/v1/customers`",
45
+ "- `GET /api/v1/reports`",
46
+ ].join("\n"));
47
+ }
48
+
49
+ export function updateMemoryForGeneratedApp(spec: AppSpec, traceId: string): void {
50
+ const memoryPath = getMemoryPath();
51
+ if (!fs.existsSync(memoryPath)) return;
52
+
53
+ const pathsMap = getConfiguredPaths();
54
+ const today = new Date().toISOString().split("T")[0];
55
+ const history = [
56
+ `### ${today} — Generated ${spec.title}`,
57
+ "",
58
+ "- **Agent:** @manager",
59
+ `- **Trace ID:** ${traceId}`,
60
+ "- **Action:** Created full-stack starter from natural language request.",
61
+ `- **Files:** ${pathsMap.backend}, ${pathsMap.frontend}, project docs`,
62
+ ].join("\n");
63
+
64
+ updateProjectMemory("HISTORY", history);
65
+ }
@@ -0,0 +1,257 @@
1
+ import path from "path";
2
+ import type { AppSpec } from "./app-inferrer.js";
3
+ import { createBaseTypeFiles } from "./app-types.js";
4
+ import { writeJsonFile, writeTextFile } from "./fs.js";
5
+ import { getConfiguredPaths } from "./memory.js";
6
+ import { getDependencyVersions } from "./pkg.js";
7
+
8
+ const targetDir = process.cwd();
9
+
10
+ export function createWebFiles(spec: AppSpec): void {
11
+ const pathsMap = getConfiguredPaths();
12
+ const frontendDir = pathsMap.frontend;
13
+ const { "@vitejs/plugin-react": vitePluginReact, vite, react, "react-dom": reactDom, "lucide-react": lucideReact, "@types/react": typesReact, "@types/react-dom": typesReactDom, typescript, "vitest-frontend": vitest } = getDependencyVersions();
14
+
15
+ createBaseTypeFiles(path.join(targetDir, frontendDir, "src"));
16
+ writeJsonFile(path.join(targetDir, frontendDir, "package.json"), {
17
+ name: "@agent-enderun/web",
18
+ version: "0.1.0",
19
+ private: true,
20
+ type: "module",
21
+ scripts: {
22
+ dev: "vite --host 0.0.0.0",
23
+ build: "tsc -p tsconfig.json && vite build",
24
+ preview: "vite preview",
25
+ test: "vitest run",
26
+ },
27
+ dependencies: {
28
+ "@vitejs/plugin-react": vitePluginReact,
29
+ vite: vite,
30
+ react: react,
31
+ "react-dom": reactDom,
32
+ "lucide-react": lucideReact,
33
+ },
34
+ devDependencies: {
35
+ "@types/react": typesReact,
36
+ "@types/react-dom": typesReactDom,
37
+ typescript: typescript,
38
+ vitest: vitest,
39
+ },
40
+ });
41
+
42
+ writeJsonFile(path.join(targetDir, frontendDir, "tsconfig.json"), {
43
+ extends: "../../tsconfig.json",
44
+ compilerOptions: {
45
+ jsx: "react-jsx",
46
+ module: "NodeNext",
47
+ moduleResolution: "NodeNext",
48
+ target: "ES2022",
49
+ strict: true,
50
+ skipLibCheck: true,
51
+ },
52
+ include: ["src/**/*.ts", "src/**/*.tsx"],
53
+ });
54
+
55
+ writeTextFile(path.join(targetDir, frontendDir, "index.html"), [
56
+ "<div id=\"root\"></div>",
57
+ "<script type=\"module\" src=\"/src/main.tsx\"></script>",
58
+ ].join("\n"));
59
+
60
+ writeTextFile(path.join(targetDir, frontendDir, "src/main.tsx"), [
61
+ "import React from \"react\";",
62
+ "import { createRoot } from \"react-dom/client\";",
63
+ "import { App } from \"./App.js\";",
64
+ "import \"./styles.css\";",
65
+ "",
66
+ "createRoot(document.getElementById(\"root\") as HTMLElement).render(",
67
+ " <React.StrictMode>",
68
+ " <App />",
69
+ " </React.StrictMode>,",
70
+ ");",
71
+ ].join("\n"));
72
+
73
+ writeTextFile(path.join(targetDir, frontendDir, "src/App.tsx"), [
74
+ "import { BarChart3, ShieldCheck, UsersRound } from \"lucide-react\";",
75
+ "",
76
+ "const metrics = [",
77
+ " { label: \"Pipeline\", value: \"$261K\", tone: \"green\" },",
78
+ " { label: \"Active customers\", value: \"18\", tone: \"blue\" },",
79
+ " { label: \"At risk\", value: \"3\", tone: \"red\" },",
80
+ "];",
81
+ "",
82
+ "const customers = [",
83
+ " { name: \"Northwind\", status: \"Active\", owner: \"Sales Manager\", value: \"$125K\" },",
84
+ " { name: \"Acme Corp\", status: \"Lead\", owner: \"Sales Manager\", value: \"$82K\" },",
85
+ " { name: \"Globex\", status: \"At risk\", owner: \"Admin User\", value: \"$54K\" },",
86
+ "];",
87
+ "",
88
+ "export function App() {",
89
+ " return (",
90
+ " <main className=\"shell\">",
91
+ " <aside className=\"sidebar\" aria-label=\"Primary navigation\">",
92
+ " <div className=\"brand\">AE</div>",
93
+ " <nav>",
94
+ " <a className=\"active\" href=\"#dashboard\"><BarChart3 size={18} /> Dashboard</a>",
95
+ " <a href=\"#users\"><UsersRound size={18} /> Users</a>",
96
+ " <a href=\"#roles\"><ShieldCheck size={18} /> Roles</a>",
97
+ " </nav>",
98
+ " </aside>",
99
+ "",
100
+ " <section className=\"workspace\">",
101
+ " <header className=\"topbar\">",
102
+ " <div>",
103
+ " <p>{spec.domain}</p>",
104
+ " <h1>{spec.title}</h1>",
105
+ " </div>",
106
+ " <button type=\"button\">New customer</button>",
107
+ " </header>",
108
+ "",
109
+ " <section className=\"metrics\" aria-label=\"Report metrics\">",
110
+ " {metrics.map((metric) => (",
111
+ " <article className={`metric ${metric.tone}`} key={metric.label}>",
112
+ " <span>{metric.label}</span>",
113
+ " <strong>{metric.value}</strong>",
114
+ " </article>",
115
+ " ))}",
116
+ " </section>",
117
+ "",
118
+ " <section className=\"panel\">",
119
+ " <div>",
120
+ " <h2>Customers</h2>",
121
+ " <p>Ownership, value and status at a glance.</p>",
122
+ " </div>",
123
+ " <div className=\"table\">",
124
+ " {customers.map((customer) => (",
125
+ " <div className=\"row\" key={customer.name}>",
126
+ " <strong>{customer.name}</strong>",
127
+ " <span>{customer.status}</span>",
128
+ " <span>{customer.owner}</span>",
129
+ " <b>{customer.value}</b>",
130
+ " </div>",
131
+ " ))}",
132
+ " </div>",
133
+ " </section>",
134
+ " </section>",
135
+ " </main>",
136
+ " );",
137
+ "}",
138
+ ].join("\n"));
139
+
140
+ writeTextFile(path.join(targetDir, frontendDir, "src/styles.css"), [
141
+ ":root {",
142
+ " color: #172026;",
143
+ " background: #f4f7f6;",
144
+ " font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;",
145
+ "}",
146
+ "",
147
+ "* { box-sizing: border-box; }",
148
+ "body { margin: 0; }",
149
+ "button { font: inherit; }",
150
+ "",
151
+ ".shell {",
152
+ " min-height: 100vh;",
153
+ " display: grid;",
154
+ " grid-template-columns: 240px 1fr;",
155
+ "}",
156
+ "",
157
+ ".sidebar {",
158
+ " background: #102022;",
159
+ " color: #eef6f2;",
160
+ " padding: 24px;",
161
+ "}",
162
+ "",
163
+ ".brand {",
164
+ " width: 40px;",
165
+ " height: 40px;",
166
+ " display: grid;",
167
+ " place-items: center;",
168
+ " background: #d8f36a;",
169
+ " color: #102022;",
170
+ " font-weight: 800;",
171
+ " border-radius: 8px;",
172
+ " margin-bottom: 32px;",
173
+ "}",
174
+ "",
175
+ "nav { display: grid; gap: 8px; }",
176
+ "nav a {",
177
+ " color: inherit;",
178
+ " text-decoration: none;",
179
+ " display: flex;",
180
+ " gap: 10px;",
181
+ " align-items: center;",
182
+ " padding: 10px 12px;",
183
+ " border-radius: 8px;",
184
+ "}",
185
+ "nav a.active, nav a:hover { background: rgba(255,255,255,0.12); }",
186
+ "",
187
+ ".workspace { padding: 32px; }",
188
+ ".topbar {",
189
+ " display: flex;",
190
+ " justify-content: space-between;",
191
+ " align-items: center;",
192
+ " gap: 24px;",
193
+ " margin-bottom: 24px;",
194
+ "}",
195
+ ".topbar p { margin: 0 0 4px; color: #58666a; font-size: 14px; }",
196
+ ".topbar h1 { margin: 0; font-size: 32px; letter-spacing: 0; }",
197
+ ".topbar button {",
198
+ " border: 0;",
199
+ " border-radius: 8px;",
200
+ " background: #176b5d;",
201
+ " color: white;",
202
+ " padding: 10px 14px;",
203
+ "}",
204
+ "",
205
+ ".metrics {",
206
+ " display: grid;",
207
+ " grid-template-columns: repeat(3, minmax(0, 1fr));",
208
+ " gap: 16px;",
209
+ " margin-bottom: 24px;",
210
+ "}",
211
+ ".metric, .panel {",
212
+ " background: white;",
213
+ " border: 1px solid #d9e3e0;",
214
+ " border-radius: 8px;",
215
+ "}",
216
+ ".metric { padding: 18px; }",
217
+ ".metric span { display: block; color: #58666a; margin-bottom: 8px; }",
218
+ ".metric strong { font-size: 28px; }",
219
+ ".metric.green { border-top: 4px solid #49a078; }",
220
+ ".metric.blue { border-top: 4px solid #3f7cac; }",
221
+ ".metric.red { border-top: 4px solid #d95d39; }",
222
+ "",
223
+ ".panel { padding: 20px; }",
224
+ ".panel h2 { margin: 0 0 4px; font-size: 20px; }",
225
+ ".panel p { margin: 0 0 18px; color: #58666a; }",
226
+ ".table { display: grid; gap: 8px; }",
227
+ ".row {",
228
+ " display: grid;",
229
+ " grid-template-columns: 1.4fr 0.8fr 1fr 0.6fr;",
230
+ " gap: 16px;",
231
+ " align-items: center;",
232
+ " padding: 12px;",
233
+ " border-radius: 8px;",
234
+ " background: #f7faf9;",
235
+ "}",
236
+ "",
237
+ "@media (max-width: 760px) {",
238
+ " .shell { grid-template-columns: 1fr; }",
239
+ " .sidebar { position: static; }",
240
+ " .metrics { grid-template-columns: 1fr; }",
241
+ " .topbar { align-items: flex-start; flex-direction: column; }",
242
+ " .row { grid-template-columns: 1fr; }",
243
+ "}",
244
+ ].join("\n"));
245
+
246
+ writeTextFile(path.join(targetDir, frontendDir, "README.md"), [
247
+ `# ${spec.title} Web`,
248
+ "",
249
+ "React dashboard generated by Agent Enderun.",
250
+ "",
251
+ "## Commands",
252
+ "",
253
+ "- `npm run dev` strings",
254
+ "- `npm run build` strings",
255
+ "- `npm run test` strings",
256
+ ].join("\n"));
257
+ }