devtronic 1.0.0 → 1.2.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 (46) hide show
  1. package/README.md +5 -3
  2. package/dist/{chunk-B6Q6YVID.js → chunk-V4QEAL7Y.js} +53 -23
  3. package/dist/index.js +1070 -220
  4. package/dist/{plugin-JTDPHWUF.js → plugin-SGSFVXPA.js} +5 -1
  5. package/package.json +1 -1
  6. package/templates/claude-code/.claude/agents/a11y-auditor.md +82 -0
  7. package/templates/claude-code/.claude/agents/design-critic.md +77 -0
  8. package/templates/claude-code/.claude/agents/design-system-guardian.md +78 -0
  9. package/templates/claude-code/.claude/agents/design-token-extractor.md +53 -0
  10. package/templates/claude-code/.claude/agents/ia-architect.md +81 -0
  11. package/templates/claude-code/.claude/agents/ux-researcher.md +69 -0
  12. package/templates/claude-code/.claude/agents/visual-qa.md +74 -0
  13. package/templates/claude-code/.claude/skills/audit/SKILL.md +1 -1
  14. package/templates/claude-code/.claude/skills/backlog/SKILL.md +1 -1
  15. package/templates/claude-code/.claude/skills/brief/SKILL.md +1 -1
  16. package/templates/claude-code/.claude/skills/briefing/SKILL.md +1 -1
  17. package/templates/claude-code/.claude/skills/checkpoint/SKILL.md +1 -1
  18. package/templates/claude-code/.claude/skills/create-plan/SKILL.md +1 -1
  19. package/templates/claude-code/.claude/skills/create-skill/SKILL.md +1 -1
  20. package/templates/claude-code/.claude/skills/design/SKILL.md +48 -0
  21. package/templates/claude-code/.claude/skills/design-audit/SKILL.md +114 -0
  22. package/templates/claude-code/.claude/skills/design-define/SKILL.md +109 -0
  23. package/templates/claude-code/.claude/skills/design-ia/SKILL.md +103 -0
  24. package/templates/claude-code/.claude/skills/design-research/SKILL.md +116 -0
  25. package/templates/claude-code/.claude/skills/design-review/SKILL.md +99 -0
  26. package/templates/claude-code/.claude/skills/design-spec/SKILL.md +136 -0
  27. package/templates/claude-code/.claude/skills/design-system/SKILL.md +39 -0
  28. package/templates/claude-code/.claude/skills/design-system-audit/SKILL.md +106 -0
  29. package/templates/claude-code/.claude/skills/design-system-define/SKILL.md +141 -0
  30. package/templates/claude-code/.claude/skills/design-system-sync/SKILL.md +105 -0
  31. package/templates/claude-code/.claude/skills/design-wireframe/SKILL.md +120 -0
  32. package/templates/claude-code/.claude/skills/execute-plan/SKILL.md +1 -1
  33. package/templates/claude-code/.claude/skills/generate-tests/SKILL.md +1 -1
  34. package/templates/claude-code/.claude/skills/handoff/SKILL.md +1 -1
  35. package/templates/claude-code/.claude/skills/investigate/SKILL.md +1 -1
  36. package/templates/claude-code/.claude/skills/learn/SKILL.md +1 -1
  37. package/templates/claude-code/.claude/skills/opensrc/SKILL.md +1 -1
  38. package/templates/claude-code/.claude/skills/post-review/SKILL.md +1 -1
  39. package/templates/claude-code/.claude/skills/quick/SKILL.md +1 -1
  40. package/templates/claude-code/.claude/skills/recap/SKILL.md +1 -1
  41. package/templates/claude-code/.claude/skills/research/SKILL.md +1 -1
  42. package/templates/claude-code/.claude/skills/scaffold/SKILL.md +1 -1
  43. package/templates/claude-code/.claude/skills/setup/SKILL.md +1 -1
  44. package/templates/claude-code/.claude/skills/spec/SKILL.md +1 -1
  45. package/templates/claude-code/.claude/skills/summary/SKILL.md +1 -1
  46. package/templates/claude-code/.claude/skills/worktree/SKILL.md +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ADDONS,
4
+ BASE_AGENT_COUNT,
4
5
  BASE_SKILL_COUNT,
6
+ DESIGN_SKILL_COUNT,
5
7
  MANIFEST_DIR,
6
8
  MARKETPLACE_NAME,
7
9
  PLUGIN_DIR,
@@ -22,12 +24,12 @@ import {
22
24
  readManifest,
23
25
  writeFile,
24
26
  writeManifest
25
- } from "./chunk-B6Q6YVID.js";
27
+ } from "./chunk-V4QEAL7Y.js";
26
28
 
27
29
  // src/index.ts
28
30
  import { Command } from "commander";
29
- import * as p15 from "@clack/prompts";
30
- import chalk16 from "chalk";
31
+ import * as p16 from "@clack/prompts";
32
+ import chalk17 from "chalk";
31
33
 
32
34
  // src/commands/init.ts
33
35
  import { existsSync as existsSync5, chmodSync } from "fs";
@@ -1205,9 +1207,9 @@ function getCliVersion() {
1205
1207
  resolve(__dirname, "../package.json"),
1206
1208
  resolve(__dirname, "../../package.json")
1207
1209
  ];
