agent-enderun 0.9.4 → 1.0.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 (99) hide show
  1. package/.enderun/ENDERUN.md +1 -1
  2. package/.enderun/PROJECT_MEMORY.md +2 -21
  3. package/.enderun/STATUS.md +11 -7
  4. package/.enderun/agents/analyst.md +28 -0
  5. package/.enderun/agents/backend.md +33 -367
  6. package/.enderun/agents/database.md +27 -45
  7. package/.enderun/agents/devops.md +27 -187
  8. package/.enderun/agents/explorer.md +23 -124
  9. package/.enderun/agents/frontend.md +32 -345
  10. package/.enderun/agents/git.md +26 -110
  11. package/.enderun/agents/manager.md +30 -299
  12. package/.enderun/agents/mobile.md +28 -91
  13. package/.enderun/agents/native.md +27 -96
  14. package/.enderun/agents/quality.md +26 -114
  15. package/.enderun/agents/security.md +28 -0
  16. package/.enderun/config.json +1 -1
  17. package/.enderun/knowledge/analyst_reference_guide.md +31 -0
  18. package/.enderun/knowledge/backend_reference_guide.md +165 -0
  19. package/.enderun/knowledge/database_reference_guide.md +38 -0
  20. package/.enderun/knowledge/devops_reference_guide.md +43 -0
  21. package/.enderun/knowledge/explorer_reference_guide.md +34 -0
  22. package/.enderun/knowledge/frontend_reference_guide.md +64 -0
  23. package/.enderun/knowledge/git_reference_guide.md +51 -0
  24. package/.enderun/knowledge/manager_reference_guide.md +55 -0
  25. package/.enderun/knowledge/mobile_reference_guide.md +35 -0
  26. package/.enderun/knowledge/native_reference_guide.md +34 -0
  27. package/.enderun/knowledge/quality_reference_guide.md +42 -0
  28. package/.enderun/knowledge/security_reference_guide.md +35 -0
  29. package/.enderun/logs/manager.json +1 -17
  30. package/.enderun/memory-graph/agent-contexts/analyst.json +1 -0
  31. package/.enderun/memory-graph/agent-contexts/backend.json +1 -1
  32. package/.enderun/memory-graph/agent-contexts/devops.json +1 -1
  33. package/.enderun/memory-graph/agent-contexts/explorer.json +1 -1
  34. package/.enderun/memory-graph/agent-contexts/frontend.json +1 -1
  35. package/.enderun/memory-graph/agent-contexts/git.json +1 -1
  36. package/.enderun/memory-graph/agent-contexts/manager.json +1 -1
  37. package/.enderun/memory-graph/agent-contexts/mobile.json +1 -1
  38. package/.enderun/memory-graph/agent-contexts/native.json +1 -1
  39. package/.enderun/memory-graph/agent-contexts/quality.json +1 -1
  40. package/.enderun/memory-graph/agent-contexts/security.json +1 -0
  41. package/.enderun/memory-graph/shared-facts.json +1 -1
  42. package/README.md +95 -21
  43. package/agent.md +1 -1
  44. package/bin/cli.js +2 -1
  45. package/bin/init-check.js +1 -1
  46. package/bin/update-contract.js +1 -1
  47. package/claude.md +1 -1
  48. package/dist/.keep +1 -0
  49. package/docs/README.md +11 -1
  50. package/docs/getting-started.md +1 -1
  51. package/eslint.config.js +1 -1
  52. package/framework-mcp/dist/index.js +44 -242
  53. package/framework-mcp/dist/tools/definitions.js +109 -0
  54. package/framework-mcp/dist/tools/file_system/patch_file.js +18 -0
  55. package/framework-mcp/dist/tools/file_system/read_file.js +7 -0
  56. package/framework-mcp/dist/tools/file_system/replace_text.js +14 -0
  57. package/framework-mcp/dist/tools/file_system/write_file.js +9 -0
  58. package/framework-mcp/dist/tools/framework/get_status.js +5 -0
  59. package/framework-mcp/dist/tools/framework/orchestrate.js +5 -0
  60. package/framework-mcp/dist/tools/framework/update_contract_hash.js +5 -0
  61. package/framework-mcp/dist/tools/framework/update_memory.js +8 -0
  62. package/framework-mcp/dist/tools/index.js +25 -0
  63. package/framework-mcp/dist/tools/messaging/log_action.js +22 -0
  64. package/framework-mcp/dist/tools/messaging/send_message.js +21 -0
  65. package/framework-mcp/dist/tools/types.js +1 -0
  66. package/framework-mcp/dist/utils/cli.js +20 -0
  67. package/framework-mcp/dist/utils/security.js +35 -0
  68. package/framework-mcp/package.json +1 -1
  69. package/framework-mcp/src/declarations.d.ts +17 -3
  70. package/framework-mcp/src/index.ts +51 -279
  71. package/framework-mcp/src/tools/definitions.ts +111 -0
  72. package/framework-mcp/src/tools/file_system/patch_file.ts +23 -0
  73. package/framework-mcp/src/tools/file_system/read_file.ts +9 -0
  74. package/framework-mcp/src/tools/file_system/replace_text.ts +18 -0
  75. package/framework-mcp/src/tools/file_system/write_file.ts +11 -0
  76. package/framework-mcp/src/tools/framework/get_status.ts +7 -0
  77. package/framework-mcp/src/tools/framework/orchestrate.ts +7 -0
  78. package/framework-mcp/src/tools/framework/update_contract_hash.ts +7 -0
  79. package/framework-mcp/src/tools/framework/update_memory.ts +10 -0
  80. package/framework-mcp/src/tools/index.ts +31 -0
  81. package/framework-mcp/src/tools/messaging/log_action.ts +28 -0
  82. package/framework-mcp/src/tools/messaging/send_message.ts +26 -0
  83. package/framework-mcp/src/tools/types.ts +40 -0
  84. package/framework-mcp/src/utils/cli.ts +20 -0
  85. package/framework-mcp/src/utils/security.ts +41 -0
  86. package/gemini.md +7 -1
  87. package/grok.md +1 -1
  88. package/package.json +6 -5
  89. package/src/cli/adapters.ts +29 -8
  90. package/src/cli/commands/check.ts +7 -1
  91. package/src/cli/commands/init.ts +80 -27
  92. package/src/cli/commands/orchestrate.ts +98 -92
  93. package/src/cli/index.ts +5 -2
  94. package/src/cli/utils/app.ts +1 -1
  95. package/src/cli/utils/fs.ts +47 -13
  96. package/src/cli/utils/memory.ts +11 -4
  97. package/src/cli/utils/pkg.ts +5 -4
  98. package/docs/architecture/README.md +0 -9
  99. package/docs/user/README.md +0 -35
