@rryando/arcs 3.1.2 → 3.2.1

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 (128) hide show
  1. package/README.md +65 -40
  2. package/dist/cli/arcs-orchestrate.d.ts +1 -1
  3. package/dist/cli/arcs-orchestrate.d.ts.map +1 -1
  4. package/dist/cli/arcs-orchestrate.js +8 -4
  5. package/dist/cli/arcs-orchestrate.js.map +1 -1
  6. package/dist/cli/arg-parser.d.ts +3 -3
  7. package/dist/cli/arg-parser.d.ts.map +1 -1
  8. package/dist/cli/arg-parser.js +1 -1
  9. package/dist/cli/arg-parser.js.map +1 -1
  10. package/dist/cli/bundle-installer.d.ts +11 -0
  11. package/dist/cli/bundle-installer.d.ts.map +1 -1
  12. package/dist/cli/bundle-installer.js +14 -4
  13. package/dist/cli/bundle-installer.js.map +1 -1
  14. package/dist/cli/command-registry.d.ts +61 -6
  15. package/dist/cli/command-registry.d.ts.map +1 -1
  16. package/dist/cli/command-registry.js.map +1 -1
  17. package/dist/cli/commands/batch.js +17 -16
  18. package/dist/cli/commands/batch.js.map +1 -1
  19. package/dist/cli/commands/brief.js +71 -74
  20. package/dist/cli/commands/brief.js.map +1 -1
  21. package/dist/cli/commands/bundle.js +79 -31
  22. package/dist/cli/commands/bundle.js.map +1 -1
  23. package/dist/cli/commands/dependency.js +47 -43
  24. package/dist/cli/commands/dependency.js.map +1 -1
  25. package/dist/cli/commands/diagnostics.js +1 -1
  26. package/dist/cli/commands/diagnostics.js.map +1 -1
  27. package/dist/cli/commands/diagram.js +96 -58
  28. package/dist/cli/commands/diagram.js.map +1 -1
  29. package/dist/cli/commands/done.js +19 -18
  30. package/dist/cli/commands/done.js.map +1 -1
  31. package/dist/cli/commands/graph.js +12 -10
  32. package/dist/cli/commands/graph.js.map +1 -1
  33. package/dist/cli/commands/index.d.ts +1 -0
  34. package/dist/cli/commands/index.d.ts.map +1 -1
  35. package/dist/cli/commands/index.js +1 -0
  36. package/dist/cli/commands/index.js.map +1 -1
  37. package/dist/cli/commands/knowledge-search.js +11 -12
  38. package/dist/cli/commands/knowledge-search.js.map +1 -1
  39. package/dist/cli/commands/knowledge.js +63 -56
  40. package/dist/cli/commands/knowledge.js.map +1 -1
  41. package/dist/cli/commands/loop.js +40 -36
  42. package/dist/cli/commands/loop.js.map +1 -1
  43. package/dist/cli/commands/maintenance.js +30 -19
  44. package/dist/cli/commands/maintenance.js.map +1 -1
  45. package/dist/cli/commands/next.js +9 -8
  46. package/dist/cli/commands/next.js.map +1 -1
  47. package/dist/cli/commands/plan.js +61 -55
  48. package/dist/cli/commands/plan.js.map +1 -1
  49. package/dist/cli/commands/project-updates.js +37 -33
  50. package/dist/cli/commands/project-updates.js.map +1 -1
  51. package/dist/cli/commands/project.js +33 -23
  52. package/dist/cli/commands/project.js.map +1 -1
  53. package/dist/cli/commands/proposal.d.ts +2 -0
  54. package/dist/cli/commands/proposal.d.ts.map +1 -0
  55. package/dist/cli/commands/proposal.js +509 -0
  56. package/dist/cli/commands/proposal.js.map +1 -0
  57. package/dist/cli/commands/remember.js +6 -6
  58. package/dist/cli/commands/remember.js.map +1 -1
  59. package/dist/cli/commands/status.js +9 -8
  60. package/dist/cli/commands/status.js.map +1 -1
  61. package/dist/cli/commands/task.d.ts +1 -5
  62. package/dist/cli/commands/task.d.ts.map +1 -1
  63. package/dist/cli/commands/task.js +261 -248
  64. package/dist/cli/commands/task.js.map +1 -1
  65. package/dist/cli/commands/utility.d.ts.map +1 -1
  66. package/dist/cli/commands/utility.js +39 -35
  67. package/dist/cli/commands/utility.js.map +1 -1
  68. package/dist/cli/dag-commands.js.map +1 -1
  69. package/dist/cli/help-generator.d.ts +3 -3
  70. package/dist/cli/help-generator.d.ts.map +1 -1
  71. package/dist/cli/help-generator.js +1 -1
  72. package/dist/cli/help-generator.js.map +1 -1
  73. package/dist/cli/md-renderer.d.ts.map +1 -1
  74. package/dist/cli/md-renderer.js +45 -11
  75. package/dist/cli/md-renderer.js.map +1 -1
  76. package/dist/cli/setup.d.ts.map +1 -1
  77. package/dist/cli/setup.js +235 -113
  78. package/dist/cli/setup.js.map +1 -1
  79. package/dist/utils/diagram-generator.d.ts.map +1 -1
  80. package/dist/utils/diagram-generator.js +8 -1
  81. package/dist/utils/diagram-generator.js.map +1 -1
  82. package/dist/utils/diagram-store.d.ts +45 -0
  83. package/dist/utils/diagram-store.d.ts.map +1 -0
  84. package/dist/utils/diagram-store.js +99 -0
  85. package/dist/utils/diagram-store.js.map +1 -0
  86. package/dist/utils/errors.d.ts.map +1 -1
  87. package/dist/utils/errors.js.map +1 -1
  88. package/dist/utils/graphify-knowledge.d.ts +19 -3
  89. package/dist/utils/graphify-knowledge.d.ts.map +1 -1
  90. package/dist/utils/graphify-knowledge.js +45 -58
  91. package/dist/utils/graphify-knowledge.js.map +1 -1
  92. package/dist/utils/graphify.d.ts +58 -3
  93. package/dist/utils/graphify.d.ts.map +1 -1
  94. package/dist/utils/graphify.js +175 -42
  95. package/dist/utils/graphify.js.map +1 -1
  96. package/dist/utils/json-schemas.d.ts +153 -0
  97. package/dist/utils/json-schemas.d.ts.map +1 -1
  98. package/dist/utils/json-schemas.js +23 -0
  99. package/dist/utils/json-schemas.js.map +1 -1
  100. package/dist/utils/proposal-store.d.ts +64 -0
  101. package/dist/utils/proposal-store.d.ts.map +1 -0
  102. package/dist/utils/proposal-store.js +161 -0
  103. package/dist/utils/proposal-store.js.map +1 -0
  104. package/dist/utils/storage-utils.d.ts +1 -1
  105. package/dist/utils/storage-utils.d.ts.map +1 -1
  106. package/dist/utils/storage-utils.js +1 -1
  107. package/dist/utils/storage-utils.js.map +1 -1
  108. package/dist/utils/task-store.d.ts +20 -0
  109. package/dist/utils/task-store.d.ts.map +1 -1
  110. package/dist/utils/task-store.js +42 -5
  111. package/dist/utils/task-store.js.map +1 -1
  112. package/dist/utils/toposort.d.ts +1 -1
  113. package/dist/utils/toposort.d.ts.map +1 -1
  114. package/dist/utils/toposort.js +5 -4
  115. package/dist/utils/toposort.js.map +1 -1
  116. package/dist/utils/workflow-policy.d.ts +1 -1
  117. package/dist/utils/workflow-policy.d.ts.map +1 -1
  118. package/dist/utils/workflow-policy.js +1 -1
  119. package/dist/utils/workflow-policy.js.map +1 -1
  120. package/opencode/arcs/bundle-runtime.json +3 -0
  121. package/opencode/arcs/manifest.json +47 -66
  122. package/opencode/arcs/prompts/arcs-orchestrate-caveman.txt +8 -4
  123. package/opencode/arcs/prompts/arcs-orchestrate.txt +8 -4
  124. package/opencode/arcs/skills/enriching-graphify-proposals/SKILL.md +170 -0
  125. package/opencode/arcs/skills/init-project/SKILL.md +18 -11
  126. package/opencode/arcs/skills/to-diagram/SKILL.md +1 -1
  127. package/package.json +1 -1
  128. package/scripts/deploy-claudecode-bundle.mjs +414 -0
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env node
2
+ // Deploy claudecode ARCS bundle from repo to Claude Code config.
3
+ //
4
+ // Direction: repo → config ONLY. Never writes config → repo.
5
+ //
6
+ // What gets deployed:
7
+ // 1. Sub-agent prompts → <destination>/agents/<stem>.md
8
+ // 2. Skills tree → <destination>/skills/arcs-<name>/...
9
+ // (Claude Code skills must be flat under skills/; we use the `arcs-`
10
+ // prefix as a namespace so orphan pruning never touches user skills.)
11
+ // 3. Default agent → settings.json `agent` field set to "arcs-orchestrate"
12
+ //
13
+ // Env vars:
14
+ // DEPLOY_BUNDLE_ROOT — override bundle root (default: opencode/arcs)
15
+ // DEPLOY_CONFIG_ROOT — override config root (default: ~/.claude)
16
+ // DEPLOY_PROJECT_ROOT — override project root (default: repository root)
17
+ // DEPLOY_SCOPE — `global` or `project` (default: `global`)
18
+ // DEPLOY_DRY_RUN — "false" to actually write; anything else = dry-run (default: dry-run)
19
+ //
20
+ // Outputs JSON to stdout: DeployResult
21
+ // Exit code: 0 on success, 1 on error.
22
+
23
+ import {
24
+ existsSync,
25
+ mkdirSync,
26
+ readdirSync,
27
+ readFileSync,
28
+ rmSync,
29
+ statSync,
30
+ writeFileSync,
31
+ } from "node:fs";
32
+ import { homedir } from "node:os";
33
+ import { dirname, relative, resolve } from "node:path";
34
+
35
+ const repoRoot = resolve(import.meta.dirname, "..");
36
+ const defaultBundleRoot = resolve(repoRoot, "opencode/arcs");
37
+ const defaultConfigRoot = resolve(homedir(), ".claude");
38
+ const defaultProjectRoot = repoRoot;
39
+
40
+ const bundleRoot = process.env.DEPLOY_BUNDLE_ROOT
41
+ ? resolve(repoRoot, process.env.DEPLOY_BUNDLE_ROOT)
42
+ : defaultBundleRoot;
43
+
44
+ let configRoot;
45
+ if (process.env.DEPLOY_CONFIG_ROOT) {
46
+ const rawPath = process.env.DEPLOY_CONFIG_ROOT;
47
+ if (rawPath.startsWith("~/")) {
48
+ configRoot = resolve(homedir(), rawPath.slice(2));
49
+ } else if (rawPath === "~") {
50
+ configRoot = homedir();
51
+ } else {
52
+ configRoot = resolve(homedir(), rawPath);
53
+ }
54
+ } else {
55
+ configRoot = defaultConfigRoot;
56
+ }
57
+
58
+ const projectRoot = process.env.DEPLOY_PROJECT_ROOT
59
+ ? resolve(repoRoot, process.env.DEPLOY_PROJECT_ROOT)
60
+ : defaultProjectRoot;
61
+
62
+ const scope = process.env.DEPLOY_SCOPE === "project" ? "project" : "global";
63
+ const dryRun = process.env.DEPLOY_DRY_RUN !== "false";
64
+
65
+ const destination = scope === "project" ? projectRoot : configRoot;
66
+
67
+ const DEFAULT_AGENT = "arcs-orchestrate";
68
+ const SKILL_PREFIX = "arcs-";
69
+
70
+ const agentMetadata = {
71
+ "software-engineer": {
72
+ name: "Software Engineer",
73
+ description: "Implementation specialist. Writes code, runs tests, and ships features.",
74
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
75
+ model: "inherit",
76
+ },
77
+ "devil-advocate": {
78
+ name: "Devil's Advocate",
79
+ description: "Adversarial phase-gate agent. Checks work using KISS/YAGNI/DRY principles.",
80
+ tools: "Read, Glob, Grep, Bash",
81
+ model: "inherit",
82
+ },
83
+ "tech-architect": {
84
+ name: "Technical Architect",
85
+ description:
86
+ "Architecture and analysis specialist. Deep structural reasoning and trade-off evaluation.",
87
+ tools: "Read, Glob, Grep, Bash",
88
+ model: "inherit",
89
+ },
90
+ "code-reviewer": {
91
+ name: "Code Reviewer",
92
+ description: "Reviews code changes for correctness, maintainability, and testing issues.",
93
+ tools: "Read, Glob, Grep, Bash",
94
+ model: "inherit",
95
+ },
96
+ "qa-analyst": {
97
+ name: "QA Analyst",
98
+ description: "Quality enforcement specialist. Proactive code audits and convention compliance.",
99
+ tools: "Read, Glob, Grep, Bash",
100
+ model: "inherit",
101
+ },
102
+ "system-architect": {
103
+ name: "System Architect",
104
+ description:
105
+ "Architecture and design specialist. Module boundaries, dependency graphs, and design decisions.",
106
+ tools: "Read, Glob, Grep, Bash",
107
+ model: "inherit",
108
+ },
109
+ "arcs-docs": {
110
+ name: "ARCS Docs Specialist",
111
+ description:
112
+ "Documentation specialist. Manages plans, knowledge entries, diagrams, and DAG health.",
113
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
114
+ model: "inherit",
115
+ },
116
+ "docs-researcher": {
117
+ name: "Docs Researcher",
118
+ description: "Handles documentation writing, research synthesis, and document-heavy analysis.",
119
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
120
+ model: "inherit",
121
+ },
122
+ "oncall-ops": {
123
+ name: "On-Call Ops",
124
+ description:
125
+ "Debugging and diagnosis specialist. Finds root causes through systematic investigation.",
126
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
127
+ model: "inherit",
128
+ },
129
+ "arcs-orchestrate": {
130
+ name: "ARCS Orchestrator",
131
+ description:
132
+ "The central coordinator for executing plans, managing agent dispatch, and handling DAG workflows.",
133
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
134
+ model: "inherit",
135
+ },
136
+ "arcs-orchestrate-caveman": {
137
+ name: "ARCS Orchestrator (Caveman)",
138
+ description:
139
+ "A terse, high-efficiency orchestrator that drives tasks without extra commentary.",
140
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
141
+ model: "inherit",
142
+ },
143
+ };
144
+
145
+ function ensureParentDir(filePath) {
146
+ mkdirSync(dirname(filePath), { recursive: true });
147
+ }
148
+
149
+ function configRelativePath(suffix) {
150
+ return scope === "project" ? `.claude/${suffix}` : suffix;
151
+ }
152
+
153
+ function walkFiles(dir) {
154
+ const out = [];
155
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
156
+ const full = resolve(dir, entry.name);
157
+ if (entry.isDirectory()) {
158
+ out.push(...walkFiles(full));
159
+ } else if (entry.isFile()) {
160
+ out.push(full);
161
+ }
162
+ }
163
+ return out;
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // 1. Compile sub-agent prompts
168
+ // ---------------------------------------------------------------------------
169
+
170
+ function buildAgentSources() {
171
+ const promptsDir = resolve(bundleRoot, "prompts");
172
+ if (!existsSync(promptsDir)) {
173
+ throw new Error(`Prompts directory not found at ${promptsDir}`);
174
+ }
175
+
176
+ const promptFiles = readdirSync(promptsDir).filter((f) => f.endsWith(".txt"));
177
+ const sources = [];
178
+
179
+ for (const file of promptFiles) {
180
+ const stem = file.slice(0, -4);
181
+ const sourcePath = resolve(promptsDir, file);
182
+ const promptContent = readFileSync(sourcePath, "utf-8");
183
+
184
+ const meta = agentMetadata[stem] || {
185
+ name: stem
186
+ .split("-")
187
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
188
+ .join(" "),
189
+ description: `ARCS ${stem} agent.`,
190
+ tools: "Read, Write, Edit, Glob, Grep, Bash",
191
+ model: "inherit",
192
+ };
193
+
194
+ const toolsArray = meta.tools.split(",").map((t) => t.trim());
195
+ const toolsYaml = `[${toolsArray.map((t) => `"${t}"`).join(", ")}]`;
196
+
197
+ const compiledContent = [
198
+ "---",
199
+ `name: ${meta.name}`,
200
+ `description: ${meta.description}`,
201
+ `model: ${meta.model}`,
202
+ `tools: ${toolsYaml}`,
203
+ "---",
204
+ "",
205
+ promptContent,
206
+ ].join("\n");
207
+
208
+ sources.push({
209
+ stem,
210
+ compiledContent,
211
+ configRelative: configRelativePath(`agents/${stem}.md`),
212
+ });
213
+ }
214
+
215
+ return sources;
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // 2. Walk skills tree
220
+ // ---------------------------------------------------------------------------
221
+
222
+ function buildSkillSources() {
223
+ const skillsDir = resolve(bundleRoot, "skills");
224
+ if (!existsSync(skillsDir)) return { skillNames: [], files: [] };
225
+
226
+ const skillNames = readdirSync(skillsDir, { withFileTypes: true })
227
+ .filter((e) => e.isDirectory())
228
+ .map((e) => e.name);
229
+
230
+ const files = [];
231
+ for (const skillName of skillNames) {
232
+ const skillRoot = resolve(skillsDir, skillName);
233
+ for (const absPath of walkFiles(skillRoot)) {
234
+ const rel = relative(skillRoot, absPath).split(/[\\/]/).join("/");
235
+ files.push({
236
+ skillName,
237
+ absSource: absPath,
238
+ configRelative: configRelativePath(`skills/${SKILL_PREFIX}${skillName}/${rel}`),
239
+ });
240
+ }
241
+ }
242
+
243
+ return { skillNames, files };
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // 3. settings.json merge — set default agent
248
+ // ---------------------------------------------------------------------------
249
+
250
+ function planSettingsUpdate() {
251
+ const settingsConfigRelative = configRelativePath("settings.json");
252
+ const settingsAbsolute = resolve(destination, settingsConfigRelative);
253
+
254
+ let existing = null;
255
+ let parsed = {};
256
+ if (existsSync(settingsAbsolute)) {
257
+ existing = readFileSync(settingsAbsolute, "utf-8");
258
+ try {
259
+ parsed = JSON.parse(existing);
260
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
261
+ parsed = {};
262
+ }
263
+ } catch {
264
+ // Malformed JSON — preserve as-is by skipping the merge.
265
+ return { settingsConfigRelative, settingsAbsolute, status: "skipped-malformed" };
266
+ }
267
+ }
268
+
269
+ const merged = { ...parsed, agent: DEFAULT_AGENT };
270
+ const serialized = `${JSON.stringify(merged, null, 2)}\n`;
271
+
272
+ let status;
273
+ if (existing === null) {
274
+ status = "added";
275
+ } else if (existing === serialized) {
276
+ status = "unchanged";
277
+ } else {
278
+ status = "changed";
279
+ }
280
+
281
+ return { settingsConfigRelative, settingsAbsolute, status, serialized };
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Main
286
+ // ---------------------------------------------------------------------------
287
+
288
+ function main() {
289
+ const agentSources = buildAgentSources();
290
+ const skillSources = buildSkillSources();
291
+ const settingsPlan = planSettingsUpdate();
292
+
293
+ // Pass 1: Classify add/change/unchanged for every planned write
294
+ const filesAdded = [];
295
+ const filesChanged = [];
296
+ const filesUnchanged = [];
297
+
298
+ for (const { compiledContent, configRelative } of agentSources) {
299
+ const abs = resolve(destination, configRelative);
300
+ if (!existsSync(abs)) {
301
+ filesAdded.push(configRelative);
302
+ } else if (readFileSync(abs, "utf-8") !== compiledContent) {
303
+ filesChanged.push(configRelative);
304
+ } else {
305
+ filesUnchanged.push(configRelative);
306
+ }
307
+ }
308
+
309
+ for (const { absSource, configRelative } of skillSources.files) {
310
+ const abs = resolve(destination, configRelative);
311
+ const sourceContent = readFileSync(absSource);
312
+ if (!existsSync(abs)) {
313
+ filesAdded.push(configRelative);
314
+ } else {
315
+ const existing = readFileSync(abs);
316
+ if (Buffer.compare(existing, sourceContent) !== 0) {
317
+ filesChanged.push(configRelative);
318
+ } else {
319
+ filesUnchanged.push(configRelative);
320
+ }
321
+ }
322
+ }
323
+
324
+ if (settingsPlan.status === "added") {
325
+ filesAdded.push(settingsPlan.settingsConfigRelative);
326
+ } else if (settingsPlan.status === "changed") {
327
+ filesChanged.push(settingsPlan.settingsConfigRelative);
328
+ } else if (settingsPlan.status === "unchanged") {
329
+ filesUnchanged.push(settingsPlan.settingsConfigRelative);
330
+ }
331
+ // status "skipped-malformed" intentionally omitted from all lists
332
+
333
+ // Orphans
334
+ const filesRemoved = [];
335
+
336
+ // Agent orphans — only `.md` files whose stem we recognize from agentMetadata
337
+ const agentsTargetDir = resolve(destination, configRelativePath("agents"));
338
+ const activeAgentStems = new Set(agentSources.map((f) => f.stem));
339
+ if (existsSync(agentsTargetDir)) {
340
+ for (const entry of readdirSync(agentsTargetDir, { withFileTypes: true })) {
341
+ if (entry.isFile() && entry.name.endsWith(".md")) {
342
+ const stem = entry.name.slice(0, -3);
343
+ if (agentMetadata[stem] && !activeAgentStems.has(stem)) {
344
+ filesRemoved.push(configRelativePath(`agents/${entry.name}`));
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ // Skill orphans — only `arcs-*/` directories that aren't in current source.
351
+ // The prefix gives us a safe namespace; foreign user skills are never touched.
352
+ const skillsTargetDir = resolve(destination, configRelativePath("skills"));
353
+ const activeSkillDirs = new Set(skillSources.skillNames.map((n) => `${SKILL_PREFIX}${n}`));
354
+ const orphanSkillDirs = [];
355
+ if (existsSync(skillsTargetDir)) {
356
+ for (const entry of readdirSync(skillsTargetDir, { withFileTypes: true })) {
357
+ if (entry.isDirectory() && entry.name.startsWith(SKILL_PREFIX)) {
358
+ if (!activeSkillDirs.has(entry.name)) {
359
+ orphanSkillDirs.push(entry.name);
360
+ filesRemoved.push(configRelativePath(`skills/${entry.name}`));
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ // Pass 2: Write if not dry-run
367
+ if (!dryRun) {
368
+ for (const { compiledContent, configRelative } of agentSources) {
369
+ const abs = resolve(destination, configRelative);
370
+ ensureParentDir(abs);
371
+ writeFileSync(abs, compiledContent, "utf-8");
372
+ }
373
+
374
+ for (const { absSource, configRelative } of skillSources.files) {
375
+ const abs = resolve(destination, configRelative);
376
+ ensureParentDir(abs);
377
+ writeFileSync(abs, readFileSync(absSource));
378
+ }
379
+
380
+ if (settingsPlan.serialized) {
381
+ ensureParentDir(settingsPlan.settingsAbsolute);
382
+ writeFileSync(settingsPlan.settingsAbsolute, settingsPlan.serialized, "utf-8");
383
+ }
384
+
385
+ for (const fileToRemove of filesRemoved) {
386
+ const abs = resolve(destination, fileToRemove);
387
+ if (existsSync(abs)) {
388
+ // Could be a file (agent orphan) or directory (skill orphan)
389
+ const isDir = statSync(abs).isDirectory();
390
+ rmSync(abs, { recursive: isDir, force: true });
391
+ }
392
+ }
393
+ }
394
+
395
+ const result = {
396
+ dryRun,
397
+ source: bundleRoot,
398
+ destination,
399
+ filesAdded,
400
+ filesChanged,
401
+ filesRemoved,
402
+ filesUnchanged,
403
+ };
404
+
405
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
406
+ }
407
+
408
+ try {
409
+ main();
410
+ } catch (error) {
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ process.stderr.write(`${message}\n`);
413
+ process.exitCode = 1;
414
+ }