1208
- for (const p16 of paths) {
1210
+ for (const p17 of paths) {
1209
1211
  try {
1210
- const pkg = JSON.parse(readFileSync(p16, "utf-8"));
1212
+ const pkg = JSON.parse(readFileSync(p17, "utf-8"));
1211
1213
  if (pkg.name === "devtronic" && pkg.version) {
1212
1214
  return pkg.version;
1213
1215
  }
@@ -1372,7 +1374,18 @@ Valid addons: ${validAddons.join(", ")}`);
1372
1374
  process.exit(0);
1373
1375
  }
1374
1376
  if (wantOrchestration) {
1375
- enabledAddons = ["orchestration"];
1377
+ enabledAddons.push("orchestration");
1378
+ }
1379
+ const wantDesign = await p3.confirm({
1380
+ message: "Enable design best practices? (design-init \u2192 design-review \u2192 design-refine \u2192 design-harden)",
1381
+ initialValue: false
1382
+ });
1383
+ if (p3.isCancel(wantDesign)) {
1384
+ p3.cancel("Operation cancelled");
1385
+ process.exit(0);
1386
+ }
1387
+ if (wantDesign) {
1388
+ enabledAddons.push("design-best-practices");
1376
1389
  }
1377
1390
  }
1378
1391
  if (enabledAddons.length > 0) {
@@ -1452,8 +1465,9 @@ Valid addons: ${validAddons.join(", ")}`);
1452
1465
  manifest.installMode = "plugin";
1453
1466
  manifest.pluginPath = pluginResult.pluginPath;
1454
1467
  const addonSkillCount = (projectConfig.enabledAddons || []).reduce((sum, a) => sum + (ADDONS[a]?.skills.length ?? 0), 0);
1455
- const skillLabel = addonSkillCount > 0 ? `${BASE_SKILL_COUNT} + ${addonSkillCount} addon skills` : `${BASE_SKILL_COUNT} skills`;
1456
- generatedFiles.push(`Plugin ${PLUGIN_NAME} (${skillLabel}, 8 agents, 5 hooks)`);
1468
+ const baseTotal = BASE_SKILL_COUNT + DESIGN_SKILL_COUNT;
1469
+ const skillLabel = addonSkillCount > 0 ? `${baseTotal} + ${addonSkillCount} addon skills` : `${baseTotal} skills`;
1470
+ generatedFiles.push(`Plugin ${PLUGIN_NAME} (${skillLabel}, ${BASE_AGENT_COUNT} agents, 5 hooks)`);
1457
1471
  }
1458
1472
  for (const ide of selectedIDEs) {
1459
1473
  const templateName = IDE_TEMPLATE_MAP[ide];
@@ -1601,6 +1615,13 @@ Valid addons: ${validAddons.join(", ")}`);
1601
1615
  ].join("\n"),
1602
1616
  "Next Steps"
1603
1617
  );
1618
+ p3.note(
1619
+ [
1620
+ `${chalk4.dim("Execution mode:")} HITL ${chalk4.dim("(default \u2014 safe for unfamiliar codebases)")}`,
1621
+ `${chalk4.dim("Change anytime:")} ${chalk4.cyan("npx devtronic mode afk")}`
1622
+ ].join("\n"),
1623
+ "Autonomous Mode"
1624
+ );
1604
1625
  p3.outro(chalk4.green("Setup complete!"));
1605
1626
  } catch (err) {
1606
1627
  spinner8.stop("Configuration failed");
@@ -1642,8 +1663,9 @@ async function showPreview(targetDir, selectedIDEs, projectConfig, analysis) {
1642
1663
  fileLines.push(` ${symbols.star} AGENTS.md ${chalk4.dim(`(${agentsMdLines} lines)`)}`);
1643
1664
  if (selectedIDEs.includes("claude-code")) {
1644
1665
  const previewAddonCount = (projectConfig.enabledAddons || []).reduce((sum, a) => sum + (ADDONS[a]?.skills.length ?? 0), 0);
1645
- const previewSkillLabel = previewAddonCount > 0 ? `${BASE_SKILL_COUNT} + ${previewAddonCount} addon skills` : `${BASE_SKILL_COUNT} skills`;
1646
- fileLines.push(` ${symbols.star} Plugin ${chalk4.cyan(PLUGIN_NAME)} ${chalk4.dim(`(${previewSkillLabel}, 8 agents, 5 hooks)`)}`);
1666
+ const previewBaseTotal = BASE_SKILL_COUNT + DESIGN_SKILL_COUNT;
1667
+ const previewSkillLabel = previewAddonCount > 0 ? `${previewBaseTotal} + ${previewAddonCount} addon skills` : `${previewBaseTotal} skills`;
1668
+ fileLines.push(` ${symbols.star} Plugin ${chalk4.cyan(PLUGIN_NAME)} ${chalk4.dim(`(${previewSkillLabel}, ${BASE_AGENT_COUNT} agents, 5 hooks)`)}`);
1647
1669
  }
1648
1670
  const generatedRules = generateArchitectureRules(projectConfig);
1649
1671
  for (const ide of selectedIDEs) {
@@ -1702,8 +1724,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
1702
1724
  }
1703
1725
 
1704
1726
  // src/commands/update.ts
1705
- import { resolve as resolve3, join as join8, dirname as dirname3 } from "path";
1706
- import { existsSync as existsSync6, unlinkSync, lstatSync, readdirSync, rmdirSync, chmodSync as chmodSync2 } from "fs";
1727
+ import { resolve as resolve3, join as join11, dirname as dirname5 } from "path";
1728
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, lstatSync, readdirSync as readdirSync2, rmdirSync as rmdirSync2, chmodSync as chmodSync2 } from "fs";
1707
1729
  import * as p4 from "@clack/prompts";
1708
1730
  import chalk5 from "chalk";
1709
1731
 
@@ -1731,6 +1753,553 @@ var REMOVED_FILES = {
1731
1753
  }
1732
1754
  };
1733
1755
 
1756
+ // src/utils/addonConfig.ts
1757
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync, mkdirSync, renameSync } from "fs";
1758
+ import { join as join9, dirname as dirname3 } from "path";
1759
+
1760
+ // src/addons/registry.ts
1761
+ import { readFileSync as readFileSync2 } from "fs";
1762
+ import { join as join8 } from "path";
1763
+ function getAddonSourceDir(name) {
1764
+ const addonsDir = new URL(".", import.meta.url).pathname;
1765
+ return join8(addonsDir, name);
1766
+ }
1767
+ function getAddonManifest(name) {
1768
+ const sourceDir = getAddonSourceDir(name);
1769
+ const manifestPath = join8(sourceDir, "manifest.json");
1770
+ const raw = readFileSync2(manifestPath, "utf-8");
1771
+ const manifest = JSON.parse(raw);
1772
+ if (!manifest.name) throw new Error(`Addon manifest missing "name": ${manifestPath}`);
1773
+ if (!manifest.version) throw new Error(`Addon manifest missing "version": ${manifestPath}`);
1774
+ if (!manifest.files?.skills?.length) throw new Error(`Addon manifest has empty skills: ${manifestPath}`);
1775
+ return manifest;
1776
+ }
1777
+ function getAvailableAddons() {
1778
+ return Object.values(ADDONS);
1779
+ }
1780
+
1781
+ // src/utils/addonConfig.ts
1782
+ var CONFIG_DIR = ".claude";
1783
+ var CONFIG_FILE = "devtronic.json";
1784
+ function getConfigPath(targetDir) {
1785
+ return join9(targetDir, CONFIG_DIR, CONFIG_FILE);
1786
+ }
1787
+ function getLegacyConfigPath(targetDir) {
1788
+ return join9(targetDir, CONFIG_FILE);
1789
+ }
1790
+ function readAddonConfig(targetDir) {
1791
+ const configPath = getConfigPath(targetDir);
1792
+ const legacyPath = getLegacyConfigPath(targetDir);
1793
+ if (!existsSync6(configPath) && existsSync6(legacyPath)) {
1794
+ mkdirSync(dirname3(configPath), { recursive: true });
1795
+ renameSync(legacyPath, configPath);
1796
+ }
1797
+ if (!existsSync6(configPath)) {
1798
+ return { agents: ["claude"], installed: {} };
1799
+ }
1800
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1801
+ const data = raw.addons ?? raw;
1802
+ return {
1803
+ version: 1,
1804
+ mode: data.mode,
1805
+ agents: data.agents ?? ["claude"],
1806
+ installed: data.installed ?? {}
1807
+ };
1808
+ }
1809
+ function writeAddonConfig(targetDir, config) {
1810
+ const configPath = getConfigPath(targetDir);
1811
+ mkdirSync(dirname3(configPath), { recursive: true });
1812
+ const payload = { version: 1, ...config };
1813
+ writeFileSync(configPath, JSON.stringify(payload, null, 2) + "\n");
1814
+ }
1815
+ function writeAddonToConfig(targetDir, name, entry) {
1816
+ const config = readAddonConfig(targetDir);
1817
+ config.installed[name] = entry;
1818
+ writeAddonConfig(targetDir, config);
1819
+ }
1820
+ function removeAddonFromConfig(targetDir, name) {
1821
+ const config = readAddonConfig(targetDir);
1822
+ delete config.installed[name];
1823
+ writeAddonConfig(targetDir, config);
1824
+ }
1825
+ function readMode(targetDir) {
1826
+ const config = readAddonConfig(targetDir);
1827
+ return config.mode ?? "hitl";
1828
+ }
1829
+ function writeMode(targetDir, mode) {
1830
+ const config = readAddonConfig(targetDir);
1831
+ config.mode = mode;
1832
+ writeAddonConfig(targetDir, config);
1833
+ }
1834
+ function detectOrphanedAddonFiles(targetDir) {
1835
+ const config = readAddonConfig(targetDir);
1836
+ const orphaned = [];
1837
+ for (const addon of getAvailableAddons()) {
1838
+ if (config.installed[addon.name]) continue;
1839
+ const skillDir = join9(targetDir, ".claude", "skills", addon.name);
1840
+ if (existsSync6(skillDir)) {
1841
+ orphaned.push(addon.name);
1842
+ }
1843
+ }
1844
+ return orphaned;
1845
+ }
1846
+ function registerAddonInConfig(targetDir, addonName) {
1847
+ const manifest = getAddonManifest(addonName);
1848
+ const fileList = [
1849
+ ...(manifest.files.skills ?? []).map((s) => `skills/${s}`),
1850
+ ...(manifest.files.agents ?? []).map((a) => `agents/${a}.md`),
1851
+ ...(manifest.files.rules ?? []).map((r) => `rules/${r}`)
1852
+ ];
1853
+ writeAddonToConfig(targetDir, addonName, {
1854
+ version: manifest.version,
1855
+ files: fileList
1856
+ });
1857
+ }
1858
+
1859
+ // src/generators/addonFiles.ts
1860
+ import {
1861
+ existsSync as existsSync7,
1862
+ readFileSync as readFileSync4,
1863
+ writeFileSync as writeFileSync2,
1864
+ mkdirSync as mkdirSync2,
1865
+ rmSync,
1866
+ unlinkSync,
1867
+ readdirSync,
1868
+ rmdirSync
1869
+ } from "fs";
1870
+ import { join as join10, dirname as dirname4 } from "path";
1871
+ import { createHash } from "crypto";
1872
+ var AGENT_PATHS = {
1873
+ claude: ".claude",
1874
+ cursor: ".cursor",
1875
+ gemini: ".gemini"
1876
+ };
1877
+ function stripFrontmatterName(content) {
1878
+ return content.replace(/^(---\n)([\s\S]*?)(---)/m, (_match, open, body, close) => {
1879
+ const cleaned = body.split("\n").filter((l) => !l.startsWith("name:")).join("\n");
1880
+ return `${open}${cleaned}${close}`;
1881
+ });
1882
+ }
1883
+ var RUNTIME_SPECS = {
1884
+ claude: {
1885
+ baseDir: ".claude",
1886
+ skillAdapter: (name, content) => ({
1887
+ relPath: `skills/${name}/SKILL.md`,
1888
+ content
1889
+ }),
1890
+ rulesDir: "rules"
1891
+ },
1892
+ gemini: {
1893
+ baseDir: ".gemini",
1894
+ skillAdapter: (name, content) => ({
1895
+ relPath: `skills/${name}/SKILL.md`,
1896
+ content
1897
+ }),
1898
+ rulesDir: "rules"
1899
+ },
1900
+ opencode: {
1901
+ baseDir: ".opencode",
1902
+ skillAdapter: (name, content) => ({
1903
+ relPath: `command/${name}.md`,
1904
+ content: stripFrontmatterName(content)
1905
+ })
1906
+ },
1907
+ cursor: {
1908
+ baseDir: ".cursor",
1909
+ skillAdapter: (name, content) => ({
1910
+ relPath: `skills/${name}/SKILL.md`,
1911
+ content
1912
+ }),
1913
+ rulesDir: "rules"
1914
+ },
1915
+ codex: {
1916
+ baseDir: ".codex",
1917
+ skillAdapter: (name, content) => ({
1918
+ relPath: `skills/${name}/SKILL.md`,
1919
+ content
1920
+ })
1921
+ }
1922
+ };
1923
+ function checksum(content) {
1924
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
1925
+ }
1926
+ function ensureDir2(dir) {
1927
+ if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1928
+ }
1929
+ function readManifest2(addonSourceDir) {
1930
+ const manifestPath = join10(addonSourceDir, "manifest.json");
1931
+ return JSON.parse(readFileSync4(manifestPath, "utf-8"));
1932
+ }
1933
+ function buildFileMap(addonSourceDir) {
1934
+ const manifest = readManifest2(addonSourceDir);
1935
+ const files = /* @__PURE__ */ new Map();
1936
+ for (const skill of manifest.files.skills ?? []) {
1937
+ const skillDir = join10(addonSourceDir, "skills", skill);
1938
+ if (!existsSync7(skillDir)) continue;
1939
+ const skillFile = join10(skillDir, "SKILL.md");
1940
+ if (existsSync7(skillFile)) {
1941
+ files.set(`skills/${skill}/SKILL.md`, readFileSync4(skillFile, "utf-8"));
1942
+ }
1943
+ }
1944
+ for (const agent of manifest.files.agents ?? []) {
1945
+ const agentFile = join10(addonSourceDir, "agents", `${agent}.md`);
1946
+ if (existsSync7(agentFile)) {
1947
+ files.set(`agents/${agent}.md`, readFileSync4(agentFile, "utf-8"));
1948
+ }
1949
+ }
1950
+ for (const ref of manifest.files.reference ?? []) {
1951
+ const refPath = join10(addonSourceDir, "reference", ref);
1952
+ if (existsSync7(refPath)) {
1953
+ files.set(`skills/design-harden/reference/${ref}`, readFileSync4(refPath, "utf-8"));
1954
+ }
1955
+ }
1956
+ for (const rule of manifest.files.rules ?? []) {
1957
+ const rulePath = join10(addonSourceDir, "rules", rule);
1958
+ if (existsSync7(rulePath)) {
1959
+ files.set(`rules/${rule}`, readFileSync4(rulePath, "utf-8"));
1960
+ }
1961
+ }
1962
+ return files;
1963
+ }
1964
+ function generateAddonFiles(projectDir, addonSourceDir, agents) {
1965
+ const fileMap = buildFileMap(addonSourceDir);
1966
+ const manifest = readManifest2(addonSourceDir);
1967
+ const result = { written: 0, skipped: 0, conflicts: [], checksums: {} };
1968
+ for (const agent of agents) {
1969
+ const spec = RUNTIME_SPECS[agent];
1970
+ if (!spec) {
1971
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
1972
+ for (const [relPath, content] of fileMap) {
1973
+ const destPath = join10(projectDir, basePath, relPath);
1974
+ if (existsSync7(destPath)) {
1975
+ result.skipped++;
1976
+ continue;
1977
+ }
1978
+ ensureDir2(dirname4(destPath));
1979
+ writeFileSync2(destPath, content);
1980
+ result.written++;
1981
+ result.checksums[relPath] = checksum(content);
1982
+ }
1983
+ continue;
1984
+ }
1985
+ for (const skillName of manifest.files.skills ?? []) {
1986
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
1987
+ if (!existsSync7(skillFile)) continue;
1988
+ const rawContent = readFileSync4(skillFile, "utf-8");
1989
+ const { relPath, content } = spec.skillAdapter(skillName, rawContent);
1990
+ const destPath = join10(projectDir, spec.baseDir, relPath);
1991
+ if (existsSync7(destPath)) {
1992
+ const existing = readFileSync4(destPath, "utf-8");
1993
+ if (existing === content) {
1994
+ result.skipped++;
1995
+ continue;
1996
+ }
1997
+ result.skipped++;
1998
+ continue;
1999
+ }
2000
+ ensureDir2(dirname4(destPath));
2001
+ writeFileSync2(destPath, content);
2002
+ result.written++;
2003
+ result.checksums[relPath] = checksum(content);
2004
+ }
2005
+ for (const agentName of manifest.files.agents ?? []) {
2006
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2007
+ if (!existsSync7(agentFile)) continue;
2008
+ const content = readFileSync4(agentFile, "utf-8");
2009
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2010
+ if (!existsSync7(destPath)) {
2011
+ ensureDir2(dirname4(destPath));
2012
+ writeFileSync2(destPath, content);
2013
+ result.written++;
2014
+ result.checksums[`agents/${agentName}.md`] = checksum(content);
2015
+ } else {
2016
+ result.skipped++;
2017
+ }
2018
+ }
2019
+ if (spec.rulesDir) {
2020
+ for (const rule of manifest.files.rules ?? []) {
2021
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2022
+ if (!existsSync7(ruleSrcPath)) continue;
2023
+ const content = readFileSync4(ruleSrcPath, "utf-8");
2024
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2025
+ if (!existsSync7(destPath)) {
2026
+ ensureDir2(dirname4(destPath));
2027
+ writeFileSync2(destPath, content);
2028
+ result.written++;
2029
+ result.checksums[`rules/${rule}`] = checksum(content);
2030
+ } else {
2031
+ result.skipped++;
2032
+ }
2033
+ }
2034
+ }
2035
+ for (const ref of manifest.files.reference ?? []) {
2036
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2037
+ if (!existsSync7(refSrcPath)) continue;
2038
+ const content = readFileSync4(refSrcPath, "utf-8");
2039
+ const relPath = `skills/design-harden/reference/${ref}`;
2040
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2041
+ if (!existsSync7(destPath)) {
2042
+ ensureDir2(dirname4(destPath));
2043
+ writeFileSync2(destPath, content);
2044
+ result.written++;
2045
+ result.checksums[relPath] = checksum(content);
2046
+ } else {
2047
+ result.skipped++;
2048
+ }
2049
+ }
2050
+ }
2051
+ if (manifest.attribution) {
2052
+ const noticePath = join10(projectDir, "NOTICE.md");
2053
+ const noticeContent = [
2054
+ "# NOTICE",
2055
+ "",
2056
+ "This project includes materials from third-party sources.",
2057
+ "",
2058
+ `## ${manifest.name}`,
2059
+ "",
2060
+ manifest.attribution,
2061
+ "",
2062
+ "Original source: Anthropic frontend-design skill",
2063
+ "License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)",
2064
+ ""
2065
+ ].join("\n");
2066
+ writeFileSync2(noticePath, noticeContent);
2067
+ }
2068
+ return result;
2069
+ }
2070
+ function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
2071
+ const sourceDir = addonSourceDir ?? getAddonSourceDir(addonName);
2072
+ const manifest = readManifest2(sourceDir);
2073
+ const knownSkills = manifest.files.skills ?? [];
2074
+ const knownAgents = manifest.files.agents ?? [];
2075
+ for (const agent of agents) {
2076
+ const spec = RUNTIME_SPECS[agent];
2077
+ if (!spec) {
2078
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2079
+ for (const skill of knownSkills) {
2080
+ const skillDir = join10(projectDir, basePath, "skills", skill);
2081
+ if (existsSync7(skillDir)) rmSync(skillDir, { recursive: true, force: true });
2082
+ }
2083
+ for (const agentName of knownAgents) {
2084
+ const agentPath = join10(projectDir, basePath, "agents", `${agentName}.md`);
2085
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2086
+ }
2087
+ continue;
2088
+ }
2089
+ for (const skillName of knownSkills) {
2090
+ const { relPath } = spec.skillAdapter(skillName, "");
2091
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2092
+ if (existsSync7(destPath)) unlinkSync(destPath);
2093
+ const parentDir = dirname4(destPath);
2094
+ if (existsSync7(parentDir)) {
2095
+ try {
2096
+ const entries = readdirSync(parentDir);
2097
+ if (entries.length === 0) rmdirSync(parentDir);
2098
+ } catch {
2099
+ }
2100
+ }
2101
+ }
2102
+ for (const agentName of knownAgents) {
2103
+ const agentPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2104
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2105
+ }
2106
+ if (spec.rulesDir) {
2107
+ for (const rule of manifest.files.rules ?? []) {
2108
+ const rulePath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2109
+ if (existsSync7(rulePath)) unlinkSync(rulePath);
2110
+ }
2111
+ }
2112
+ for (const ref of manifest.files.reference ?? []) {
2113
+ const refPath = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference", ref);
2114
+ if (existsSync7(refPath)) unlinkSync(refPath);
2115
+ }
2116
+ const refDir = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference");
2117
+ if (existsSync7(refDir)) {
2118
+ try {
2119
+ const entries = readdirSync(refDir);
2120
+ if (entries.length === 0) rmdirSync(refDir);
2121
+ } catch {
2122
+ }
2123
+ }
2124
+ }
2125
+ const noticePath = join10(projectDir, "NOTICE.md");
2126
+ if (existsSync7(noticePath)) unlinkSync(noticePath);
2127
+ }
2128
+ function syncAddonFiles(projectDir, addonSourceDir, agents) {
2129
+ const fileMap = buildFileMap(addonSourceDir);
2130
+ const manifest = readManifest2(addonSourceDir);
2131
+ const addonName = manifest.name;
2132
+ const result = { written: 0, skipped: 0, conflicts: [], updated: 0 };
2133
+ let installedChecksums = {};
2134
+ try {
2135
+ const config = readAddonConfig(projectDir);
2136
+ const installed = config.installed?.[addonName];
2137
+ if (installed?.checksums) {
2138
+ installedChecksums = installed.checksums;
2139
+ }
2140
+ } catch {
2141
+ }
2142
+ for (const agent of agents) {
2143
+ const spec = RUNTIME_SPECS[agent];
2144
+ if (!spec) {
2145
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2146
+ for (const [relPath, newContent] of fileMap) {
2147
+ const destPath = join10(projectDir, basePath, relPath);
2148
+ if (!existsSync7(destPath)) {
2149
+ ensureDir2(dirname4(destPath));
2150
+ writeFileSync2(destPath, newContent);
2151
+ result.written++;
2152
+ continue;
2153
+ }
2154
+ const existing = readFileSync4(destPath, "utf-8");
2155
+ const existingChecksum = checksum(existing);
2156
+ const originalChecksum = installedChecksums[relPath];
2157
+ if (existing === newContent) {
2158
+ result.skipped++;
2159
+ continue;
2160
+ }
2161
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2162
+ result.conflicts.push(relPath);
2163
+ continue;
2164
+ }
2165
+ writeFileSync2(destPath, newContent);
2166
+ result.updated = (result.updated ?? 0) + 1;
2167
+ }
2168
+ continue;
2169
+ }
2170
+ for (const skillName of manifest.files.skills ?? []) {
2171
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
2172
+ if (!existsSync7(skillFile)) continue;
2173
+ const rawContent = readFileSync4(skillFile, "utf-8");
2174
+ const { relPath, content: newContent } = spec.skillAdapter(skillName, rawContent);
2175
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2176
+ if (!existsSync7(destPath)) {
2177
+ ensureDir2(dirname4(destPath));
2178
+ writeFileSync2(destPath, newContent);
2179
+ result.written++;
2180
+ continue;
2181
+ }
2182
+ const existing = readFileSync4(destPath, "utf-8");
2183
+ const existingChecksum = checksum(existing);
2184
+ const originalChecksum = installedChecksums[relPath];
2185
+ if (existing === newContent) {
2186
+ result.skipped++;
2187
+ continue;
2188
+ }
2189
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2190
+ result.conflicts.push(relPath);
2191
+ continue;
2192
+ }
2193
+ writeFileSync2(destPath, newContent);
2194
+ result.updated = (result.updated ?? 0) + 1;
2195
+ }
2196
+ for (const agentName of manifest.files.agents ?? []) {
2197
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2198
+ if (!existsSync7(agentFile)) continue;
2199
+ const newContent = readFileSync4(agentFile, "utf-8");
2200
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2201
+ const relPath = `agents/${agentName}.md`;
2202
+ if (!existsSync7(destPath)) {
2203
+ ensureDir2(dirname4(destPath));
2204
+ writeFileSync2(destPath, newContent);
2205
+ result.written++;
2206
+ continue;
2207
+ }
2208
+ const existing = readFileSync4(destPath, "utf-8");
2209
+ const existingChecksum = checksum(existing);
2210
+ const originalChecksum = installedChecksums[relPath];
2211
+ if (existing === newContent) {
2212
+ result.skipped++;
2213
+ continue;
2214
+ }
2215
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2216
+ result.conflicts.push(relPath);
2217
+ continue;
2218
+ }
2219
+ writeFileSync2(destPath, newContent);
2220
+ result.updated = (result.updated ?? 0) + 1;
2221
+ }
2222
+ if (spec.rulesDir) {
2223
+ for (const rule of manifest.files.rules ?? []) {
2224
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2225
+ if (!existsSync7(ruleSrcPath)) continue;
2226
+ const newContent = readFileSync4(ruleSrcPath, "utf-8");
2227
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2228
+ const relPath = `rules/${rule}`;
2229
+ if (!existsSync7(destPath)) {
2230
+ ensureDir2(dirname4(destPath));
2231
+ writeFileSync2(destPath, newContent);
2232
+ result.written++;
2233
+ continue;
2234
+ }
2235
+ const existing = readFileSync4(destPath, "utf-8");
2236
+ const existingChecksum = checksum(existing);
2237
+ const originalChecksum = installedChecksums[relPath];
2238
+ if (existing === newContent) {
2239
+ result.skipped++;
2240
+ continue;
2241
+ }
2242
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2243
+ result.conflicts.push(relPath);
2244
+ continue;
2245
+ }
2246
+ writeFileSync2(destPath, newContent);
2247
+ result.updated = (result.updated ?? 0) + 1;
2248
+ }
2249
+ }
2250
+ for (const ref of manifest.files.reference ?? []) {
2251
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2252
+ if (!existsSync7(refSrcPath)) continue;
2253
+ const newContent = readFileSync4(refSrcPath, "utf-8");
2254
+ const relPath = `skills/design-harden/reference/${ref}`;
2255
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2256
+ if (!existsSync7(destPath)) {
2257
+ ensureDir2(dirname4(destPath));
2258
+ writeFileSync2(destPath, newContent);
2259
+ result.written++;
2260
+ continue;
2261
+ }
2262
+ const existing = readFileSync4(destPath, "utf-8");
2263
+ const existingChecksum = checksum(existing);
2264
+ const originalChecksum = installedChecksums[relPath];
2265
+ if (existing === newContent) {
2266
+ result.skipped++;
2267
+ continue;
2268
+ }
2269
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2270
+ result.conflicts.push(relPath);
2271
+ continue;
2272
+ }
2273
+ writeFileSync2(destPath, newContent);
2274
+ result.updated = (result.updated ?? 0) + 1;
2275
+ }
2276
+ }
2277
+ return result;
2278
+ }
2279
+ function detectModifiedAddonFiles(projectDir, addonName) {
2280
+ let installedChecksums = {};
2281
+ try {
2282
+ const config = readAddonConfig(projectDir);
2283
+ const installed = config.installed?.[addonName];
2284
+ if (!installed?.checksums) return [];
2285
+ installedChecksums = installed.checksums;
2286
+ } catch {
2287
+ return [];
2288
+ }
2289
+ const modified = [];
2290
+ for (const agentDir of Object.values(AGENT_PATHS)) {
2291
+ for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
2292
+ const absPath = join10(projectDir, agentDir, relPath);
2293
+ if (!existsSync7(absPath)) continue;
2294
+ const current = checksum(readFileSync4(absPath, "utf-8"));
2295
+ if (current !== originalHash) {
2296
+ modified.push(relPath);
2297
+ }
2298
+ }
2299
+ }
2300
+ return [...new Set(modified)];
2301
+ }
2302
+
1734
2303
  // src/commands/update.ts