@@ -0,0 +1,26 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveFrameworkDir } from "../../utils/security.js";
4
+ import { ToolArgs, ToolResult } from "../types.js";
5
+
6
+ export function handleSendAgentMessage(projectRoot: string, args: ToolArgs): ToolResult {
7
+ const { to, category, content, traceId } = args as Required<Pick<ToolArgs, "to" | "category" | "content" | "traceId">>;
8
+ const frameworkDir = resolveFrameworkDir(projectRoot);
9
+ const agentName = to.replace("@", "");
10
+ const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
11
+
12
+ const message = {
13
+ timestamp: new Date().toISOString(),
14
+ from: "@mcp",
15
+ to,
16
+ category,
17
+ traceId,
18
+ content,
19
+ status: "PENDING"
20
+ };
21
+
22
+ fs.mkdirSync(path.dirname(messagePath), { recursive: true });
23
+ fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
24
+
25
+ return { content: [{ type: "text", text: `✅ Message sent to ${to}` }] };
26
+ }
@@ -0,0 +1,40 @@
1
+ export interface ToolDefinition {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: Record<string, { type: string; description?: string; enum?: string[] }>;
7
+ required?: string[];
8
+ };
9
+ }
10
+
11
+ export interface CallToolRequest {
12
+ params: {
13
+ name: string;
14
+ arguments?: ToolArgs;
15
+ };
16
+ }
17
+
18
+ export interface ToolArgs {
19
+ path?: string;
20
+ content?: string;
21
+ oldText?: string;
22
+ newText?: string;
23
+ startLine?: number;
24
+ endLine?: number;
25
+ newContent?: string;
26
+ section?: string;
27
+ to?: string;
28
+ category?: "ACTION" | "DELEGATION" | "INFO" | "ALERT";
29
+ traceId?: string;
30
+ agent?: string;
31
+ action?: string;
32
+ status?: "SUCCESS" | "FAILURE";
33
+ summary?: string;
34
+ findings?: string;
35
+ }
36
+
37
+ export interface ToolResult {
38
+ isError?: boolean;
39
+ content: Array<{ type: "text"; text: string }>;
40
+ }
@@ -0,0 +1,20 @@
1
+ import { execFileSync } from "child_process";
2
+
3
+ /**
4
+ * Executes a CLI command safely using execFileSync (no shell injection risk).
5
+ * Captures stderr gracefully to prevent stream crashes.
6
+ */
7
+ export function safeExec(command: string, args: string[], cwd: string): string {
8
+ try {
9
+ return execFileSync(command, args, {
10
+ cwd,
11
+ encoding: "utf8",
12
+ timeout: 30000,
13
+ maxBuffer: 1024 * 1024,
14
+ stdio: ["pipe", "pipe", "pipe"],
15
+ });
16
+ } catch (error: unknown) {
17
+ const err = error as { stderr?: string; message?: string };
18
+ throw new Error(err.stderr || err.message || "Command execution failed", { cause: error });
19
+ }
20
+ }
@@ -0,0 +1,41 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+
4
+ /**
5
+ * Validates and resolves a user-provided path to prevent path traversal attacks.
6
+ * Ensures the resolved path stays within the project root boundary.
7
+ */
8
+ export function safePath(projectRoot: string, userPath: string): string {
9
+ const resolved = path.resolve(projectRoot, userPath);
10
+ const normalizedRoot = path.resolve(projectRoot);
11
+
12
+ if (!resolved.startsWith(normalizedRoot + path.sep) && resolved !== normalizedRoot) {
13
+ throw new Error(`Access denied: path "${userPath}" escapes project root.`);
14
+ }
15
+
16
+ return resolved;
17
+ }
18
+
19
+ /**
20
+ * Resolves the active framework directory by scanning known candidates.
21
+ */
22
+ export function resolveFrameworkDir(projectRoot: string): string {
23
+ const candidates = [
24
+ ".gemini/antigravity",
25
+ ".gemini/antigravity-cli",
26
+ ".gemini",
27
+ ".claude",
28
+ ".grok",
29
+ ".agent",
30
+ ".enderun",
31
+ ];
32
+
33
+ for (const candidate of candidates) {
34
+ const candidatePath = path.join(projectRoot, candidate);
35
+ if (fs.existsSync(candidatePath)) {
36
+ return candidate;
37
+ }
38
+ }
39
+
40
+ return ".enderun";
41
+ }
package/gemini.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is the primary entry point when using Agent Enderun within the Gemini CLI ecosystem.
4
4
 
5
- The framework operates under a unified governance structure located in the `.enderun/` directory.
5
+ The framework operates under a unified governance structure located in the .enderun/ directory.
6
6
 
7
7
  ### Primary Constitution
8
8
  All governance rules, agent checklists, and project memory live here:
@@ -17,9 +17,15 @@ At the **start of every session**, before any work:
17
17
  2. **Load Identity:** Read the relevant agent definition under `.enderun/agents/{agent}.md` (default to `@manager`).
18
18
  3. **Internalize Knowledge:** Reference the `.enderun/knowledge/` base for architectural and coding standards.
19
19
 
20
+ ### 💾 Memory Tiers
21
+ - **Project Memory (Shared):** `.enderun/PROJECT_MEMORY.md` is the source of truth for task history, Trace IDs, and architectural decisions. **Update this at the end of every turn.**
22
+ - **Private Memory (User-Specific):** Use the system's temporary directory memory folder for personal workflows or user-specific notes that should not be committed to Git.
23
+ - **Project Instructions:** This `gemini.md` file contains the "Supreme Law" and coding standards.
24
+
20
25
  ### 🛡️ Core Mandates
21
26
  - **Surgical Edits:** Use `replace_text` or `patch_file` via MCP for precise changes.
22
27
  - **Traceability:** Every action must inherit the active Trace ID from memory.
23
28
  - **Contract First:** No implementation without verified types.