1735
2304
  async function updateCommand(options) {
1736
2305
  if (!options.check && !options.dryRun) {
@@ -1796,7 +2365,7 @@ async function updateCommand(options) {
1796
2365
  const modifiedFiles = [];
1797
2366
  const outdatedFiles = [];
1798
2367
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
1799
- const filePath = join8(targetDir, relativePath);
2368
+ const filePath = join11(targetDir, relativePath);
1800
2369
  if (!fileExists(filePath)) {
1801
2370
  continue;
1802
2371
  }
@@ -1806,17 +2375,20 @@ async function updateCommand(options) {
1806
2375
  modifiedFiles.push(relativePath);
1807
2376
  }
1808
2377
  }
2378
+ const newFiles = [];
1809
2379
  for (const ide of manifest.selectedIDEs) {
1810
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1811
- if (!existsSync6(templateDir)) continue;
2380
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2381
+ if (!existsSync8(templateDir)) continue;
1812
2382
  const files = getAllFilesRecursive(templateDir);
1813
2383
  for (const file of files) {
1814
- const templatePath = join8(templateDir, file);
2384
+ const templatePath = join11(templateDir, file);
1815
2385
  const templateContent = readFile(templatePath);
1816
2386
  const templateChecksum = calculateChecksum(templateContent);
1817
2387
  const fileInfo = manifest.files[file];
1818
2388
  if (fileInfo && fileInfo.checksum !== templateChecksum) {
1819
2389
  outdatedFiles.push(file);
2390
+ } else if (!fileInfo) {
2391
+ newFiles.push(file);
1820
2392
  }
1821
2393
  }
1822
2394
  }
@@ -1825,14 +2397,14 @@ async function updateCommand(options) {
1825
2397
  if (fileInfo.ignored) continue;
1826
2398
  let foundInAnyTemplate = false;
1827
2399
  for (const ide of manifest.selectedIDEs) {
1828
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1829
- if (existsSync6(join8(templateDir, relativePath))) {
2400
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2401
+ if (existsSync8(join11(templateDir, relativePath))) {
1830
2402
  foundInAnyTemplate = true;
1831
2403
  break;
1832
2404
  }
1833
2405
  }
1834
2406
  if (!foundInAnyTemplate) {
1835
- const localPath = join8(targetDir, relativePath);
2407
+ const localPath = join11(targetDir, relativePath);
1836
2408
  if (fileExists(localPath)) {
1837
2409
  removedFromTemplate.push({
1838
2410
  path: relativePath,
@@ -1847,7 +2419,7 @@ async function updateCommand(options) {
1847
2419
  "Locally Modified Files (will be preserved)"
1848
2420
  );
1849
2421
  }
1850
- if (outdatedFiles.length === 0 && removedFromTemplate.length === 0) {
2422
+ if (outdatedFiles.length === 0 && removedFromTemplate.length === 0 && newFiles.length === 0) {
1851
2423
  p4.log.success("All files are up to date!");
1852
2424
  p4.outro("No updates needed");
1853
2425
  return;
@@ -1859,6 +2431,12 @@ async function updateCommand(options) {
1859
2431
  "Files to Update"
1860
2432
  );
1861
2433
  }
2434
+ if (newFiles.length > 0) {
2435
+ p4.note(
2436
+ newFiles.map((f) => ` ${symbols.star} ${f}`).join("\n"),
2437
+ "New Files in This Version"
2438
+ );
2439
+ }
1862
2440
  const conflictFiles = outdatedFiles.filter((f) => modifiedFiles.includes(f));
1863
2441
  if (conflictFiles.length > 0) {
1864
2442
  p4.note(
@@ -1925,15 +2503,17 @@ async function updateCommand(options) {
1925
2503
  return;
1926
2504
  }
1927
2505
  const hasUpdates = filesToUpdate.length > 0;
2506
+ const hasNewFiles = newFiles.length > 0;
1928
2507
  const hasDeletions = filesToDelete.length > 0;
1929
2508
  const hasIgnores = filesToIgnore.length > 0;
1930
- if (!hasUpdates && !hasDeletions && !hasIgnores) {
2509
+ if (!hasUpdates && !hasNewFiles && !hasDeletions && !hasIgnores) {
1931
2510
  p4.log.success("No changes to apply");
1932
2511
  p4.outro("Update complete");
1933
2512
  return;
1934
2513
  }
1935
2514
  const actionParts = [];
1936
2515
  if (hasUpdates) actionParts.push(`update ${filesToUpdate.length} files`);
2516
+ if (hasNewFiles) actionParts.push(`add ${newFiles.length} new files`);
1937
2517
  if (hasDeletions) actionParts.push(`delete ${filesToDelete.length} files`);
1938
2518
  if (hasIgnores) actionParts.push(`ignore ${filesToIgnore.length} files`);
1939
2519
  const proceed = await p4.confirm({
@@ -1952,8 +2532,8 @@ async function updateCommand(options) {
1952
2532
  implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1953
2533
  };
1954
2534
  for (const ide of manifest.selectedIDEs) {
1955
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1956
- if (!existsSync6(templateDir)) continue;
2535
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2536
+ if (!existsSync8(templateDir)) continue;
1957
2537
  const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
1958
2538
  const files = getAllFilesRecursive(templateDir);
1959
2539
  for (const file of files) {
@@ -1963,10 +2543,10 @@ async function updateCommand(options) {
1963
2543
  if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
1964
2544
  continue;
1965
2545
  }
1966
- const templatePath = join8(templateDir, file);
1967
- const destPath = join8(targetDir, file);
2546
+ const templatePath = join11(templateDir, file);
2547
+ const destPath = join11(targetDir, file);
1968
2548
  const templateContent = readFile(templatePath);
1969
- ensureDir(dirname3(destPath));
2549
+ ensureDir(dirname5(destPath));
1970
2550
  writeFile(destPath, templateContent);
1971
2551
  updatedManifest.files[file] = createManifestEntry(templateContent);
1972
2552
  }
@@ -1974,7 +2554,7 @@ async function updateCommand(options) {
1974
2554
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
1975
2555
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
1976
2556
  for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
1977
- const filePath = join8(targetDir, relPath);
2557
+ const filePath = join11(targetDir, relPath);
1978
2558
  if (!fileExists(filePath)) continue;
1979
2559
  const diskContent = readFile(filePath);
1980
2560
  const diskChecksum = calculateChecksum(diskContent);
@@ -1991,13 +2571,13 @@ async function updateCommand(options) {
1991
2571
  analysis.packageManager
1992
2572
  );
1993
2573
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
1994
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
1995
- if (existsSync6(scriptPath)) {
2574
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2575
+ if (existsSync8(scriptPath)) {
1996
2576
  chmodSync2(scriptPath, 493);
1997
2577
  }
1998
2578
  }
1999
2579
  for (const [relPath, content] of userModifiedPluginFiles) {
2000
- writeFile(join8(targetDir, relPath), content);
2580
+ writeFile(join11(targetDir, relPath), content);
2001
2581
  }
2002
2582
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2003
2583
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2008,9 +2588,9 @@ async function updateCommand(options) {
2008
2588
  updatedManifest.pluginPath = pluginResult.pluginPath;
2009
2589
  }
2010
2590
  for (const filePath of filesToDelete) {
2011
- const localPath = join8(targetDir, filePath);
2591
+ const localPath = join11(targetDir, filePath);
2012
2592
  if (fileExists(localPath)) {
2013
- unlinkSync(localPath);
2593
+ unlinkSync2(localPath);
2014
2594
  }
2015
2595
  delete updatedManifest.files[filePath];
2016
2596
  }
@@ -2019,12 +2599,12 @@ async function updateCommand(options) {
2019
2599
  updatedManifest.files[filePath].ignored = true;
2020
2600
  }
2021
2601
  }
2022
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2602
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2023
2603
  if (fileExists(claudeMdPath)) {
2024
2604
  const stat = lstatSync(claudeMdPath);
2025
2605
  if (stat.isSymbolicLink()) {
2026
2606
  const content = readFile(claudeMdPath);
2027
- unlinkSync(claudeMdPath);
2607
+ unlinkSync2(claudeMdPath);
2028
2608
  writeFile(claudeMdPath, content);
2029
2609
  updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
2030
2610
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2032,6 +2612,28 @@ async function updateCommand(options) {
2032
2612
  }
2033
2613
  writeManifest(targetDir, updatedManifest);
2034
2614
  spinner8.stop("Updates applied");
2615
+ const orphaned = detectOrphanedAddonFiles(targetDir);
2616
+ if (orphaned.length > 0) {
2617
+ const migSpinner = p4.spinner();
2618
+ migSpinner.start("Migrating detected addon files to config...");
2619
+ for (const addonName of orphaned) {
2620
+ registerAddonInConfig(targetDir, addonName);
2621
+ }
2622
+ migSpinner.stop(`${symbols.pass} Migrated: ${orphaned.join(", ")}`);
2623
+ }
2624
+ const addonConfig = readAddonConfig(targetDir);
2625
+ const enabledAddons = Object.keys(addonConfig.installed);
2626
+ if (enabledAddons.length > 0 && !options.check) {
2627
+ const syncSpinner = p4.spinner();
2628
+ syncSpinner.start("Updating enabled addon files...");
2629
+ let totalUpdated = 0;
2630
+ for (const name of enabledAddons) {
2631
+ const addonSourceDir = getAddonSourceDir(name);
2632
+ const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
2633
+ totalUpdated += (result.updated ?? 0) + result.written;
2634
+ }
2635
+ syncSpinner.stop(`${symbols.pass} Addon files updated (${totalUpdated} files)`);
2636
+ }
2035
2637
  p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
2036
2638
  }
2037
2639
  function detectStackChanges(savedConfig, analysis) {
@@ -2138,7 +2740,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2138
2740
  const spinner8 = p4.spinner();
2139
2741
  spinner8.start("Regenerating with new stack...");
2140
2742
  const regeneratedFiles = [];
2141
- const agentsMdPath = join8(targetDir, "AGENTS.md");
2743
+ const agentsMdPath = join11(targetDir, "AGENTS.md");
2142
2744
  if (fileExists(agentsMdPath)) {
2143
2745
  const agentsMdContent = generateAgentsMdFromConfig(
2144
2746
  newConfig,
@@ -2149,12 +2751,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2149
2751
  regeneratedFiles.push("AGENTS.md");
2150
2752
  manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
2151
2753
  }
2152
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2754
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2153
2755
  if (fileExists(claudeMdPath)) {
2154
2756
  const stat = lstatSync(claudeMdPath);
2155
2757
  if (stat.isSymbolicLink()) {
2156
2758
  const content = readFile(claudeMdPath);
2157
- unlinkSync(claudeMdPath);
2759
+ unlinkSync2(claudeMdPath);
2158
2760
  writeFile(claudeMdPath, content);
2159
2761
  manifest.files["CLAUDE.md"] = createManifestEntry(content);
2160
2762
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2166,8 +2768,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2166
2768
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2167
2769
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2168
2770
  if (ruleContent && rulePath) {
2169
- const destPath = join8(targetDir, rulePath);
2170
- ensureDir(dirname3(destPath));
2771
+ const destPath = join11(targetDir, rulePath);
2772
+ ensureDir(dirname5(destPath));
2171
2773
  writeFile(destPath, ruleContent);
2172
2774
  regeneratedFiles.push(rulePath);
2173
2775
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2177,7 +2779,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2177
2779
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
2178
2780
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
2179
2781
  for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
2180
- const filePath = join8(targetDir, relPath);
2782
+ const filePath = join11(targetDir, relPath);
2181
2783
  if (!fileExists(filePath)) continue;
2182
2784
  const diskContent = readFile(filePath);
2183
2785
  const diskChecksum = calculateChecksum(diskContent);
@@ -2193,13 +2795,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2193
2795
  analysis.packageManager
2194
2796
  );
2195
2797
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2196
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2197
- if (existsSync6(scriptPath)) {
2798
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2799
+ if (existsSync8(scriptPath)) {
2198
2800
  chmodSync2(scriptPath, 493);
2199
2801
  }
2200
2802
  }
2201
2803
  for (const [relPath, content] of userModifiedPluginFiles) {
2202
- writeFile(join8(targetDir, relPath), content);
2804
+ writeFile(join11(targetDir, relPath), content);
2203
2805
  }
2204
2806
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2205
2807
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2242,8 +2844,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2242
2844
  analysis.packageManager
2243
2845
  );
2244
2846
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2245
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2246
- if (existsSync6(scriptPath)) {
2847
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2848
+ if (existsSync8(scriptPath)) {
2247
2849
  chmodSync2(scriptPath, 493);
2248
2850
  }
2249
2851
  }
@@ -2252,19 +2854,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2252
2854
  const preserved = [];
2253
2855
  for (const [path, fileInfo] of Object.entries(manifest.files)) {
2254
2856
  if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
2255
- const filePath = join8(targetDir, path);
2857
+ const filePath = join11(targetDir, path);
2256
2858
  if (!fileExists(filePath)) continue;
2257
2859
  const current = calculateChecksum(readFile(filePath));
2258
2860
  if (current === fileInfo.originalChecksum) {
2259
- unlinkSync(filePath);
2861
+ unlinkSync2(filePath);
2260
2862
  delete manifest.files[path];
2261
2863
  removed.push(path);
2262
2864
  } else {
2263
2865
  preserved.push(path);
2264
2866
  }
2265
2867
  }
2266
- cleanEmptyDirs(join8(targetDir, ".claude", "skills"));
2267
- cleanEmptyDirs(join8(targetDir, ".claude", "agents"));
2868
+ cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
2869
+ cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
2268
2870
  Object.assign(manifest.files, pluginResult.files);
2269
2871
  manifest.installMode = "plugin";
2270
2872
  manifest.pluginPath = pluginResult.pluginPath;
@@ -2313,22 +2915,22 @@ function buildDefaultConfig(analysis) {
2313
2915
  };
2314
2916
  }
2315
2917
  function cleanEmptyDirs(dirPath) {
2316
- if (!existsSync6(dirPath)) return;
2317
- const entries = readdirSync(dirPath);
2918
+ if (!existsSync8(dirPath)) return;
2919
+ const entries = readdirSync2(dirPath);
2318
2920
  for (const entry of entries) {
2319
- const fullPath = join8(dirPath, entry);
2320
- if (existsSync6(fullPath) && lstatSync(fullPath).isDirectory()) {
2921
+ const fullPath = join11(dirPath, entry);
2922
+ if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
2321
2923
  cleanEmptyDirs(fullPath);
2322
2924
  }
2323
2925
  }
2324
- const remaining = readdirSync(dirPath);
2926
+ const remaining = readdirSync2(dirPath);
2325
2927
  if (remaining.length === 0) {
2326
- rmdirSync(dirPath);
2928
+ rmdirSync2(dirPath);
2327
2929
  }
2328
2930
  }
2329
2931
 
2330
2932
  // src/commands/status.ts
2331
- import { resolve as resolve4, join as join9 } from "path";
2933
+ import { resolve as resolve4, join as join12 } from "path";
2332
2934
  import * as p5 from "@clack/prompts";
2333
2935
  import chalk6 from "chalk";
2334
2936
  async function statusCommand(options = {}) {
@@ -2342,6 +2944,22 @@ async function statusCommand(options = {}) {
2342
2944
  p5.outro("");
2343
2945
  return;
2344
2946
  }
2947
+ const mode = readMode(targetDir);
2948
+ const addonConfig = readAddonConfig(targetDir);
2949
+ const allAddons = getAvailableAddons();
2950
+ p5.note(
2951
+ [
2952
+ `${formatKV("Mode:", chalk6.cyan(mode))}`,
2953
+ `${formatKV("Config:", chalk6.dim(".claude/devtronic.json"))}`
2954
+ ].join("\n"),
2955
+ "Execution Mode"
2956
+ );
2957
+ const addonLines = allAddons.map((addon) => {
2958
+ const isEnabled = !!addonConfig.installed[addon.name];
2959
+ const statusStr = isEnabled ? chalk6.green("enabled") : chalk6.dim("disabled");
2960
+ return ` ${chalk6.bold((addon.name + ":").padEnd(28))} ${statusStr}`;
2961
+ });
2962
+ p5.note(addonLines.join("\n"), "Addons");
2345
2963
  p5.note(
2346
2964
  [
2347
2965
  formatKV("Version:", manifest.version),
@@ -2352,7 +2970,7 @@ async function statusCommand(options = {}) {
2352
2970
  );
2353
2971
  const fileStatuses = [];
2354
2972
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
2355
- const filePath = join9(targetDir, relativePath);
2973
+ const filePath = join12(targetDir, relativePath);
2356
2974
  if (!fileExists(filePath)) {
2357
2975
  fileStatuses.push({ path: relativePath, status: "missing" });
2358
2976
  continue;
@@ -2398,8 +3016,8 @@ async function statusCommand(options = {}) {
2398
3016
  }
2399
3017
 
2400
3018
  // src/commands/diff.ts
2401
- import { resolve as resolve5, join as join10 } from "path";
2402
- import { existsSync as existsSync7 } from "fs";
3019
+ import { resolve as resolve5, join as join13 } from "path";
3020
+ import { existsSync as existsSync9 } from "fs";
2403
3021
  import * as p6 from "@clack/prompts";
2404
3022
  import chalk7 from "chalk";
2405
3023
  async function diffCommand(options = {}) {
@@ -2414,12 +3032,12 @@ async function diffCommand(options = {}) {
2414
3032
  }
2415
3033
  const diffs = [];
2416
3034
  for (const ide of manifest.selectedIDEs) {
2417
- const templateDir = join10(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2418
- if (!existsSync7(templateDir)) continue;
3035
+ const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
3036
+ if (!existsSync9(templateDir)) continue;
2419
3037
  const templateFiles = getAllFilesRecursive(templateDir);
2420
3038
  for (const file of templateFiles) {
2421
- const templatePath = join10(templateDir, file);
2422
- const localPath = join10(targetDir, file);
3039
+ const templatePath = join13(templateDir, file);
3040
+ const localPath = join13(targetDir, file);
2423
3041
  const templateContent = readFile(templatePath);
2424
3042
  const templateChecksum = calculateChecksum(templateContent);
2425
3043
  const manifestEntry = manifest.files[file];
@@ -2472,8 +3090,8 @@ async function diffCommand(options = {}) {
2472
3090
  }
2473
3091
 
2474
3092
  // src/commands/add.ts
2475
- import { existsSync as existsSync8 } from "fs";
2476
- import { resolve as resolve6, join as join11, dirname as dirname4 } from "path";
3093
+ import { existsSync as existsSync10 } from "fs";
3094
+ import { resolve as resolve6, join as join14, dirname as dirname6 } from "path";
2477
3095
  import * as p7 from "@clack/prompts";
2478
3096
  import chalk8 from "chalk";
2479
3097
  var ALL_IDES = [
@@ -2567,8 +3185,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2567
3185
  const generatedFiles = [];
2568
3186
  const generatedRules = generateArchitectureRules(manifest.projectConfig);
2569
3187
  const templateName = IDE_TEMPLATE_MAP[selectedIDE];
2570
- const templateDir = join11(TEMPLATES_DIR, templateName);
2571
- if (!existsSync8(templateDir)) {
3188
+ const templateDir = join14(TEMPLATES_DIR, templateName);
3189
+ if (!existsSync10(templateDir)) {
2572
3190
  spinner8.stop("Error");
2573
3191
  p7.cancel(`Template not found: ${templateName}`);
2574
3192
  process.exit(1);
@@ -2579,8 +3197,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2579
3197
  if (dynamicFiles.includes(file)) {
2580
3198
  continue;
2581
3199
  }
2582
- const sourcePath = join11(templateDir, file);
2583
- const destPath = join11(targetDir, file);
3200
+ const sourcePath = join14(templateDir, file);
3201
+ const destPath = join14(targetDir, file);
2584
3202
  const sourceContent = readFile(sourcePath);
2585
3203
  if (fileExists(destPath)) {
2586
3204
  if (conflictResolution === "keep") {
@@ -2597,7 +3215,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2597
3215
  continue;
2598
3216
  }
2599
3217
  }
2600
- ensureDir(dirname4(destPath));
3218
+ ensureDir(dirname6(destPath));
2601
3219
  writeFile(destPath, sourceContent);
2602
3220
  appliedFiles.push(file);
2603
3221
  manifest.files[file] = createManifestEntry(sourceContent);
@@ -2606,7 +3224,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2606
3224
  if (ruleContent) {
2607
3225
  const rulePath = dynamicFiles[0];
2608
3226
  if (rulePath) {
2609
- const destPath = join11(targetDir, rulePath);
3227
+ const destPath = join14(targetDir, rulePath);
2610
3228
  if (fileExists(destPath) && conflictResolution === "keep") {
2611
3229
  skippedFiles.push(rulePath);
2612
3230
  } else if (fileExists(destPath) && conflictResolution === "merge") {
@@ -2617,7 +3235,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2617
3235
  mergedFiles.push(rulePath);
2618
3236
  manifest.files[rulePath] = createManifestEntry(mergedContent);
2619
3237
  } else {
2620
- ensureDir(dirname4(destPath));
3238
+ ensureDir(dirname6(destPath));
2621
3239
  writeFile(destPath, ruleContent);
2622
3240
  generatedFiles.push(`${rulePath} (personalized)`);
2623
3241
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2658,14 +3276,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2658
3276
  }
2659
3277
 
2660
3278
  // src/commands/regenerate.ts
2661
- import { existsSync as existsSync9 } from "fs";
2662
- import { resolve as resolve7, join as join12, dirname as dirname5 } from "path";
3279
+ import { existsSync as existsSync11 } from "fs";
3280
+ import { resolve as resolve7, join as join15, dirname as dirname7 } from "path";
2663
3281
  import { fileURLToPath as fileURLToPath3 } from "url";
2664
3282
  import * as p8 from "@clack/prompts";
2665
3283
  import chalk9 from "chalk";
2666
3284
  var __filename = fileURLToPath3(import.meta.url);
2667
- var __regen_dirname = dirname5(__filename);
2668
- var TEMPLATES_DIR2 = existsSync9(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
3285
+ var __regen_dirname = dirname7(__filename);
3286
+ var TEMPLATES_DIR2 = existsSync11(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
2669
3287
  async function regenerateCommand(target, options) {
2670
3288
  ensureInteractive("regenerate");
2671
3289
  const targetDir = resolve7(options.path || ".");
@@ -2742,7 +3360,7 @@ Valid options:
2742
3360
  return;
2743
3361
  }
2744
3362
  if (regenerateClaudeMd) {
2745
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3363
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2746
3364
  if (fileExists(claudeMdPath)) {
2747
3365
  p8.log.warn(
2748
3366
  "Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
@@ -2779,7 +3397,7 @@ Valid options:
2779
3397
  spinner8.start("Regenerating files...");
2780
3398
  const regeneratedFiles = [];
2781
3399
  if (regenerateClaudeMd) {
2782
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3400
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2783
3401
  const claudeMdContent = generateClaudeMd(
2784
3402
  fullProjectConfig,
2785
3403
  analysis.scripts,
@@ -2790,7 +3408,7 @@ Valid options:
2790
3408
  manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
2791
3409
  }
2792
3410
  if (regenerateAgentsMd) {
2793
- const agentsMdPath = join12(targetDir, "AGENTS.md");
3411
+ const agentsMdPath = join15(targetDir, "AGENTS.md");
2794
3412
  const agentsMdContent = generateAgentsMdFromConfig(
2795
3413
  fullProjectConfig,
2796
3414
  analysis.scripts,
@@ -2807,8 +3425,8 @@ Valid options:
2807
3425
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2808
3426
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2809
3427
  if (ruleContent && rulePath) {
2810
- const destPath = join12(targetDir, rulePath);
2811
- ensureDir(dirname5(destPath));
3428
+ const destPath = join15(targetDir, rulePath);
3429
+ ensureDir(dirname7(destPath));
2812
3430
  writeFile(destPath, ruleContent);
2813
3431
  regeneratedFiles.push(rulePath);
2814
3432
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2822,7 +3440,7 @@ Valid options:
2822
3440
  if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
2823
3441
  p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
2824
3442
  } else {
2825
- const { generatePlugin: generatePlugin2 } = await import("./plugin-JTDPHWUF.js");
3443
+ const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
2826
3444
  const pluginResult = generatePlugin2(
2827
3445
  targetDir,
2828
3446
  TEMPLATES_DIR2,
@@ -2857,8 +3475,8 @@ Valid options:
2857
3475
  }
2858
3476
 
2859
3477
  // src/commands/info.ts
2860
- import { resolve as resolve8, join as join13 } from "path";
2861
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
3478
+ import { resolve as resolve8, join as join16 } from "path";
3479
+ import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
2862
3480
  import * as p9 from "@clack/prompts";
2863
3481
  import chalk10 from "chalk";
2864
3482
  async function infoCommand() {
@@ -2871,33 +3489,33 @@ async function infoCommand() {
2871
3489
  let skillCount = 0;
2872
3490
  let agentCount = 0;
2873
3491
  if (manifest) {
2874
- const pluginDir = manifest.pluginPath ? join13(targetDir, manifest.pluginPath) : null;
2875
- if (pluginDir && existsSync10(pluginDir)) {
2876
- const skillsDir = join13(pluginDir, "skills");
2877
- const agentsDir = join13(pluginDir, "agents");
2878
- if (existsSync10(skillsDir)) {
2879
- skillCount = readdirSync2(skillsDir, { withFileTypes: true }).filter(
3492
+ const pluginDir = manifest.pluginPath ? join16(targetDir, manifest.pluginPath) : null;
3493
+ if (pluginDir && existsSync12(pluginDir)) {
3494
+ const skillsDir = join16(pluginDir, "skills");
3495
+ const agentsDir = join16(pluginDir, "agents");
3496
+ if (existsSync12(skillsDir)) {
3497
+ skillCount = readdirSync3(skillsDir, { withFileTypes: true }).filter(
2880
3498
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2881
3499
  ).length;
2882
3500
  }
2883
- if (existsSync10(agentsDir)) {
2884
- agentCount = readdirSync2(agentsDir, { withFileTypes: true }).filter(
3501
+ if (existsSync12(agentsDir)) {
3502
+ agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
2885
3503
  (e) => e.isFile() && e.name.endsWith(".md")
2886
3504
  ).length;
2887
3505
  }
2888
3506
  }
2889
3507
  if (skillCount === 0) {
2890
- const claudeSkills = join13(targetDir, ".claude", "skills");
2891
- if (existsSync10(claudeSkills)) {
2892
- skillCount = readdirSync2(claudeSkills, { withFileTypes: true }).filter(
3508
+ const claudeSkills = join16(targetDir, ".claude", "skills");
3509
+ if (existsSync12(claudeSkills)) {
3510
+ skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
2893
3511
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2894
3512
  ).length;
2895
3513
  }
2896
3514
  }
2897
3515
  if (agentCount === 0) {
2898
- const claudeAgents = join13(targetDir, ".claude", "agents");
2899
- if (existsSync10(claudeAgents)) {
2900
- agentCount = readdirSync2(claudeAgents, { withFileTypes: true }).filter(
3516
+ const claudeAgents = join16(targetDir, ".claude", "agents");
3517
+ if (existsSync12(claudeAgents)) {
3518
+ agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
2901
3519
  (e) => e.isFile() && e.name.endsWith(".md")
2902
3520
  ).length;
2903
3521
  }
@@ -2940,8 +3558,8 @@ async function infoCommand() {
2940
3558
  }
2941
3559
 
2942
3560
  // src/commands/list.ts
2943
- import { resolve as resolve9, join as join14 } from "path";
2944
- import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync2 } from "fs";
3561
+ import { resolve as resolve9, join as join17 } from "path";
3562
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
2945
3563
  import * as p10 from "@clack/prompts";
2946
3564
  import chalk11 from "chalk";
2947
3565
  async function listCommand(filter, options) {
@@ -2958,30 +3576,30 @@ Valid options: skills, agents`);
2958
3576
  }
2959
3577
  const skills = [];
2960
3578
  const agents = [];
2961
- const pluginDir = manifest?.pluginPath ? join14(targetDir, manifest.pluginPath) : null;
2962
- if (pluginDir && existsSync11(pluginDir)) {
3579
+ const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
3580
+ if (pluginDir && existsSync13(pluginDir)) {
2963
3581
  if (showSkills) {
2964
- const skillsDir = join14(pluginDir, "skills");
2965
- if (existsSync11(skillsDir)) {
3582
+ const skillsDir = join17(pluginDir, "skills");
3583
+ if (existsSync13(skillsDir)) {
2966
3584
  skills.push(...discoverSkills(skillsDir));
2967
3585
  }
2968
3586
  }
2969
3587
  if (showAgents) {
2970
- const agentsDir = join14(pluginDir, "agents");
2971
- if (existsSync11(agentsDir)) {
3588
+ const agentsDir = join17(pluginDir, "agents");
3589
+ if (existsSync13(agentsDir)) {
2972
3590
  agents.push(...discoverAgents(agentsDir));
2973
3591
  }
2974
3592
  }
2975
3593
  }
2976
3594
  if (showSkills && skills.length === 0) {
2977
- const claudeSkills = join14(targetDir, ".claude", "skills");
2978
- if (existsSync11(claudeSkills)) {
3595
+ const claudeSkills = join17(targetDir, ".claude", "skills");
3596
+ if (existsSync13(claudeSkills)) {
2979
3597
  skills.push(...discoverSkills(claudeSkills));
2980
3598
  }
2981
3599
  }
2982
3600
  if (showAgents && agents.length === 0) {
2983
- const claudeAgents = join14(targetDir, ".claude", "agents");
2984
- if (existsSync11(claudeAgents)) {
3601
+ const claudeAgents = join17(targetDir, ".claude", "agents");
3602
+ if (existsSync13(claudeAgents)) {
2985
3603
  agents.push(...discoverAgents(claudeAgents));
2986
3604
  }
2987
3605
  }
@@ -3005,15 +3623,15 @@ Valid options: skills, agents`);
3005
3623
  }
3006
3624
  function discoverSkills(skillsDir) {
3007
3625
  const items = [];
3008
- const entries = readdirSync3(skillsDir, { withFileTypes: true });
3626
+ const entries = readdirSync4(skillsDir, { withFileTypes: true });
3009
3627
  for (const entry of entries) {
3010
3628
  if (entry.isDirectory()) {
3011
- const skillMd = join14(skillsDir, entry.name, "SKILL.md");
3012
- const description = existsSync11(skillMd) ? extractDescription(skillMd) : "";
3629
+ const skillMd = join17(skillsDir, entry.name, "SKILL.md");
3630
+ const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
3013
3631
  items.push({ name: entry.name, description });
3014
3632
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
3015
3633
  const name = entry.name.replace(/\.md$/, "");
3016
- const description = extractDescription(join14(skillsDir, entry.name));
3634
+ const description = extractDescription(join17(skillsDir, entry.name));
3017
3635
  items.push({ name, description });
3018
3636
  }
3019
3637
  }
@@ -3021,11 +3639,11 @@ function discoverSkills(skillsDir) {
3021
3639
  }
3022
3640
  function discoverAgents(agentsDir) {
3023
3641
  const items = [];
3024
- const entries = readdirSync3(agentsDir, { withFileTypes: true });
3642
+ const entries = readdirSync4(agentsDir, { withFileTypes: true });
3025
3643
  for (const entry of entries) {
3026
3644
  if (entry.isFile() && entry.name.endsWith(".md")) {
3027
3645
  const name = entry.name.replace(/\.md$/, "");
3028
- const description = extractDescription(join14(agentsDir, entry.name));
3646
+ const description = extractDescription(join17(agentsDir, entry.name));
3029
3647
  items.push({ name, description });
3030
3648
  }
3031
3649
  }
@@ -3033,7 +3651,7 @@ function discoverAgents(agentsDir) {
3033
3651
  }
3034
3652
  function extractDescription(filePath) {
3035
3653
  try {
3036
- const content = readFileSync2(filePath, "utf-8");
3654
+ const content = readFileSync5(filePath, "utf-8");
3037
3655
  const lines = content.split("\n");
3038
3656
  let pastHeading = false;
3039
3657
  for (const line of lines) {
@@ -3212,8 +3830,8 @@ async function configResetCommand(options) {
3212
3830
  }
3213
3831
 
3214
3832
  // src/commands/doctor.ts
3215
- import { resolve as resolve11, join as join15 } from "path";
3216
- import { existsSync as existsSync12, readdirSync as readdirSync4, statSync, chmodSync as chmodSync3, mkdirSync } from "fs";
3833
+ import { resolve as resolve11, join as join18 } from "path";
3834
+ import { existsSync as existsSync14, readdirSync as readdirSync5, statSync, chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
3217
3835
  import { execSync } from "child_process";
3218
3836
  import * as p12 from "@clack/prompts";
3219
3837
  import chalk13 from "chalk";
@@ -3314,7 +3932,7 @@ function checkManifestFiles(targetDir, manifest) {
3314
3932
  let existCount = 0;
3315
3933
  const missing = [];
3316
3934
  for (const relativePath of Object.keys(manifest.files)) {
3317
- if (fileExists(join15(targetDir, relativePath))) {
3935
+ if (fileExists(join18(targetDir, relativePath))) {
3318
3936
  existCount++;
3319
3937
  } else {
3320
3938
  missing.push(relativePath);
@@ -3334,15 +3952,15 @@ function checkManifestFiles(targetDir, manifest) {
3334
3952
  };
3335
3953
  }
3336
3954
  function checkScriptPermissions(targetDir, manifest) {
3337
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3955
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
3338
3956
  const shFiles = [];
3339
- if (pluginDir && existsSync12(pluginDir)) {
3340
- const scriptsDir = join15(pluginDir, "scripts");
3341
- if (existsSync12(scriptsDir)) {
3342
- const entries = readdirSync4(scriptsDir);
3957
+ if (pluginDir && existsSync14(pluginDir)) {
3958
+ const scriptsDir = join18(pluginDir, "scripts");
3959
+ if (existsSync14(scriptsDir)) {
3960
+ const entries = readdirSync5(scriptsDir);
3343
3961
  for (const entry of entries) {
3344
3962
  if (entry.endsWith(".sh")) {
3345
- shFiles.push(join15(scriptsDir, entry));
3963
+ shFiles.push(join18(scriptsDir, entry));
3346
3964
  }
3347
3965
  }
3348
3966
  }
@@ -3405,28 +4023,28 @@ function checkPluginRegistered(targetDir) {
3405
4023
  };
3406
4024
  }
3407
4025
  function checkHookScripts(targetDir, manifest) {
3408
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3409
- if (!pluginDir || !existsSync12(pluginDir)) {
4026
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
4027
+ if (!pluginDir || !existsSync14(pluginDir)) {
3410
4028
  return {
3411
4029
  name: "hooks",
3412
4030
  status: "pass",
3413
4031
  message: "No hooks directory to check"
3414
4032
  };
3415
4033
  }
3416
- const hooksDir = join15(pluginDir, "hooks");
3417
- if (!existsSync12(hooksDir)) {
4034
+ const hooksDir = join18(pluginDir, "hooks");
4035
+ if (!existsSync14(hooksDir)) {
3418
4036
  return {
3419
4037
  name: "hooks",
3420
4038
  status: "pass",
3421
4039
  message: "No hooks directory found"
3422
4040
  };
3423
4041
  }
3424
- const hookFiles = readdirSync4(hooksDir).filter((f) => f.endsWith(".json"));
4042
+ const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
3425
4043
  let total = 0;
3426
4044
  let valid = 0;
3427
4045
  for (const hookFile of hookFiles) {
3428
4046
  try {
3429
- const hookContent = JSON.parse(readFile(join15(hooksDir, hookFile)));
4047
+ const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
3430
4048
  const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
3431
4049
  for (const hook of hooks) {
3432
4050
  if (hook.command) {
@@ -3440,8 +4058,8 @@ function checkHookScripts(targetDir, manifest) {
3440
4058
  /\$\{[A-Z_]+\}\//g,
3441
4059
  ""
3442
4060
  );
3443
- const scriptPath = join15(pluginDir, resolved);
3444
- if (existsSync12(scriptPath)) {
4061
+ const scriptPath = join18(pluginDir, resolved);
4062
+ if (existsSync14(scriptPath)) {
3445
4063
  valid++;
3446
4064
  }
3447
4065
  } else {
@@ -3465,7 +4083,7 @@ function checkHookScripts(targetDir, manifest) {
3465
4083
  };
3466
4084
  }
3467
4085
  function checkQualityScripts(targetDir) {
3468
- const pkgPath = join15(targetDir, "package.json");
4086
+ const pkgPath = join18(targetDir, "package.json");
3469
4087
  if (!fileExists(pkgPath)) {
3470
4088
  return { name: "quality", status: "warn", message: "No package.json found" };
3471
4089
  }
@@ -3491,8 +4109,8 @@ function checkQualityScripts(targetDir) {
3491
4109
  }
3492
4110
  }
3493
4111
  function checkThoughtsDir(targetDir) {
3494
- const thoughtsDir = join15(targetDir, "thoughts");
3495
- if (existsSync12(thoughtsDir)) {
4112
+ const thoughtsDir = join18(targetDir, "thoughts");
4113
+ if (existsSync14(thoughtsDir)) {
3496
4114
  return {
3497
4115
  name: "thoughts",
3498
4116
  status: "pass",
@@ -3516,15 +4134,15 @@ function checkThoughtsDir(targetDir) {
3516
4134
  "thoughts/archive/backlog"
3517
4135
  ];
3518
4136
  for (const dir of dirs) {
3519
- mkdirSync(join15(targetDir, dir), { recursive: true });
4137
+ mkdirSync3(join18(targetDir, dir), { recursive: true });
3520
4138
  }
3521
4139
  }
3522
4140
  };
3523
4141
  }
3524
4142
  function checkEslint(targetDir) {
3525
4143
  try {
3526
- const eslintLocal = join15(targetDir, "node_modules", ".bin", "eslint");
3527
- if (existsSync12(eslintLocal)) {
4144
+ const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
4145
+ if (existsSync14(eslintLocal)) {
3528
4146
  return { name: "eslint", status: "pass", message: "eslint is available" };
3529
4147
  }
3530
4148
  execSync("which eslint", { stdio: "pipe" });
@@ -3539,8 +4157,8 @@ function checkEslint(targetDir) {
3539
4157
  }
3540
4158
 
3541
4159
  // src/commands/uninstall.ts
3542
- import { existsSync as existsSync13, rmSync, readdirSync as readdirSync5 } from "fs";
3543
- import { resolve as resolve12, join as join16, dirname as dirname6 } from "path";
4160
+ import { existsSync as existsSync15, rmSync as rmSync2, readdirSync as readdirSync6 } from "fs";
4161
+ import { resolve as resolve12, join as join19, dirname as dirname8 } from "path";
3544
4162
  import * as p13 from "@clack/prompts";
3545
4163
  import chalk14 from "chalk";
3546
4164
  var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
@@ -3558,12 +4176,12 @@ async function uninstallCommand(options) {
3558
4176
  return;
3559
4177
  }
3560
4178
  const managedFiles = Object.keys(manifest.files);
3561
- const existingFiles = managedFiles.filter((f) => fileExists(join16(targetDir, f)));
3562
- const missingFiles = managedFiles.filter((f) => !fileExists(join16(targetDir, f)));
3563
- const hasPlugin = manifest.installMode === "plugin" && existsSync13(join16(targetDir, PLUGIN_DIR, PLUGIN_NAME));
3564
- const hasThoughts = existsSync13(join16(targetDir, "thoughts"));
3565
- const hasClaudeMd = fileExists(join16(targetDir, "CLAUDE.md"));
3566
- const hasAgentsMd = fileExists(join16(targetDir, "AGENTS.md"));
4179
+ const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
4180
+ const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
4181
+ const hasPlugin = manifest.installMode === "plugin" && existsSync15(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
4182
+ const hasThoughts = existsSync15(join19(targetDir, "thoughts"));
4183
+ const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
4184
+ const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
3567
4185
  p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
3568
4186
  p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
3569
4187
  p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
@@ -3649,18 +4267,18 @@ async function uninstallCommand(options) {
3649
4267
  }
3650
4268
  if (hasPlugin) {
3651
4269
  try {
3652
- rmSync(join16(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
4270
+ rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
3653
4271
  removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
3654
- const marketplaceDescDir = join16(targetDir, PLUGIN_DIR, ".claude-plugin");
3655
- if (existsSync13(marketplaceDescDir)) {
3656
- rmSync(marketplaceDescDir, { recursive: true, force: true });
4272
+ const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
4273
+ if (existsSync15(marketplaceDescDir)) {
4274
+ rmSync2(marketplaceDescDir, { recursive: true, force: true });
3657
4275
  removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
3658
4276
  }
3659
- const pluginsDir = join16(targetDir, PLUGIN_DIR);
3660
- if (existsSync13(pluginsDir)) {
4277
+ const pluginsDir = join19(targetDir, PLUGIN_DIR);
4278
+ if (existsSync15(pluginsDir)) {
3661
4279
  const remaining = readdirSafe(pluginsDir);
3662
4280
  if (remaining.length === 0) {
3663
- rmSync(pluginsDir, { recursive: true, force: true });
4281
+ rmSync2(pluginsDir, { recursive: true, force: true });
3664
4282
  removed.push(`${PLUGIN_DIR}/ (empty)`);
3665
4283
  }
3666
4284
  }
@@ -3673,11 +4291,11 @@ async function uninstallCommand(options) {
3673
4291
  if (file.startsWith("thoughts/")) continue;
3674
4292
  if (file.startsWith(PLUGIN_DIR + "/")) continue;
3675
4293
  try {
3676
- const filePath = join16(targetDir, file);
3677
- if (existsSync13(filePath)) {
3678
- rmSync(filePath, { force: true });
4294
+ const filePath = join19(targetDir, file);
4295
+ if (existsSync15(filePath)) {
4296
+ rmSync2(filePath, { force: true });
3679
4297
  removed.push(file);
3680
- cleanEmptyParents(targetDir, dirname6(file));
4298
+ cleanEmptyParents(targetDir, dirname8(file));
3681
4299
  }
3682
4300
  } catch (err) {
3683
4301
  errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3686,7 +4304,7 @@ async function uninstallCommand(options) {
3686
4304
  if (hasClaudeMd) {
3687
4305
  if (removeClaudeMd) {
3688
4306
  try {
3689
- rmSync(join16(targetDir, "CLAUDE.md"), { force: true });
4307
+ rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
3690
4308
  removed.push("CLAUDE.md");
3691
4309
  } catch (err) {
3692
4310
  errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3698,7 +4316,7 @@ async function uninstallCommand(options) {
3698
4316
  if (hasAgentsMd) {
3699
4317
  if (removeAgentsMd) {
3700
4318
  try {
3701
- rmSync(join16(targetDir, "AGENTS.md"), { force: true });
4319
+ rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
3702
4320
  removed.push("AGENTS.md");
3703
4321
  } catch (err) {
3704
4322
  errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3710,7 +4328,7 @@ async function uninstallCommand(options) {
3710
4328
  if (hasThoughts) {
3711
4329
  if (removeThoughts) {
3712
4330
  try {
3713
- rmSync(join16(targetDir, "thoughts"), { recursive: true, force: true });
4331
+ rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
3714
4332
  removed.push("thoughts/");
3715
4333
  } catch (err) {
3716
4334
  errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
@@ -3720,9 +4338,9 @@ async function uninstallCommand(options) {
3720
4338
  }
3721
4339
  }
3722
4340
  try {
3723
- const manifestDir = join16(targetDir, MANIFEST_DIR);
3724
- if (existsSync13(manifestDir)) {
3725
- rmSync(manifestDir, { recursive: true, force: true });
4341
+ const manifestDir = join19(targetDir, MANIFEST_DIR);
4342
+ if (existsSync15(manifestDir)) {
4343
+ rmSync2(manifestDir, { recursive: true, force: true });
3726
4344
  removed.push(`${MANIFEST_DIR}/`);
3727
4345
  }
3728
4346
  } catch (err) {
@@ -3767,33 +4385,59 @@ async function uninstallCommand(options) {
3767
4385
  }
3768
4386
  function cleanEmptyParents(targetDir, relDir) {
3769
4387
  if (!relDir || relDir === ".") return;
3770
- const absDir = join16(targetDir, relDir);
3771
- if (!existsSync13(absDir)) return;
4388
+ const absDir = join19(targetDir, relDir);
4389
+ if (!existsSync15(absDir)) return;
3772
4390
  const entries = readdirSafe(absDir);
3773
4391
  if (entries.length === 0) {
3774
4392
  try {
3775
- rmSync(absDir, { recursive: true, force: true });
3776
- cleanEmptyParents(targetDir, dirname6(relDir));
4393
+ rmSync2(absDir, { recursive: true, force: true });
4394
+ cleanEmptyParents(targetDir, dirname8(relDir));
3777
4395
  } catch {
3778
4396
  }
3779
4397
  }
3780
4398
  }
3781
4399
  function readdirSafe(dir) {
3782
4400
  try {
3783
- return readdirSync5(dir);
4401
+ return readdirSync6(dir);
3784
4402
  } catch {
3785
4403
  return [];
3786
4404
  }
3787
4405
  }
3788
4406
 
3789
4407
  // src/commands/addon.ts
3790
- import { resolve as resolve13, join as join17, dirname as dirname7 } from "path";
3791
- import { existsSync as existsSync14, unlinkSync as unlinkSync2, rmSync as rmSync2 } from "fs";
4408
+ import { resolve as resolve13, join as join20, dirname as dirname9 } from "path";
4409
+ import { existsSync as existsSync16, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
3792
4410
  import * as p14 from "@clack/prompts";
3793
4411
  import chalk15 from "chalk";
4412
+ function isFileBasedAddon(addonName) {
4413
+ return addonName !== "orchestration";
4414
+ }
3794
4415
  async function addonCommand(action, addonName, options) {
3795
4416
  const targetDir = resolve13(options.path || ".");
3796
4417
  p14.intro(introTitle(`Addon ${action}`));
4418
+ const validAddons = Object.keys(ADDONS);
4419
+ if (!validAddons.includes(addonName)) {
4420
+ p14.cancel(`Unknown addon: ${addonName}
4421
+
4422
+ Valid addons: ${validAddons.join(", ")}`);
4423
+ process.exit(1);
4424
+ }
4425
+ const typedName = addonName;
4426
+ const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
4427
+ if (action === "add" || action === "remove") {
4428
+ const canonical = action === "add" ? "enable" : "disable";
4429
+ p14.log.warn(
4430
+ `"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
4431
+ );
4432
+ }
4433
+ if (isFileBasedAddon(typedName)) {
4434
+ if (canonicalAction === "add") {
4435
+ await addFileBasedAddon(targetDir, typedName, options);
4436
+ } else {
4437
+ await removeFileBasedAddon(targetDir, typedName, options);
4438
+ }
4439
+ return;
4440
+ }
3797
4441
  const manifest = readManifest(targetDir);
3798
4442
  if (!manifest) {
3799
4443
  p14.log.warn("No devtronic installation found.");
@@ -3807,16 +4451,9 @@ async function addonCommand(action, addonName, options) {
3807
4451
  p14.outro("");
3808
4452
  return;
3809
4453
  }
3810
- const validAddons = Object.keys(ADDONS);
3811
- if (!validAddons.includes(addonName)) {
3812
- p14.cancel(`Unknown addon: ${addonName}
3813
-
3814
- Valid addons: ${validAddons.join(", ")}`);
3815
- process.exit(1);
3816
- }
3817
- const addon = ADDONS[addonName];
4454
+ const addon = ADDONS[typedName];
3818
4455
  const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
3819
- if (action === "add") {
4456
+ if (canonicalAction === "add") {
3820
4457
  await addAddon(targetDir, manifest, addon.name, currentAddons);
3821
4458
  } else {
3822
4459
  await removeAddon(targetDir, manifest, addon.name, currentAddons);
@@ -3830,7 +4467,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3830
4467
  }
3831
4468
  const addon = ADDONS[addonName];
3832
4469
  const pluginRoot = manifest.pluginPath;
3833
- const skillsSourceDir = join17(TEMPLATES_DIR, "claude-code", ".claude", "skills");
4470
+ const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
3834
4471
  p14.note(
3835
4472
  [
3836
4473
  ` ${chalk15.dim("Name:")} ${addon.label}`,
@@ -3849,18 +4486,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3849
4486
  spinner8.start(`Adding ${addon.label}...`);
3850
4487
  const addedFiles = [];
3851
4488
  for (const skillDir of addon.skills) {
3852
- const sourceDir = join17(skillsSourceDir, skillDir);
3853
- if (!existsSync14(sourceDir)) {
4489
+ const sourceDir = join20(skillsSourceDir, skillDir);
4490
+ if (!existsSync16(sourceDir)) {
3854
4491
  spinner8.stop(`Template not found for skill: ${skillDir}`);
3855
4492
  p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
3856
4493
  continue;
3857
4494
  }
3858
4495
  const templateFiles = getAllFilesRecursive(sourceDir);
3859
4496
  for (const file of templateFiles) {
3860
- const content = readFile(join17(sourceDir, file));
3861
- const destRelPath = join17(pluginRoot, "skills", skillDir, file);
3862
- const destAbsPath = join17(targetDir, destRelPath);
3863
- ensureDir(dirname7(destAbsPath));
4497
+ const content = readFile(join20(sourceDir, file));
4498
+ const destRelPath = join20(pluginRoot, "skills", skillDir, file);
4499
+ const destAbsPath = join20(targetDir, destRelPath);
4500
+ ensureDir(dirname9(destAbsPath));
3864
4501
  writeFile(destAbsPath, content);
3865
4502
  manifest.files[destRelPath] = createManifestEntry(content);
3866
4503
  addedFiles.push(destRelPath);
@@ -3905,11 +4542,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3905
4542
  }
3906
4543
  const modifiedFiles = [];
3907
4544
  for (const skillDir of addon.skills) {
3908
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
4545
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
3909
4546
  for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
3910
4547
  if (!filePath.startsWith(skillRelBase)) continue;
3911
- const absPath = join17(targetDir, filePath);
3912
- if (!existsSync14(absPath)) continue;
4548
+ const absPath = join20(targetDir, filePath);
4549
+ if (!existsSync16(absPath)) continue;
3913
4550
  const current = calculateChecksum(readFile(absPath));
3914
4551
  if (current !== fileInfo.originalChecksum) {
3915
4552
  modifiedFiles.push(filePath);
@@ -3932,18 +4569,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3932
4569
  const spinner8 = p14.spinner();
3933
4570
  spinner8.start(`Removing ${addon.label}...`);
3934
4571
  for (const skillDir of addon.skills) {
3935
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
3936
- const skillAbsDir = join17(targetDir, skillRelBase);
4572
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
4573
+ const skillAbsDir = join20(targetDir, skillRelBase);
3937
4574
  for (const filePath of Object.keys(manifest.files)) {
3938
4575
  if (filePath.startsWith(skillRelBase)) {
3939
- const absPath = join17(targetDir, filePath);
3940
- if (existsSync14(absPath)) unlinkSync2(absPath);
4576
+ const absPath = join20(targetDir, filePath);
4577
+ if (existsSync16(absPath)) unlinkSync3(absPath);
3941
4578
  delete manifest.files[filePath];
3942
4579
  }
3943
4580
  }
3944
- if (existsSync14(skillAbsDir)) {
4581
+ if (existsSync16(skillAbsDir)) {
3945
4582
  try {
3946
- rmSync2(skillAbsDir, { recursive: true });
4583
+ rmSync3(skillAbsDir, { recursive: true });
3947
4584
  } catch {
3948
4585
  }
3949
4586
  }
@@ -3960,32 +4597,196 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3960
4597
  );
3961
4598
  p14.outro("Done. Restart Claude Code to apply the changes.");
3962
4599
  }
4600
+ async function addFileBasedAddon(targetDir, addonName, _options) {
4601
+ const addon = ADDONS[addonName];
4602
+ const config = readAddonConfig(targetDir);
4603
+ if (config.installed[addonName]) {
4604
+ p14.log.warn(`Addon "${addonName}" is already installed.`);
4605
+ p14.outro("");
4606
+ return;
4607
+ }
4608
+ p14.note(
4609
+ [
4610
+ ` ${chalk15.dim("Name:")} ${addon.label}`,
4611
+ ` ${chalk15.dim("Description:")} ${addon.description}`,
4612
+ ` ${chalk15.dim("Skills:")} ${addon.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`,
4613
+ addon.agents.length ? ` ${chalk15.dim("Agents:")} ${addon.agents.map((a) => chalk15.cyan(a)).join(", ")}` : ` ${chalk15.dim("Agents:")} ${chalk15.dim("\u2014")}`
4614
+ ].join("\n"),
4615
+ "Adding addon"
4616
+ );
4617
+ const confirmed = await p14.confirm({ message: "Add this addon?" });
4618
+ if (p14.isCancel(confirmed) || !confirmed) {
4619
+ p14.cancel("Addon installation cancelled.");
4620
+ process.exit(0);
4621
+ }
4622
+ const spinner8 = p14.spinner();
4623
+ spinner8.start(`Adding ${addon.label}...`);
4624
+ const addonSourceDir = getAddonSourceDir(addonName);
4625
+ const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
4626
+ const addonManifest = JSON.parse(
4627
+ readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
4628
+ );
4629
+ const fileList = [
4630
+ ...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
4631
+ ...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
4632
+ ...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
4633
+ ];
4634
+ writeAddonToConfig(targetDir, addonName, {
4635
+ version: addonManifest.version,
4636
+ files: fileList,
4637
+ checksums: result.checksums ?? {}
4638
+ });
4639
+ spinner8.stop(`${symbols.pass} ${addon.label} added (${result.written} files written)`);
4640
+ p14.note(
4641
+ addon.skills.map((s) => ` ${chalk15.cyan(`/${s}`)}`).join("\n"),
4642
+ "New skills available"
4643
+ );
4644
+ p14.outro("Done. Skills are now available in your agent directories.");
4645
+ }
4646
+ async function removeFileBasedAddon(targetDir, addonName, _options) {
4647
+ const addon = ADDONS[addonName];
4648
+ const config = readAddonConfig(targetDir);
4649
+ if (!config.installed[addonName]) {
4650
+ p14.log.warn(`Addon "${addonName}" is not currently installed.`);
4651
+ p14.outro("");
4652
+ return;
4653
+ }
4654
+ const modified = detectModifiedAddonFiles(targetDir, addonName);
4655
+ if (modified.length > 0) {
4656
+ p14.log.warn("The following files have been customized:");
4657
+ for (const f of modified) {
4658
+ p14.log.message(` ${chalk15.yellow(f)}`);
4659
+ }
4660
+ const confirm9 = await p14.confirm({
4661
+ message: "Remove them anyway? (customizations will be lost)"
4662
+ });
4663
+ if (p14.isCancel(confirm9) || !confirm9) {
4664
+ p14.cancel("Addon removal cancelled.");
4665
+ process.exit(0);
4666
+ }
4667
+ }
4668
+ const spinner8 = p14.spinner();
4669
+ spinner8.start(`Removing ${addon.label}...`);
4670
+ removeAddonFiles(targetDir, addonName, config.agents);
4671
+ removeAddonFromConfig(targetDir, addonName);
4672
+ spinner8.stop(`${symbols.pass} ${addon.label} removed`);
4673
+ p14.note(
4674
+ addon.skills.map((s) => ` ${chalk15.dim(`/${s}`)}`).join("\n"),
4675
+ "Skills removed"
4676
+ );
4677
+ p14.outro("Done.");
4678
+ }
4679
+ function getAddonListInfo(targetDir) {
4680
+ const config = readAddonConfig(targetDir);
4681
+ return getAvailableAddons().map((addon) => ({
4682
+ name: addon.name,
4683
+ label: addon.label,
4684
+ description: addon.description,
4685
+ installed: !!config.installed[addon.name],
4686
+ agents: config.installed[addon.name] ? config.agents : void 0
4687
+ }));
4688
+ }
4689
+ async function addonListCommand(options) {
4690
+ const targetDir = resolve13(options.path || ".");
4691
+ p14.intro(introTitle("Addon List"));
4692
+ const items = getAddonListInfo(targetDir);
4693
+ const lines = items.map((item) => {
4694
+ const status = item.installed ? chalk15.green("\u2713 installed") : chalk15.dim("available");
4695
+ const agents = item.agents ? chalk15.dim(` \u2192 ${item.agents.join(", ")}`) : "";
4696
+ return ` ${chalk15.bold(item.name)} ${status}${agents}
4697
+ ${chalk15.dim(item.description)}`;
4698
+ });
4699
+ p14.note(lines.join("\n\n"), "Addons");
4700
+ p14.outro(`Use ${chalk15.cyan("devtronic addon add <name>")} to install.`);
4701
+ }
4702
+ async function addonSyncCommand(options) {
4703
+ const targetDir = resolve13(options.path || ".");
4704
+ p14.intro(introTitle("Addon Sync"));
4705
+ const config = readAddonConfig(targetDir);
4706
+ const installedNames = Object.keys(config.installed);
4707
+ if (installedNames.length === 0) {
4708
+ p14.log.info("No addons installed. Nothing to sync.");
4709
+ p14.outro("");
4710
+ return;
4711
+ }
4712
+ const spinner8 = p14.spinner();
4713
+ spinner8.start("Syncing addon files...");
4714
+ let totalWritten = 0;
4715
+ let totalConflicts = [];
4716
+ for (const name of installedNames) {
4717
+ const addonSourceDir = getAddonSourceDir(name);
4718
+ const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
4719
+ totalWritten += result.written + (result.updated ?? 0);
4720
+ totalConflicts = totalConflicts.concat(result.conflicts);
4721
+ }
4722
+ spinner8.stop(`${symbols.pass} Sync complete (${totalWritten} files updated)`);
4723
+ if (totalConflicts.length > 0) {
4724
+ p14.log.warn("Customized files were preserved:");
4725
+ for (const f of totalConflicts) {
4726
+ p14.log.message(` ${chalk15.yellow(f)}`);
4727
+ }
4728
+ }
4729
+ p14.outro("Done.");
4730
+ }
3963
4731
  function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
3964
4732
  const cliVersion2 = getCliVersion();
3965
4733
  const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
3966
- const pluginJsonRelPath = join17(pluginRoot, ".claude-plugin", "plugin.json");
3967
- writeFile(join17(targetDir, pluginJsonRelPath), pluginJsonContent);
4734
+ const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
4735
+ writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
3968
4736
  manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
3969
4737
  const marketplaceContent = generateMarketplaceJson(addonSkillCount);
3970
- const marketplaceRelPath = join17(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
3971
- writeFile(join17(targetDir, marketplaceRelPath), marketplaceContent);
4738
+ const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
4739
+ writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
3972
4740
  manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
3973
4741
  }
3974
4742
 
4743
+ // src/commands/mode.ts
4744
+ import { resolve as resolve14, join as join21 } from "path";
4745
+ import { existsSync as existsSync17 } from "fs";
4746
+ import * as p15 from "@clack/prompts";
4747
+ import chalk16 from "chalk";
4748
+ async function modeCommand(action, options) {
4749
+ const targetDir = resolve14(options.path || ".");
4750
+ p15.intro(introTitle("Mode"));
4751
+ if (action === "show") {
4752
+ const configPath = join21(targetDir, ".claude", "devtronic.json");
4753
+ const hasConfig = existsSync17(configPath);
4754
+ const config = readAddonConfig(targetDir);
4755
+ const currentMode = config.mode;
4756
+ const isDefault = !hasConfig || currentMode === void 0;
4757
+ const displayMode = currentMode ?? "hitl";
4758
+ p15.log.info(
4759
+ `Mode: ${chalk16.cyan(displayMode)}${isDefault ? chalk16.dim(" (default)") : ""}`
4760
+ );
4761
+ p15.log.info(`Config: ${chalk16.dim(".claude/devtronic.json")}`);
4762
+ p15.outro("");
4763
+ return;
4764
+ }
4765
+ const newMode = action;
4766
+ writeMode(targetDir, newMode);
4767
+ const description = newMode === "afk" ? "Fully autonomous \u2014 no human gates" : "Human-in-the-loop \u2014 pauses for approval at key stages";
4768
+ p15.log.success(`Mode set to ${chalk16.cyan(newMode)}`);
4769
+ p15.log.info(chalk16.dim(description));
4770
+ p15.outro(
4771
+ `Change back anytime with ${chalk16.cyan(`npx devtronic mode ${newMode === "afk" ? "hitl" : "afk"}`)}`
4772
+ );
4773
+ }
4774
+
3975
4775
  // src/index.ts
3976
4776
  var cliVersion = getCliVersion();
3977
4777
  var program = new Command();
3978
4778
  program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
3979
4779
  showLogo();
3980
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}`));
4780
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
3981
4781
  console.log();
3982
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic init")} ${chalk16.dim("[path]")} ${chalk16.dim("Initialize in a project")}`);
3983
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic info")} ${chalk16.dim("Version & config summary")}`);
3984
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic doctor")} ${chalk16.dim("Health diagnostics")}`);
3985
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic status")} ${chalk16.dim("File status overview")}`);
4782
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic init")} ${chalk17.dim("[path]")} ${chalk17.dim("Initialize in a project")}`);
4783
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic info")} ${chalk17.dim("Version & config summary")}`);
4784
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic doctor")} ${chalk17.dim("Health diagnostics")}`);
4785
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic status")} ${chalk17.dim("File status overview")}`);
4786
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic mode")} ${chalk17.dim("<afk|hitl|show>")} ${chalk17.dim("Get or set execution mode")}`);
3986
4787
  console.log();
3987
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic help")} ${chalk16.dim("Show all commands")}`);
3988
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic help --all")} ${chalk16.dim("Full reference with all options")}`);
4788
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help")} ${chalk17.dim("Show all commands")}`);
4789
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help --all")} ${chalk17.dim("Full reference with all options")}`);
3989
4790
  console.log();
3990
4791
  });
3991
4792
  program.command("init").description("Initialize devtronic in your project").argument("[path]", "Target directory (default: current directory)").option("--ide <ides>", "Comma-separated list of IDEs to configure").option("-y, --yes", "Skip prompts and use defaults").option("--preview", "Show what would be generated without making changes").option(
@@ -4044,13 +4845,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
4044
4845
  program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
4045
4846
  await uninstallCommand({ path: options.path });
4046
4847
  });
4848
+ program.command("mode").description("Set or show the execution mode (hitl or afk)").argument("<mode>", "Mode: afk, hitl, or show").option("--path <path>", "Target directory (default: current directory)").action(async (mode, options) => {
4849
+ if (!["afk", "hitl", "show"].includes(mode)) {
4850
+ console.error(`Invalid mode: "${mode}". Valid values: afk, hitl, show`);
4851
+ process.exit(1);
4852
+ }
4853
+ await modeCommand(mode, { path: options.path });
4854
+ });
4047
4855
  program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
4048
4856
  if (!options.all) {
4049
4857
  program.outputHelp();
4050
4858
  return;
4051
4859
  }
4052
4860
  showLogo();
4053
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}
4861
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
4054
4862
  `));
4055
4863
  const sections = [
4056
4864
  {
@@ -4074,7 +4882,7 @@ program.command("help").description("Show help (use --all for full reference)").
4074
4882
  {
4075
4883
  title: "Addons",
4076
4884
  usage: "addon add <name>",
4077
- desc: "Add an optional skill pack (e.g., orchestration)",
4885
+ desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
4078
4886
  opts: ["--path <path> Target directory"]
4079
4887
  },
4080
4888
  {
@@ -4083,6 +4891,30 @@ program.command("help").description("Show help (use --all for full reference)").
4083
4891
  desc: "Remove an addon skill pack",
4084
4892
  opts: ["--path <path> Target directory"]
4085
4893
  },
4894
+ {
4895
+ title: "",
4896
+ usage: "addon list",
4897
+ desc: "List available and installed addons",
4898
+ opts: ["--path <path> Target directory"]
4899
+ },
4900
+ {
4901
+ title: "",
4902
+ usage: "addon sync",
4903
+ desc: "Regenerate addon files for current agent configuration",
4904
+ opts: ["--path <path> Target directory"]
4905
+ },
4906
+ {
4907
+ title: "",
4908
+ usage: "addon enable <name>",
4909
+ desc: "Enable an addon (copies files to .claude/)",
4910
+ opts: ["--path <path> Target directory"]
4911
+ },
4912
+ {
4913
+ title: "",
4914
+ usage: "addon disable <name>",
4915
+ desc: "Disable an addon (removes files from .claude/)",
4916
+ opts: ["--path <path> Target directory"]
4917
+ },
4086
4918
  {
4087
4919
  title: "Maintenance",
4088
4920
  usage: "update",
@@ -4136,6 +4968,12 @@ program.command("help").description("Show help (use --all for full reference)").
4136
4968
  usage: "presets",
4137
4969
  desc: "List available configuration presets"
4138
4970
  },
4971
+ {
4972
+ title: "Mode",
4973
+ usage: "mode <afk|hitl|show>",
4974
+ desc: "Set or show the persistent execution mode for /auto-devtronic",
4975
+ opts: ["--path <path> Target directory"]
4976
+ },
4139
4977
  {
4140
4978
  title: "Diagnostics",
4141
4979
  usage: "status",
@@ -4171,14 +5009,14 @@ program.command("help").description("Show help (use --all for full reference)").
4171
5009
  ];
4172
5010
  for (const section of sections) {
4173
5011
  if (section.title) {
4174
- console.log(` ${chalk16.bold.underline(section.title)}
5012
+ console.log(` ${chalk17.bold.underline(section.title)}
4175
5013
  `);
4176
5014
  }
4177
- console.log(` ${chalk16.white("devtronic " + section.usage)}`);
4178
- console.log(` ${chalk16.dim(section.desc)}`);
5015
+ console.log(` ${chalk17.white("devtronic " + section.usage)}`);
5016
+ console.log(` ${chalk17.dim(section.desc)}`);
4179
5017
  if (section.opts) {
4180
5018
  for (const opt of section.opts) {
4181
- console.log(` ${chalk16.yellow(opt)}`);
5019
+ console.log(` ${chalk17.yellow(opt)}`);
4182
5020
  }
4183
5021
  }
4184
5022
  console.log();
@@ -4191,21 +5029,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
4191
5029
  addonCmd.command("remove").description("Remove an addon from the devtronic plugin").argument("<name>", "Addon name (e.g., orchestration)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
4192
5030
  await addonCommand("remove", name, { path: options.path });
4193
5031
  });
5032
+ addonCmd.command("list").description("List available and installed addons").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5033
+ await addonListCommand({ path: options.path });
5034
+ });
5035
+ addonCmd.command("enable").description("Enable an addon (copies files to .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
5036
+ await addonCommand("enable", name, { path: options.path });
5037
+ });
5038
+ addonCmd.command("disable").description("Disable an addon (removes files from .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
5039
+ await addonCommand("disable", name, { path: options.path });
5040
+ });
5041
+ addonCmd.command("sync").description("Regenerate addon files for current agent configuration").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5042
+ await addonSyncCommand({ path: options.path });
5043
+ });
4194
5044
  program.command("presets").description("List available configuration presets").action(() => {
4195
- p15.intro(introTitle("Presets"));
5045
+ p16.intro(introTitle("Presets"));
4196
5046
  const lines = Object.entries(PRESETS).map(([name, preset]) => {
4197
- const parts = [` ${chalk16.bold(name)}`];
5047
+ const parts = [` ${chalk17.bold(name)}`];
4198
5048
  parts.push(` ${preset.description}`);
4199
5049
  if (preset.config.architecture) {
4200
- parts.push(` Architecture: ${chalk16.cyan(preset.config.architecture)}`);
5050
+ parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
4201
5051
  }
4202
5052
  if (preset.config.layers) {
4203
- parts.push(` Layers: ${chalk16.cyan(preset.config.layers.join(", "))}`);
5053
+ parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
4204
5054
  }
4205
5055
  return parts.join("\n");
4206
5056
  });
4207
- p15.note(lines.join("\n\n"), "Available Presets");
4208
- p15.outro(`Usage: ${chalk16.cyan("npx devtronic init --preset <name>")}`);
5057
+ p16.note(lines.join("\n\n"), "Available Presets");
5058
+ p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
4209
5059
  });
4210
5060
  program.parseAsync().catch((err) => {
4211
5061
  console.error(`