29
+ - **NEVER FORGET:** @manager MUST update `.enderun/PROJECT_MEMORY.md` at the end of EVERY session without exception. This is a non-negotiable process integrity rule.
24
30
 
25
31
  All paths and references inside the project must comply with the Supreme Constitution located at `.enderun/ENDERUN.md`.
package/grok.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is the primary entry point when using Agent Enderun within the Grok / X.ai ecosystem.
4
4
 
5
- The framework operates under a unified governance structure located in the `.enderun/` directory.
5
+ The framework operates under a unified governance structure located in the .enderun/ directory.
6
6
 
7
7
  ### Primary Constitution
8
8
  All governance rules, agent checklists, and project memory live here:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-enderun",
3
- "version": "0.9.4",
3
+ "version": "1.0.0",
4
4
  "description": "The Supreme AI Governance & Orchestration Framework for Enterprise Development",
5
5
  "author": "Yusuf BEKAR",
6
6
  "license": "MIT",
@@ -82,7 +82,8 @@
82
82
  "zod": "^3.24.2"
83
83
  },
84
84
  "enderun": {
85
- "version": "0.9.0",
86
- "initializedAt": "2026-05-29T13:21:18.516Z"
87
- }
88
- }
85
+ "version": "1.0.0",
86
+ "initializedAt": "2026-05-31T09:58:25.773Z"
87
+ },
88
+ "dependencies": {}
89
+ }
@@ -25,46 +25,47 @@ export const ADAPTERS: Record<AdapterId, AdapterConfig> = {
25
25
  frameworkDir: ".gemini",
26
26
  shimFile: "gemini.md",
27
27
  templateDir: ".enderun",
28
- nestedDirs: ["antigravity", "antigravity-cli"],
28
+ nestedDirs: ["agents", "skills", "knowledge"],
29
29
  },
30
30
  claude: {
31
31
  id: "claude",
32
32
  frameworkDir: ".claude",
33
33
  shimFile: "claude.md",
34
34
  templateDir: ".enderun",
35
+ nestedDirs: ["agents", "skills", "knowledge"],
35
36
  },
36
37
  grok: {
37
38
  id: "grok",
38
- frameworkDir: ".grok", // Standard config directory for Grok/X.ai agent
39
+ frameworkDir: ".grok",
39
40
  shimFile: "grok.md",
40
41
  templateDir: ".enderun",
42
+ nestedDirs: ["plugins", "knowledge"],
41
43
  },
42
44
  antigravity: {
43
45
  id: "antigravity",
44
- frameworkDir: ".gemini/antigravity",
46
+ frameworkDir: ".agent",
45
47
  shimFile: "agent.md",
46
48
  templateDir: ".enderun",
49
+ nestedDirs: ["skills", "workflows", "rules", "knowledge"],
47
50
  },
48
51
  "antigravity-cli": {
49
52
  id: "antigravity-cli",
50
53
  frameworkDir: ".gemini/antigravity-cli",
51
54
  shimFile: "agent.md",
52
55
  templateDir: ".enderun",
56
+ nestedDirs: ["agents", "skills"],
53
57
  },
54
58
  };
55
59
 
56
60
  /**
57
61
  * All framework runtime directories (for discovery).
58
- * Verified against 2026 industry standards for Claude Code (.claude/)
59
- * and internal standards for Antigravity (.gemini/antigravity/).
62
+ * Verified against 2026 industry standards.
60
63
  */
61
64
  export const FRAMEWORK_DIR_CANDIDATES = [
62
- ".gemini/antigravity",
63
- ".gemini/antigravity-cli",
65
+ ".agent",
64
66
  ".gemini",
65
67
  ".claude",
66
68
  ".grok",
67
- ".agent",
68
69
  ".enderun",
69
70
  ] as const;
70
71
 
@@ -89,8 +90,28 @@ export function remapFrameworkContent(
89
90
  adapterId: AdapterId,
90
91
  ): string {
91
92
  let result = content;
93
+
94
+ // 1. Dynamic Agent Folder Mapping based on adapter
95
+ let agentFolder = "agents";
96
+ let knowledgeFolder = "knowledge";
97
+
98
+ if (adapterId === "antigravity") {
99
+ agentFolder = "skills";
100
+ knowledgeFolder = "rules";
101
+ } else if (adapterId === "grok") {
102
+ agentFolder = "plugins";
103
+ }
104
+
105
+ // 2. Replacements
92
106
  result = result.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
93
107
  result = result.replace(/\{\{ADAPTER\}\}/g, adapterId);
108
+
109
+ // Replace standard patterns with adapter-specific ones
110
+ const frameworkPattern = ".enderun/";
111
+ result = result.replace(new RegExp(frameworkPattern + "agents/", "g"), `${frameworkDir}/${agentFolder}/`);
112
+ result = result.replace(new RegExp(frameworkPattern + "knowledge/", "g"), `${frameworkDir}/${knowledgeFolder}/`);
113
+
114
+ // Generic fallback for other .enderun/ paths
94
115
  result = result.replace(/\.enderun\//g, `${frameworkDir}/`);
95
116
  result = result.replace(/`\.enderun`/g, `\`${frameworkDir}\``);
96
117
  result = result.replace(/\.enderun(?![\w/-])/g, frameworkDir);
@@ -16,6 +16,12 @@ export function checkCommand() {
16
16
 
17
17
  const frameworkDir = getFrameworkDir();
18
18
  const pathsMap = getConfiguredPaths();
19
+
20
+ // Determine folder names based on adapter detection
21
+ let knowledgeDir = "knowledge";
22
+ if (frameworkDir === ".agent") {
23
+ knowledgeDir = "rules";
24
+ }
19
25
 
20
26
  const constitutionPath = fs.existsSync(path.join(process.cwd(), "ENDERUN.md"))
21
27
  ? "ENDERUN.md"
@@ -29,7 +35,7 @@ export function checkCommand() {
29
35
  { name: "Agent Status (STATUS.md)", path: path.join(frameworkDir, "STATUS.md") },
30
36
  { name: "MCP Config (mcp.json)", path: "mcp.json" },
31
37
  { name: "ESLint Config (eslint.config.js)", path: "eslint.config.js" },
32
- { name: "ESLint Standards", path: path.join(frameworkDir, "knowledge/eslint-standards.md") },
38
+ { name: "ESLint Standards", path: path.join(frameworkDir, `${knowledgeDir}/eslint-standards.md`) },
33
39
  { name: "Backend Contract", path: path.join(pathsMap.backend, "contract.version.json") },
34
40
  { name: "MCP Server", path: "framework-mcp/package.json" },
35
41
  { name: "Panda CSS Config", path: "panda.config.ts" },
@@ -11,7 +11,7 @@ import {
11
11
  type AdapterConfig,
12
12
  } from "../adapters.js";
13
13
  import { getPackageManager, mergePackageJson, deepCleanProtocols, sanitizeJson, getPackageVersion } from "../utils/pkg.js";
14
- import { updateGitIgnore, ensureDir, writeJsonFile, copyDir } from "../utils/fs.js";
14
+ import { updateGitIgnore, ensureDir, writeJsonFile, copyDir, writeTextFile } from "../utils/fs.js";
15
15
  import { initializeMemory } from "../utils/memory.js";
16
16
  import { checkCommand } from "./check.js";
17
17
 
@@ -76,23 +76,21 @@ function buildDirsToCreate(adapter: AdapterConfig): string[] {
76
76
  /**
77
77
  * Scaffold the framework for a specific IDE adapter (separate runtime folder per IDE).
78
78
  */
79
- export async function initCommand(adapterInput?: string) {
79
+ export async function initCommand(adapterInput?: string, dryRun = false) {
80
80
  const adapter = resolveAdapter(adapterInput);
81
81
  const { frameworkDir, shimFile, templateDir } = adapter;
82
82
  const targetFrameworkDir = path.join(targetDir, frameworkDir);
83
83
 
84
+ if (dryRun) {
85
+ console.warn("\n🔍 DRY RUN MODE ACTIVE - No changes will be made to the filesystem.\n");
86
+ }
87
+
84
88
  console.warn(`🚀 Installing Agent Enderun (Adapter: ${adapter.id} → ${frameworkDir}/)...`);
85
89
 
86
- if (!fs.existsSync(targetFrameworkDir)) {
87
- fs.mkdirSync(targetFrameworkDir, { recursive: true });
88
- }
90
+ ensureDir(targetFrameworkDir, dryRun);
89
91
 
90
92
  for (const dir of buildDirsToCreate(adapter)) {
91
- const fullPath = path.join(targetDir, dir);
92
- if (!fs.existsSync(fullPath)) {
93
- fs.mkdirSync(fullPath, { recursive: true });
94
- console.warn(`📂 Created directory: ${dir}`);
95
- }
93
+ ensureDir(path.join(targetDir, dir), dryRun);
96
94
  }
97
95
 
98
96
  const filesToProcess = [
@@ -105,13 +103,13 @@ export async function initCommand(adapterInput?: string) {
105
103
  fs.existsSync(path.join(sourceDir, f)),
106
104
  );
107
105
 
108
- ensureDir(path.join(targetDir, "apps/backend"));
106
+ ensureDir(path.join(targetDir, "apps/backend"), dryRun);
109
107
  const initContractPath = path.join(targetDir, "apps/backend/contract.version.json");
110
108
  if (!fs.existsSync(initContractPath)) {
111
109
  writeJsonFile(initContractPath, {
112
110
  contract_hash: "initial_hash_placeholder",
113
111
  last_updated: new Date().toISOString(),
114
- });
112
+ }, dryRun);
115
113
  }
116
114
 
117
115
  let targetPkg: Record<string, unknown> = {};
@@ -135,16 +133,43 @@ export async function initCommand(adapterInput?: string) {
135
133
  const src = path.join(sourceDir, item);
136
134
  let dest = path.join(targetDir, item);
137
135
 
138
- if (item === "framework-mcp" && fs.existsSync(dest)) {
136
+ // Safeguard: Do not delete or overwrite if we are in the framework's own development directory
137
+ const isDevMode = src === dest;
138
+
139
+ if (item === "framework-mcp" && fs.existsSync(dest) && !isDevMode) {
139
140
  try {
140
- fs.rmSync(dest, { recursive: true, force: true });
141
+ if (dryRun) {
142
+ console.warn(`[DRY RUN] Would remove existing directory: ${dest}`);
143
+ } else {
144
+ fs.rmSync(dest, { recursive: true, force: true });
145
+ }
141
146
  } catch {
142
147
  // ignore
143
148
  }
144
149
  }
145
150
 
146
151
  if (item === templateDir || item.startsWith(`${templateDir}/`)) {
147
- dest = path.join(targetDir, item.replace(templateDir, frameworkDir));
152
+ let relativePath = item.replace(templateDir, "").replace(/^\//, "");
153
+
154
+ // Adapter-specific agent/skill folder remapping
155
+ if (relativePath.startsWith("agents")) {
156
+ if (adapter.id === "antigravity") {
157
+ relativePath = relativePath.replace("agents", "skills");
158
+ } else if (adapter.id === "grok") {
159
+ relativePath = relativePath.replace("agents", "plugins");
160
+ }
161
+ } else if (relativePath.startsWith("knowledge")) {
162
+ if (adapter.id === "antigravity") {
163
+ relativePath = relativePath.replace("knowledge", "rules");
164
+ }
165
+ }
166
+
167
+ dest = path.join(targetDir, frameworkDir, relativePath);
168
+ }
169
+
170
+ // Skip if src and dest are same and it's not a shim or specific file that needs re-mapping
171
+ if (isDevMode && !isAdapterShimFile(item) && item !== "package.json") {
172
+ continue;
148
173
  }
149
174
  if (item === "ENDERUN.md") {
150
175
  dest = path.join(targetFrameworkDir, "ENDERUN.md");
@@ -161,6 +186,11 @@ export async function initCommand(adapterInput?: string) {
161
186
  const skipFiles =
162
187
  item === templateDir ? ["logs", "PROJECT_MEMORY.md", "PROJECT_MEMORY.lock"] : [];
163
188
  const nonDestructive = ["docs", templateDir].includes(item);
189
+
190
+ if (item === templateDir) {
191
+ // the destination path (dest) is already mapped correctly above.
192
+ }
193
+
164
194
  copyDir(
165
195
  src,
166
196
  dest,
@@ -170,6 +200,7 @@ export async function initCommand(adapterInput?: string) {
170
200
  targetScope,
171
201
  sanitizeJson,
172
202
  adapter.id,
203
+ dryRun,
173
204
  );
174
205
  } else {
175
206
  if (item === "package.json") continue;
@@ -195,25 +226,38 @@ export async function initCommand(adapterInput?: string) {
195
226
  }
196
227
  }
197
228
 
198
- fs.writeFileSync(dest, content);
229
+ writeTextFile(dest, content, dryRun);
199
230
  } else {
200
- fs.copyFileSync(src, dest);
231
+ if (dryRun) {
232
+ console.warn(`[DRY RUN] Would copy binary file: ${dest}`);
233
+ } else {
234
+ fs.copyFileSync(src, dest);
235
+ }
201
236
  }
202
237
  }
203
238
  console.warn(`✅ ${item} processed -> ${path.relative(targetDir, dest)}`);
204
239
  }
205
240
 
206
- mergePackageJson(path.join(targetDir, "package.json"), path.join(sourceDir, "package.json"));
207
- updateGitIgnore(path.join(targetDir, ".gitignore"), frameworkDir);
241
+ if (!dryRun) {
242
+ mergePackageJson(path.join(targetDir, "package.json"), path.join(sourceDir, "package.json"));
243
+ } else {
244
+ console.warn("[DRY RUN] Would merge package.json content.");
245
+ }
246
+
247
+ updateGitIgnore(path.join(targetDir, ".gitignore"), frameworkDir, dryRun);
208
248
 
209
249
  const finalMemoryPath = path.join(targetFrameworkDir, "PROJECT_MEMORY.md");
210
- initializeMemory(finalMemoryPath, frameworkDir);
250
+ initializeMemory(finalMemoryPath, frameworkDir, dryRun);
211
251
 
212
- deepCleanProtocols(targetDir, targetScope);
252
+ if (!dryRun) {
253
+ deepCleanProtocols(targetDir, targetScope);
254
+ } else {
255
+ console.warn("[DRY RUN] Would clean specific workspace protocols.");
256
+ }
213
257
 
214
258
  const sampleTestPath = path.join(targetDir, "tests/initial.test.ts");
215
259
  if (!fs.existsSync(sampleTestPath)) {
216
- fs.writeFileSync(
260
+ writeTextFile(
217
261
  sampleTestPath,
218
262
  `import { describe, it, expect } from "vitest";
219
263
 
@@ -223,20 +267,29 @@ describe("Initial Setup", () => {
223
267
  });
224
268
  });
225
269
  `,
270
+ dryRun,
226
271
  );
227
272
  }
228
273
 
229
274
  if (!fs.existsSync(path.join(targetDir, ".git"))) {
230
275
  try {
231
- execSync("git init", { cwd: targetDir, stdio: "ignore" });
232
- console.warn(" Git repository initialized.");
276
+ if (dryRun) {
277
+ console.warn("[DRY RUN] Would initialize git repository.");
278
+ } else {
279
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
280
+ console.warn("✅ Git repository initialized.");
281
+ }
233
282
  } catch {
234
283
  // ignore
235
284
  }
236
285
  }
237
286
 
238
287
  console.warn(`\n🛠️ Running ${adapter.id} adapter configuration...`);
239
- runAdapterPostInit(adapter, targetDir);
288
+ if (!dryRun) {
289
+ runAdapterPostInit(adapter, targetDir);
290
+ } else {
291
+ console.warn(`[DRY RUN] Would run post-init steps for ${adapter.id}`);
292
+ }
240
293
 
241
294
  console.warn(`\n♊ ${adapter.id.toUpperCase()}: Setup complete.`);
242
295
  console.warn(` • Framework runtime: ${frameworkDir}/`);
@@ -251,8 +304,8 @@ describe("Initial Setup", () => {
251
304
 
252
305
  console.warn(`\n✨ Framework scaffolded! (v${FRAMEWORK_VERSION})`);
253
306
 
254
- if (process.env.ENDERUN_SKIP_INSTALL === "1") {
255
- console.warn("\n⏭️ Skipping install steps (ENDERUN_SKIP_INSTALL=1).");
307
+ if (dryRun || process.env.ENDERUN_SKIP_INSTALL === "1") {
308
+ console.warn(`\n⏭️ Skipping install steps (${dryRun ? "Dry-Run" : "ENDERUN_SKIP_INSTALL=1"}).`);
256
309
  return;
257
310
  }
258
311