devtronic 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +26 -4
  2. package/dist/{chunk-5V4PXTP6.js → chunk-V4QEAL7Y.js} +43 -19
  3. package/dist/index.js +1052 -222
  4. package/dist/{plugin-5ZMTSRIW.js → plugin-SGSFVXPA.js} +1 -1
  5. package/package.json +2 -2
  6. package/templates/claude-code/.claude/skills/audit/SKILL.md +1 -1
  7. package/templates/claude-code/.claude/skills/backlog/SKILL.md +1 -1
  8. package/templates/claude-code/.claude/skills/brief/SKILL.md +1 -1
  9. package/templates/claude-code/.claude/skills/briefing/SKILL.md +1 -1
  10. package/templates/claude-code/.claude/skills/checkpoint/SKILL.md +1 -1
  11. package/templates/claude-code/.claude/skills/create-plan/SKILL.md +1 -1
  12. package/templates/claude-code/.claude/skills/create-skill/SKILL.md +1 -1
  13. package/templates/claude-code/.claude/skills/design/SKILL.md +1 -1
  14. package/templates/claude-code/.claude/skills/design-audit/SKILL.md +1 -1
  15. package/templates/claude-code/.claude/skills/design-define/SKILL.md +1 -1
  16. package/templates/claude-code/.claude/skills/design-ia/SKILL.md +1 -1
  17. package/templates/claude-code/.claude/skills/design-research/SKILL.md +1 -1
  18. package/templates/claude-code/.claude/skills/design-review/SKILL.md +1 -1
  19. package/templates/claude-code/.claude/skills/design-spec/SKILL.md +1 -1
  20. package/templates/claude-code/.claude/skills/design-system/SKILL.md +1 -1
  21. package/templates/claude-code/.claude/skills/design-system-audit/SKILL.md +1 -1
  22. package/templates/claude-code/.claude/skills/design-system-define/SKILL.md +1 -1
  23. package/templates/claude-code/.claude/skills/design-system-sync/SKILL.md +1 -1
  24. package/templates/claude-code/.claude/skills/design-wireframe/SKILL.md +1 -1
  25. package/templates/claude-code/.claude/skills/execute-plan/SKILL.md +1 -1
  26. package/templates/claude-code/.claude/skills/generate-tests/SKILL.md +1 -1
  27. package/templates/claude-code/.claude/skills/handoff/SKILL.md +1 -1
  28. package/templates/claude-code/.claude/skills/investigate/SKILL.md +1 -1
  29. package/templates/claude-code/.claude/skills/learn/SKILL.md +1 -1
  30. package/templates/claude-code/.claude/skills/opensrc/SKILL.md +1 -1
  31. package/templates/claude-code/.claude/skills/post-review/SKILL.md +1 -1
  32. package/templates/claude-code/.claude/skills/quick/SKILL.md +1 -1
  33. package/templates/claude-code/.claude/skills/recap/SKILL.md +1 -1
  34. package/templates/claude-code/.claude/skills/research/SKILL.md +1 -1
  35. package/templates/claude-code/.claude/skills/scaffold/SKILL.md +1 -1
  36. package/templates/claude-code/.claude/skills/setup/SKILL.md +1 -1
  37. package/templates/claude-code/.claude/skills/spec/SKILL.md +1 -1
  38. package/templates/claude-code/.claude/skills/summary/SKILL.md +1 -1
  39. package/templates/claude-code/.claude/skills/worktree/SKILL.md +1 -1
package/dist/index.js CHANGED
@@ -24,12 +24,12 @@ import {
24
24
  readManifest,
25
25
  writeFile,
26
26
  writeManifest
27
- } from "./chunk-5V4PXTP6.js";
27
+ } from "./chunk-V4QEAL7Y.js";
28
28
 
29
29
  // src/index.ts
30
30
  import { Command } from "commander";
31
- import * as p15 from "@clack/prompts";
32
- import chalk16 from "chalk";
31
+ import * as p16 from "@clack/prompts";
32
+ import chalk17 from "chalk";
33
33
 
34
34
  // src/commands/init.ts
35
35
  import { existsSync as existsSync5, chmodSync } from "fs";
@@ -453,10 +453,16 @@ async function promptForAgentsMd() {
453
453
  initialValue: true
454
454
  });
455
455
  }
456
- async function promptForOrchestration() {
457
- return p.confirm({
458
- message: "Enable orchestration workflow? (briefing \u2192 execute-plan \u2192 recap \u2192 handoff)",
459
- initialValue: false
456
+ async function promptForAddons() {
457
+ const options = Object.values(ADDONS).map((addon) => ({
458
+ value: addon.name,
459
+ label: addon.label,
460
+ hint: addon.description
461
+ }));
462
+ return p.multiselect({
463
+ message: "Enable optional addon packs? (space to toggle, enter to confirm)",
464
+ options,
465
+ required: false
460
466
  });
461
467
  }
462
468
 
@@ -1207,9 +1213,9 @@ function getCliVersion() {
1207
1213
  resolve(__dirname, "../package.json"),
1208
1214
  resolve(__dirname, "../../package.json")
1209
1215
  ];
1210
- for (const p16 of paths) {
1216
+ for (const p17 of paths) {
1211
1217
  try {
1212
- const pkg = JSON.parse(readFileSync(p16, "utf-8"));
1218
+ const pkg = JSON.parse(readFileSync(p17, "utf-8"));
1213
1219
  if (pkg.name === "devtronic" && pkg.version) {
1214
1220
  return pkg.version;
1215
1221
  }
@@ -1368,14 +1374,12 @@ Valid addons: ${validAddons.join(", ")}`);
1368
1374
  }
1369
1375
  }
1370
1376
  if (selectedIDEs.includes("claude-code") && !options.addon && !options.yes && !options.preset && !options.preview) {
1371
- const wantOrchestration = await promptForOrchestration();
1372
- if (p3.isCancel(wantOrchestration)) {
1377
+ const selectedAddons = await promptForAddons();
1378
+ if (p3.isCancel(selectedAddons)) {
1373
1379
  p3.cancel("Operation cancelled");
1374
1380
  process.exit(0);
1375
1381
  }
1376
- if (wantOrchestration) {
1377
- enabledAddons = ["orchestration"];
1378
- }
1382
+ enabledAddons = selectedAddons;
1379
1383
  }
1380
1384
  if (enabledAddons.length > 0) {
1381
1385
  projectConfig.enabledAddons = enabledAddons;
@@ -1604,6 +1608,13 @@ Valid addons: ${validAddons.join(", ")}`);
1604
1608
  ].join("\n"),
1605
1609
  "Next Steps"
1606
1610
  );
1611
+ p3.note(
1612
+ [
1613
+ `${chalk4.dim("Execution mode:")} HITL ${chalk4.dim("(default \u2014 safe for unfamiliar codebases)")}`,
1614
+ `${chalk4.dim("Change anytime:")} ${chalk4.cyan("npx devtronic mode afk")}`
1615
+ ].join("\n"),
1616
+ "Autonomous Mode"
1617
+ );
1607
1618
  p3.outro(chalk4.green("Setup complete!"));
1608
1619
  } catch (err) {
1609
1620
  spinner8.stop("Configuration failed");
@@ -1706,8 +1717,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
1706
1717
  }
1707
1718
 
1708
1719
  // src/commands/update.ts
1709
- import { resolve as resolve3, join as join8, dirname as dirname3 } from "path";
1710
- import { existsSync as existsSync6, unlinkSync, lstatSync, readdirSync, rmdirSync, chmodSync as chmodSync2 } from "fs";
1720
+ import { resolve as resolve3, join as join11, dirname as dirname5 } from "path";
1721
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, lstatSync, readdirSync as readdirSync2, rmdirSync as rmdirSync2, chmodSync as chmodSync2 } from "fs";
1711
1722
  import * as p4 from "@clack/prompts";
1712
1723
  import chalk5 from "chalk";
1713
1724
 
@@ -1735,6 +1746,553 @@ var REMOVED_FILES = {
1735
1746
  }
1736
1747
  };
1737
1748
 
1749
+ // src/utils/addonConfig.ts
1750
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync, mkdirSync, renameSync } from "fs";
1751
+ import { join as join9, dirname as dirname3 } from "path";
1752
+
1753
+ // src/addons/registry.ts
1754
+ import { readFileSync as readFileSync2 } from "fs";
1755
+ import { join as join8 } from "path";
1756
+ function getAddonSourceDir(name) {
1757
+ const addonsDir = new URL(".", import.meta.url).pathname;
1758
+ return join8(addonsDir, name);
1759
+ }
1760
+ function getAddonManifest(name) {
1761
+ const sourceDir = getAddonSourceDir(name);
1762
+ const manifestPath = join8(sourceDir, "manifest.json");
1763
+ const raw = readFileSync2(manifestPath, "utf-8");
1764
+ const manifest = JSON.parse(raw);
1765
+ if (!manifest.name) throw new Error(`Addon manifest missing "name": ${manifestPath}`);
1766
+ if (!manifest.version) throw new Error(`Addon manifest missing "version": ${manifestPath}`);
1767
+ if (!manifest.files?.skills?.length) throw new Error(`Addon manifest has empty skills: ${manifestPath}`);
1768
+ return manifest;
1769
+ }
1770
+ function getAvailableAddons() {
1771
+ return Object.values(ADDONS);
1772
+ }
1773
+
1774
+ // src/utils/addonConfig.ts
1775
+ var CONFIG_DIR = ".claude";
1776
+ var CONFIG_FILE = "devtronic.json";
1777
+ function getConfigPath(targetDir) {
1778
+ return join9(targetDir, CONFIG_DIR, CONFIG_FILE);
1779
+ }
1780
+ function getLegacyConfigPath(targetDir) {
1781
+ return join9(targetDir, CONFIG_FILE);
1782
+ }
1783
+ function readAddonConfig(targetDir) {
1784
+ const configPath = getConfigPath(targetDir);
1785
+ const legacyPath = getLegacyConfigPath(targetDir);
1786
+ if (!existsSync6(configPath) && existsSync6(legacyPath)) {
1787
+ mkdirSync(dirname3(configPath), { recursive: true });
1788
+ renameSync(legacyPath, configPath);
1789
+ }
1790
+ if (!existsSync6(configPath)) {
1791
+ return { agents: ["claude"], installed: {} };
1792
+ }
1793
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1794
+ const data = raw.addons ?? raw;
1795
+ return {
1796
+ version: 1,
1797
+ mode: data.mode,
1798
+ agents: data.agents ?? ["claude"],
1799
+ installed: data.installed ?? {}
1800
+ };
1801
+ }
1802
+ function writeAddonConfig(targetDir, config) {
1803
+ const configPath = getConfigPath(targetDir);
1804
+ mkdirSync(dirname3(configPath), { recursive: true });
1805
+ const payload = { version: 1, ...config };
1806
+ writeFileSync(configPath, JSON.stringify(payload, null, 2) + "\n");
1807
+ }
1808
+ function writeAddonToConfig(targetDir, name, entry) {
1809
+ const config = readAddonConfig(targetDir);
1810
+ config.installed[name] = entry;
1811
+ writeAddonConfig(targetDir, config);
1812
+ }
1813
+ function removeAddonFromConfig(targetDir, name) {
1814
+ const config = readAddonConfig(targetDir);
1815
+ delete config.installed[name];
1816
+ writeAddonConfig(targetDir, config);
1817
+ }
1818
+ function readMode(targetDir) {
1819
+ const config = readAddonConfig(targetDir);
1820
+ return config.mode ?? "hitl";
1821
+ }
1822
+ function writeMode(targetDir, mode) {
1823
+ const config = readAddonConfig(targetDir);
1824
+ config.mode = mode;
1825
+ writeAddonConfig(targetDir, config);
1826
+ }
1827
+ function detectOrphanedAddonFiles(targetDir) {
1828
+ const config = readAddonConfig(targetDir);
1829
+ const orphaned = [];
1830
+ for (const addon of getAvailableAddons()) {
1831
+ if (config.installed[addon.name]) continue;
1832
+ const skillDir = join9(targetDir, ".claude", "skills", addon.name);
1833
+ if (existsSync6(skillDir)) {
1834
+ orphaned.push(addon.name);
1835
+ }
1836
+ }
1837
+ return orphaned;
1838
+ }
1839
+ function registerAddonInConfig(targetDir, addonName) {
1840
+ const manifest = getAddonManifest(addonName);
1841
+ const fileList = [
1842
+ ...(manifest.files.skills ?? []).map((s) => `skills/${s}`),
1843
+ ...(manifest.files.agents ?? []).map((a) => `agents/${a}.md`),
1844
+ ...(manifest.files.rules ?? []).map((r) => `rules/${r}`)
1845
+ ];
1846
+ writeAddonToConfig(targetDir, addonName, {
1847
+ version: manifest.version,
1848
+ files: fileList
1849
+ });
1850
+ }
1851
+
1852
+ // src/generators/addonFiles.ts
1853
+ import {
1854
+ existsSync as existsSync7,
1855
+ readFileSync as readFileSync4,
1856
+ writeFileSync as writeFileSync2,
1857
+ mkdirSync as mkdirSync2,
1858
+ rmSync,
1859
+ unlinkSync,
1860
+ readdirSync,
1861
+ rmdirSync
1862
+ } from "fs";
1863
+ import { join as join10, dirname as dirname4 } from "path";
1864
+ import { createHash } from "crypto";
1865
+ var AGENT_PATHS = {
1866
+ claude: ".claude",
1867
+ cursor: ".cursor",
1868
+ gemini: ".gemini"
1869
+ };
1870
+ function stripFrontmatterName(content) {
1871
+ return content.replace(/^(---\n)([\s\S]*?)(---)/m, (_match, open, body, close) => {
1872
+ const cleaned = body.split("\n").filter((l) => !l.startsWith("name:")).join("\n");
1873
+ return `${open}${cleaned}${close}`;
1874
+ });
1875
+ }
1876
+ var RUNTIME_SPECS = {
1877
+ claude: {
1878
+ baseDir: ".claude",
1879
+ skillAdapter: (name, content) => ({
1880
+ relPath: `skills/${name}/SKILL.md`,
1881
+ content
1882
+ }),
1883
+ rulesDir: "rules"
1884
+ },
1885
+ gemini: {
1886
+ baseDir: ".gemini",
1887
+ skillAdapter: (name, content) => ({
1888
+ relPath: `skills/${name}/SKILL.md`,
1889
+ content
1890
+ }),
1891
+ rulesDir: "rules"
1892
+ },
1893
+ opencode: {
1894
+ baseDir: ".opencode",
1895
+ skillAdapter: (name, content) => ({
1896
+ relPath: `command/${name}.md`,
1897
+ content: stripFrontmatterName(content)
1898
+ })
1899
+ },
1900
+ cursor: {
1901
+ baseDir: ".cursor",
1902
+ skillAdapter: (name, content) => ({
1903
+ relPath: `skills/${name}/SKILL.md`,
1904
+ content
1905
+ }),
1906
+ rulesDir: "rules"
1907
+ },
1908
+ codex: {
1909
+ baseDir: ".codex",
1910
+ skillAdapter: (name, content) => ({
1911
+ relPath: `skills/${name}/SKILL.md`,
1912
+ content
1913
+ })
1914
+ }
1915
+ };
1916
+ function checksum(content) {
1917
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
1918
+ }
1919
+ function ensureDir2(dir) {
1920
+ if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1921
+ }
1922
+ function readManifest2(addonSourceDir) {
1923
+ const manifestPath = join10(addonSourceDir, "manifest.json");
1924
+ return JSON.parse(readFileSync4(manifestPath, "utf-8"));
1925
+ }
1926
+ function buildFileMap(addonSourceDir) {
1927
+ const manifest = readManifest2(addonSourceDir);
1928
+ const files = /* @__PURE__ */ new Map();
1929
+ for (const skill of manifest.files.skills ?? []) {
1930
+ const skillDir = join10(addonSourceDir, "skills", skill);
1931
+ if (!existsSync7(skillDir)) continue;
1932
+ const skillFile = join10(skillDir, "SKILL.md");
1933
+ if (existsSync7(skillFile)) {
1934
+ files.set(`skills/${skill}/SKILL.md`, readFileSync4(skillFile, "utf-8"));
1935
+ }
1936
+ }
1937
+ for (const agent of manifest.files.agents ?? []) {
1938
+ const agentFile = join10(addonSourceDir, "agents", `${agent}.md`);
1939
+ if (existsSync7(agentFile)) {
1940
+ files.set(`agents/${agent}.md`, readFileSync4(agentFile, "utf-8"));
1941
+ }
1942
+ }
1943
+ for (const ref of manifest.files.reference ?? []) {
1944
+ const refPath = join10(addonSourceDir, "reference", ref);
1945
+ if (existsSync7(refPath)) {
1946
+ files.set(`skills/design-harden/reference/${ref}`, readFileSync4(refPath, "utf-8"));
1947
+ }
1948
+ }
1949
+ for (const rule of manifest.files.rules ?? []) {
1950
+ const rulePath = join10(addonSourceDir, "rules", rule);
1951
+ if (existsSync7(rulePath)) {
1952
+ files.set(`rules/${rule}`, readFileSync4(rulePath, "utf-8"));
1953
+ }
1954
+ }
1955
+ return files;
1956
+ }
1957
+ function generateAddonFiles(projectDir, addonSourceDir, agents) {
1958
+ const fileMap = buildFileMap(addonSourceDir);
1959
+ const manifest = readManifest2(addonSourceDir);
1960
+ const result = { written: 0, skipped: 0, conflicts: [], checksums: {} };
1961
+ for (const agent of agents) {
1962
+ const spec = RUNTIME_SPECS[agent];
1963
+ if (!spec) {
1964
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
1965
+ for (const [relPath, content] of fileMap) {
1966
+ const destPath = join10(projectDir, basePath, relPath);
1967
+ if (existsSync7(destPath)) {
1968
+ result.skipped++;
1969
+ continue;
1970
+ }
1971
+ ensureDir2(dirname4(destPath));
1972
+ writeFileSync2(destPath, content);
1973
+ result.written++;
1974
+ result.checksums[relPath] = checksum(content);
1975
+ }
1976
+ continue;
1977
+ }
1978
+ for (const skillName of manifest.files.skills ?? []) {
1979
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
1980
+ if (!existsSync7(skillFile)) continue;
1981
+ const rawContent = readFileSync4(skillFile, "utf-8");
1982
+ const { relPath, content } = spec.skillAdapter(skillName, rawContent);
1983
+ const destPath = join10(projectDir, spec.baseDir, relPath);
1984
+ if (existsSync7(destPath)) {
1985
+ const existing = readFileSync4(destPath, "utf-8");
1986
+ if (existing === content) {
1987
+ result.skipped++;
1988
+ continue;
1989
+ }
1990
+ result.skipped++;
1991
+ continue;
1992
+ }
1993
+ ensureDir2(dirname4(destPath));
1994
+ writeFileSync2(destPath, content);
1995
+ result.written++;
1996
+ result.checksums[relPath] = checksum(content);
1997
+ }
1998
+ for (const agentName of manifest.files.agents ?? []) {
1999
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2000
+ if (!existsSync7(agentFile)) continue;
2001
+ const content = readFileSync4(agentFile, "utf-8");
2002
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2003
+ if (!existsSync7(destPath)) {
2004
+ ensureDir2(dirname4(destPath));
2005
+ writeFileSync2(destPath, content);
2006
+ result.written++;
2007
+ result.checksums[`agents/${agentName}.md`] = checksum(content);
2008
+ } else {
2009
+ result.skipped++;
2010
+ }
2011
+ }
2012
+ if (spec.rulesDir) {
2013
+ for (const rule of manifest.files.rules ?? []) {
2014
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2015
+ if (!existsSync7(ruleSrcPath)) continue;
2016
+ const content = readFileSync4(ruleSrcPath, "utf-8");
2017
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2018
+ if (!existsSync7(destPath)) {
2019
+ ensureDir2(dirname4(destPath));
2020
+ writeFileSync2(destPath, content);
2021
+ result.written++;
2022
+ result.checksums[`rules/${rule}`] = checksum(content);
2023
+ } else {
2024
+ result.skipped++;
2025
+ }
2026
+ }
2027
+ }
2028
+ for (const ref of manifest.files.reference ?? []) {
2029
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2030
+ if (!existsSync7(refSrcPath)) continue;
2031
+ const content = readFileSync4(refSrcPath, "utf-8");
2032
+ const relPath = `skills/design-harden/reference/${ref}`;
2033
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2034
+ if (!existsSync7(destPath)) {
2035
+ ensureDir2(dirname4(destPath));
2036
+ writeFileSync2(destPath, content);
2037
+ result.written++;
2038
+ result.checksums[relPath] = checksum(content);
2039
+ } else {
2040
+ result.skipped++;
2041
+ }
2042
+ }
2043
+ }
2044
+ if (manifest.attribution) {
2045
+ const noticePath = join10(projectDir, "NOTICE.md");
2046
+ const noticeContent = [
2047
+ "# NOTICE",
2048
+ "",
2049
+ "This project includes materials from third-party sources.",
2050
+ "",
2051
+ `## ${manifest.name}`,
2052
+ "",
2053
+ manifest.attribution,
2054
+ "",
2055
+ "Original source: Anthropic frontend-design skill",
2056
+ "License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)",
2057
+ ""
2058
+ ].join("\n");
2059
+ writeFileSync2(noticePath, noticeContent);
2060
+ }
2061
+ return result;
2062
+ }
2063
+ function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
2064
+ const sourceDir = addonSourceDir ?? getAddonSourceDir(addonName);
2065
+ const manifest = readManifest2(sourceDir);
2066
+ const knownSkills = manifest.files.skills ?? [];
2067
+ const knownAgents = manifest.files.agents ?? [];
2068
+ for (const agent of agents) {
2069
+ const spec = RUNTIME_SPECS[agent];
2070
+ if (!spec) {
2071
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2072
+ for (const skill of knownSkills) {
2073
+ const skillDir = join10(projectDir, basePath, "skills", skill);
2074
+ if (existsSync7(skillDir)) rmSync(skillDir, { recursive: true, force: true });
2075
+ }
2076
+ for (const agentName of knownAgents) {
2077
+ const agentPath = join10(projectDir, basePath, "agents", `${agentName}.md`);
2078
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2079
+ }
2080
+ continue;
2081
+ }
2082
+ for (const skillName of knownSkills) {
2083
+ const { relPath } = spec.skillAdapter(skillName, "");
2084
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2085
+ if (existsSync7(destPath)) unlinkSync(destPath);
2086
+ const parentDir = dirname4(destPath);
2087
+ if (existsSync7(parentDir)) {
2088
+ try {
2089
+ const entries = readdirSync(parentDir);
2090
+ if (entries.length === 0) rmdirSync(parentDir);
2091
+ } catch {
2092
+ }
2093
+ }
2094
+ }
2095
+ for (const agentName of knownAgents) {
2096
+ const agentPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2097
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2098
+ }
2099
+ if (spec.rulesDir) {
2100
+ for (const rule of manifest.files.rules ?? []) {
2101
+ const rulePath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2102
+ if (existsSync7(rulePath)) unlinkSync(rulePath);
2103
+ }
2104
+ }
2105
+ for (const ref of manifest.files.reference ?? []) {
2106
+ const refPath = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference", ref);
2107
+ if (existsSync7(refPath)) unlinkSync(refPath);
2108
+ }
2109
+ const refDir = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference");
2110
+ if (existsSync7(refDir)) {
2111
+ try {
2112
+ const entries = readdirSync(refDir);
2113
+ if (entries.length === 0) rmdirSync(refDir);
2114
+ } catch {
2115
+ }
2116
+ }
2117
+ }
2118
+ const noticePath = join10(projectDir, "NOTICE.md");
2119
+ if (existsSync7(noticePath)) unlinkSync(noticePath);
2120
+ }
2121
+ function syncAddonFiles(projectDir, addonSourceDir, agents) {
2122
+ const fileMap = buildFileMap(addonSourceDir);
2123
+ const manifest = readManifest2(addonSourceDir);
2124
+ const addonName = manifest.name;
2125
+ const result = { written: 0, skipped: 0, conflicts: [], updated: 0 };
2126
+ let installedChecksums = {};
2127
+ try {
2128
+ const config = readAddonConfig(projectDir);
2129
+ const installed = config.installed?.[addonName];
2130
+ if (installed?.checksums) {
2131
+ installedChecksums = installed.checksums;
2132
+ }
2133
+ } catch {
2134
+ }
2135
+ for (const agent of agents) {
2136
+ const spec = RUNTIME_SPECS[agent];
2137
+ if (!spec) {
2138
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2139
+ for (const [relPath, newContent] of fileMap) {
2140
+ const destPath = join10(projectDir, basePath, relPath);
2141
+ if (!existsSync7(destPath)) {
2142
+ ensureDir2(dirname4(destPath));
2143
+ writeFileSync2(destPath, newContent);
2144
+ result.written++;
2145
+ continue;
2146
+ }
2147
+ const existing = readFileSync4(destPath, "utf-8");
2148
+ const existingChecksum = checksum(existing);
2149
+ const originalChecksum = installedChecksums[relPath];
2150
+ if (existing === newContent) {
2151
+ result.skipped++;
2152
+ continue;
2153
+ }
2154
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2155
+ result.conflicts.push(relPath);
2156
+ continue;
2157
+ }
2158
+ writeFileSync2(destPath, newContent);
2159
+ result.updated = (result.updated ?? 0) + 1;
2160
+ }
2161
+ continue;
2162
+ }
2163
+ for (const skillName of manifest.files.skills ?? []) {
2164
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
2165
+ if (!existsSync7(skillFile)) continue;
2166
+ const rawContent = readFileSync4(skillFile, "utf-8");
2167
+ const { relPath, content: newContent } = spec.skillAdapter(skillName, rawContent);
2168
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2169
+ if (!existsSync7(destPath)) {
2170
+ ensureDir2(dirname4(destPath));
2171
+ writeFileSync2(destPath, newContent);
2172
+ result.written++;
2173
+ continue;
2174
+ }
2175
+ const existing = readFileSync4(destPath, "utf-8");
2176
+ const existingChecksum = checksum(existing);
2177
+ const originalChecksum = installedChecksums[relPath];
2178
+ if (existing === newContent) {
2179
+ result.skipped++;
2180
+ continue;
2181
+ }
2182
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2183
+ result.conflicts.push(relPath);
2184
+ continue;
2185
+ }
2186
+ writeFileSync2(destPath, newContent);
2187
+ result.updated = (result.updated ?? 0) + 1;
2188
+ }
2189
+ for (const agentName of manifest.files.agents ?? []) {
2190
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2191
+ if (!existsSync7(agentFile)) continue;
2192
+ const newContent = readFileSync4(agentFile, "utf-8");
2193
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2194
+ const relPath = `agents/${agentName}.md`;
2195
+ if (!existsSync7(destPath)) {
2196
+ ensureDir2(dirname4(destPath));
2197
+ writeFileSync2(destPath, newContent);
2198
+ result.written++;
2199
+ continue;
2200
+ }
2201
+ const existing = readFileSync4(destPath, "utf-8");
2202
+ const existingChecksum = checksum(existing);
2203
+ const originalChecksum = installedChecksums[relPath];
2204
+ if (existing === newContent) {
2205
+ result.skipped++;
2206
+ continue;
2207
+ }
2208
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2209
+ result.conflicts.push(relPath);
2210
+ continue;
2211
+ }
2212
+ writeFileSync2(destPath, newContent);
2213
+ result.updated = (result.updated ?? 0) + 1;
2214
+ }
2215
+ if (spec.rulesDir) {
2216
+ for (const rule of manifest.files.rules ?? []) {
2217
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2218
+ if (!existsSync7(ruleSrcPath)) continue;
2219
+ const newContent = readFileSync4(ruleSrcPath, "utf-8");
2220
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2221
+ const relPath = `rules/${rule}`;
2222
+ if (!existsSync7(destPath)) {
2223
+ ensureDir2(dirname4(destPath));
2224
+ writeFileSync2(destPath, newContent);
2225
+ result.written++;
2226
+ continue;
2227
+ }
2228
+ const existing = readFileSync4(destPath, "utf-8");
2229
+ const existingChecksum = checksum(existing);
2230
+ const originalChecksum = installedChecksums[relPath];
2231
+ if (existing === newContent) {
2232
+ result.skipped++;
2233
+ continue;
2234
+ }
2235
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2236
+ result.conflicts.push(relPath);
2237
+ continue;
2238
+ }
2239
+ writeFileSync2(destPath, newContent);
2240
+ result.updated = (result.updated ?? 0) + 1;
2241
+ }
2242
+ }
2243
+ for (const ref of manifest.files.reference ?? []) {
2244
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2245
+ if (!existsSync7(refSrcPath)) continue;
2246
+ const newContent = readFileSync4(refSrcPath, "utf-8");
2247
+ const relPath = `skills/design-harden/reference/${ref}`;
2248
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2249
+ if (!existsSync7(destPath)) {
2250
+ ensureDir2(dirname4(destPath));
2251
+ writeFileSync2(destPath, newContent);
2252
+ result.written++;
2253
+ continue;
2254
+ }
2255
+ const existing = readFileSync4(destPath, "utf-8");
2256
+ const existingChecksum = checksum(existing);
2257
+ const originalChecksum = installedChecksums[relPath];
2258
+ if (existing === newContent) {
2259
+ result.skipped++;
2260
+ continue;
2261
+ }
2262
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2263
+ result.conflicts.push(relPath);
2264
+ continue;
2265
+ }
2266
+ writeFileSync2(destPath, newContent);
2267
+ result.updated = (result.updated ?? 0) + 1;
2268
+ }
2269
+ }
2270
+ return result;
2271
+ }
2272
+ function detectModifiedAddonFiles(projectDir, addonName) {
2273
+ let installedChecksums = {};
2274
+ try {
2275
+ const config = readAddonConfig(projectDir);
2276
+ const installed = config.installed?.[addonName];
2277
+ if (!installed?.checksums) return [];
2278
+ installedChecksums = installed.checksums;
2279
+ } catch {
2280
+ return [];
2281
+ }
2282
+ const modified = [];
2283
+ for (const agentDir of Object.values(AGENT_PATHS)) {
2284
+ for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
2285
+ const absPath = join10(projectDir, agentDir, relPath);
2286
+ if (!existsSync7(absPath)) continue;
2287
+ const current = checksum(readFileSync4(absPath, "utf-8"));
2288
+ if (current !== originalHash) {
2289
+ modified.push(relPath);
2290
+ }
2291
+ }
2292
+ }
2293
+ return [...new Set(modified)];
2294
+ }
2295
+
1738
2296
  // src/commands/update.ts
1739
2297
  async function updateCommand(options) {
1740
2298
  if (!options.check && !options.dryRun) {
@@ -1800,7 +2358,7 @@ async function updateCommand(options) {
1800
2358
  const modifiedFiles = [];
1801
2359
  const outdatedFiles = [];
1802
2360
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
1803
- const filePath = join8(targetDir, relativePath);
2361
+ const filePath = join11(targetDir, relativePath);
1804
2362
  if (!fileExists(filePath)) {
1805
2363
  continue;
1806
2364
  }
@@ -1812,11 +2370,11 @@ async function updateCommand(options) {
1812
2370
  }
1813
2371
  const newFiles = [];
1814
2372
  for (const ide of manifest.selectedIDEs) {
1815
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1816
- if (!existsSync6(templateDir)) continue;
2373
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2374
+ if (!existsSync8(templateDir)) continue;
1817
2375
  const files = getAllFilesRecursive(templateDir);
1818
2376
  for (const file of files) {
1819
- const templatePath = join8(templateDir, file);
2377
+ const templatePath = join11(templateDir, file);
1820
2378
  const templateContent = readFile(templatePath);
1821
2379
  const templateChecksum = calculateChecksum(templateContent);
1822
2380
  const fileInfo = manifest.files[file];
@@ -1832,14 +2390,14 @@ async function updateCommand(options) {
1832
2390
  if (fileInfo.ignored) continue;
1833
2391
  let foundInAnyTemplate = false;
1834
2392
  for (const ide of manifest.selectedIDEs) {
1835
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1836
- if (existsSync6(join8(templateDir, relativePath))) {
2393
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2394
+ if (existsSync8(join11(templateDir, relativePath))) {
1837
2395
  foundInAnyTemplate = true;
1838
2396
  break;
1839
2397
  }
1840
2398
  }
1841
2399
  if (!foundInAnyTemplate) {
1842
- const localPath = join8(targetDir, relativePath);
2400
+ const localPath = join11(targetDir, relativePath);
1843
2401
  if (fileExists(localPath)) {
1844
2402
  removedFromTemplate.push({
1845
2403
  path: relativePath,
@@ -1967,8 +2525,8 @@ async function updateCommand(options) {
1967
2525
  implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1968
2526
  };
1969
2527
  for (const ide of manifest.selectedIDEs) {
1970
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1971
- if (!existsSync6(templateDir)) continue;
2528
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2529
+ if (!existsSync8(templateDir)) continue;
1972
2530
  const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
1973
2531
  const files = getAllFilesRecursive(templateDir);
1974
2532
  for (const file of files) {
@@ -1978,10 +2536,10 @@ async function updateCommand(options) {
1978
2536
  if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
1979
2537
  continue;
1980
2538
  }
1981
- const templatePath = join8(templateDir, file);
1982
- const destPath = join8(targetDir, file);
2539
+ const templatePath = join11(templateDir, file);
2540
+ const destPath = join11(targetDir, file);
1983
2541
  const templateContent = readFile(templatePath);
1984
- ensureDir(dirname3(destPath));
2542
+ ensureDir(dirname5(destPath));
1985
2543
  writeFile(destPath, templateContent);
1986
2544
  updatedManifest.files[file] = createManifestEntry(templateContent);
1987
2545
  }
@@ -1989,7 +2547,7 @@ async function updateCommand(options) {
1989
2547
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
1990
2548
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
1991
2549
  for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
1992
- const filePath = join8(targetDir, relPath);
2550
+ const filePath = join11(targetDir, relPath);
1993
2551
  if (!fileExists(filePath)) continue;
1994
2552
  const diskContent = readFile(filePath);
1995
2553
  const diskChecksum = calculateChecksum(diskContent);
@@ -2006,13 +2564,13 @@ async function updateCommand(options) {
2006
2564
  analysis.packageManager
2007
2565
  );
2008
2566
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2009
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2010
- if (existsSync6(scriptPath)) {
2567
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2568
+ if (existsSync8(scriptPath)) {
2011
2569
  chmodSync2(scriptPath, 493);
2012
2570
  }
2013
2571
  }
2014
2572
  for (const [relPath, content] of userModifiedPluginFiles) {
2015
- writeFile(join8(targetDir, relPath), content);
2573
+ writeFile(join11(targetDir, relPath), content);
2016
2574
  }
2017
2575
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2018
2576
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2023,9 +2581,9 @@ async function updateCommand(options) {
2023
2581
  updatedManifest.pluginPath = pluginResult.pluginPath;
2024
2582
  }
2025
2583
  for (const filePath of filesToDelete) {
2026
- const localPath = join8(targetDir, filePath);
2584
+ const localPath = join11(targetDir, filePath);
2027
2585
  if (fileExists(localPath)) {
2028
- unlinkSync(localPath);
2586
+ unlinkSync2(localPath);
2029
2587
  }
2030
2588
  delete updatedManifest.files[filePath];
2031
2589
  }
@@ -2034,12 +2592,12 @@ async function updateCommand(options) {
2034
2592
  updatedManifest.files[filePath].ignored = true;
2035
2593
  }
2036
2594
  }
2037
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2595
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2038
2596
  if (fileExists(claudeMdPath)) {
2039
2597
  const stat = lstatSync(claudeMdPath);
2040
2598
  if (stat.isSymbolicLink()) {
2041
2599
  const content = readFile(claudeMdPath);
2042
- unlinkSync(claudeMdPath);
2600
+ unlinkSync2(claudeMdPath);
2043
2601
  writeFile(claudeMdPath, content);
2044
2602
  updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
2045
2603
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2047,6 +2605,28 @@ async function updateCommand(options) {
2047
2605
  }
2048
2606
  writeManifest(targetDir, updatedManifest);
2049
2607
  spinner8.stop("Updates applied");
2608
+ const orphaned = detectOrphanedAddonFiles(targetDir);
2609
+ if (orphaned.length > 0) {
2610
+ const migSpinner = p4.spinner();
2611
+ migSpinner.start("Migrating detected addon files to config...");
2612
+ for (const addonName of orphaned) {
2613
+ registerAddonInConfig(targetDir, addonName);
2614
+ }
2615
+ migSpinner.stop(`${symbols.pass} Migrated: ${orphaned.join(", ")}`);
2616
+ }
2617
+ const addonConfig = readAddonConfig(targetDir);
2618
+ const enabledAddons = Object.keys(addonConfig.installed);
2619
+ if (enabledAddons.length > 0 && !options.check) {
2620
+ const syncSpinner = p4.spinner();
2621
+ syncSpinner.start("Updating enabled addon files...");
2622
+ let totalUpdated = 0;
2623
+ for (const name of enabledAddons) {
2624
+ const addonSourceDir = getAddonSourceDir(name);
2625
+ const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
2626
+ totalUpdated += (result.updated ?? 0) + result.written;
2627
+ }
2628
+ syncSpinner.stop(`${symbols.pass} Addon files updated (${totalUpdated} files)`);
2629
+ }
2050
2630
  p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
2051
2631
  }
2052
2632
  function detectStackChanges(savedConfig, analysis) {
@@ -2153,7 +2733,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2153
2733
  const spinner8 = p4.spinner();
2154
2734
  spinner8.start("Regenerating with new stack...");
2155
2735
  const regeneratedFiles = [];
2156
- const agentsMdPath = join8(targetDir, "AGENTS.md");
2736
+ const agentsMdPath = join11(targetDir, "AGENTS.md");
2157
2737
  if (fileExists(agentsMdPath)) {
2158
2738
  const agentsMdContent = generateAgentsMdFromConfig(
2159
2739
  newConfig,
@@ -2164,12 +2744,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2164
2744
  regeneratedFiles.push("AGENTS.md");
2165
2745
  manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
2166
2746
  }
2167
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2747
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2168
2748
  if (fileExists(claudeMdPath)) {
2169
2749
  const stat = lstatSync(claudeMdPath);
2170
2750
  if (stat.isSymbolicLink()) {
2171
2751
  const content = readFile(claudeMdPath);
2172
- unlinkSync(claudeMdPath);
2752
+ unlinkSync2(claudeMdPath);
2173
2753
  writeFile(claudeMdPath, content);
2174
2754
  manifest.files["CLAUDE.md"] = createManifestEntry(content);
2175
2755
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2181,8 +2761,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2181
2761
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2182
2762
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2183
2763
  if (ruleContent && rulePath) {
2184
- const destPath = join8(targetDir, rulePath);
2185
- ensureDir(dirname3(destPath));
2764
+ const destPath = join11(targetDir, rulePath);
2765
+ ensureDir(dirname5(destPath));
2186
2766
  writeFile(destPath, ruleContent);
2187
2767
  regeneratedFiles.push(rulePath);
2188
2768
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2192,7 +2772,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2192
2772
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
2193
2773
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
2194
2774
  for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
2195
- const filePath = join8(targetDir, relPath);
2775
+ const filePath = join11(targetDir, relPath);
2196
2776
  if (!fileExists(filePath)) continue;
2197
2777
  const diskContent = readFile(filePath);
2198
2778
  const diskChecksum = calculateChecksum(diskContent);
@@ -2208,13 +2788,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2208
2788
  analysis.packageManager
2209
2789
  );
2210
2790
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2211
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2212
- if (existsSync6(scriptPath)) {
2791
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2792
+ if (existsSync8(scriptPath)) {
2213
2793
  chmodSync2(scriptPath, 493);
2214
2794
  }
2215
2795
  }
2216
2796
  for (const [relPath, content] of userModifiedPluginFiles) {
2217
- writeFile(join8(targetDir, relPath), content);
2797
+ writeFile(join11(targetDir, relPath), content);
2218
2798
  }
2219
2799
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2220
2800
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2257,8 +2837,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2257
2837
  analysis.packageManager
2258
2838
  );
2259
2839
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2260
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2261
- if (existsSync6(scriptPath)) {
2840
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2841
+ if (existsSync8(scriptPath)) {
2262
2842
  chmodSync2(scriptPath, 493);
2263
2843
  }
2264
2844
  }
@@ -2267,19 +2847,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2267
2847
  const preserved = [];
2268
2848
  for (const [path, fileInfo] of Object.entries(manifest.files)) {
2269
2849
  if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
2270
- const filePath = join8(targetDir, path);
2850
+ const filePath = join11(targetDir, path);
2271
2851
  if (!fileExists(filePath)) continue;
2272
2852
  const current = calculateChecksum(readFile(filePath));
2273
2853
  if (current === fileInfo.originalChecksum) {
2274
- unlinkSync(filePath);
2854
+ unlinkSync2(filePath);
2275
2855
  delete manifest.files[path];
2276
2856
  removed.push(path);
2277
2857
  } else {
2278
2858
  preserved.push(path);
2279
2859
  }
2280
2860
  }
2281
- cleanEmptyDirs(join8(targetDir, ".claude", "skills"));
2282
- cleanEmptyDirs(join8(targetDir, ".claude", "agents"));
2861
+ cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
2862
+ cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
2283
2863
  Object.assign(manifest.files, pluginResult.files);
2284
2864
  manifest.installMode = "plugin";
2285
2865
  manifest.pluginPath = pluginResult.pluginPath;
@@ -2328,22 +2908,22 @@ function buildDefaultConfig(analysis) {
2328
2908
  };
2329
2909
  }
2330
2910
  function cleanEmptyDirs(dirPath) {
2331
- if (!existsSync6(dirPath)) return;
2332
- const entries = readdirSync(dirPath);
2911
+ if (!existsSync8(dirPath)) return;
2912
+ const entries = readdirSync2(dirPath);
2333
2913
  for (const entry of entries) {
2334
- const fullPath = join8(dirPath, entry);
2335
- if (existsSync6(fullPath) && lstatSync(fullPath).isDirectory()) {
2914
+ const fullPath = join11(dirPath, entry);
2915
+ if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
2336
2916
  cleanEmptyDirs(fullPath);
2337
2917
  }
2338
2918
  }
2339
- const remaining = readdirSync(dirPath);
2919
+ const remaining = readdirSync2(dirPath);
2340
2920
  if (remaining.length === 0) {
2341
- rmdirSync(dirPath);
2921
+ rmdirSync2(dirPath);
2342
2922
  }
2343
2923
  }
2344
2924
 
2345
2925
  // src/commands/status.ts
2346
- import { resolve as resolve4, join as join9 } from "path";
2926
+ import { resolve as resolve4, join as join12 } from "path";
2347
2927
  import * as p5 from "@clack/prompts";
2348
2928
  import chalk6 from "chalk";
2349
2929
  async function statusCommand(options = {}) {
@@ -2357,6 +2937,22 @@ async function statusCommand(options = {}) {
2357
2937
  p5.outro("");
2358
2938
  return;
2359
2939
  }
2940
+ const mode = readMode(targetDir);
2941
+ const addonConfig = readAddonConfig(targetDir);
2942
+ const allAddons = getAvailableAddons();
2943
+ p5.note(
2944
+ [
2945
+ `${formatKV("Mode:", chalk6.cyan(mode))}`,
2946
+ `${formatKV("Config:", chalk6.dim(".claude/devtronic.json"))}`
2947
+ ].join("\n"),
2948
+ "Execution Mode"
2949
+ );
2950
+ const addonLines = allAddons.map((addon) => {
2951
+ const isEnabled = !!addonConfig.installed[addon.name];
2952
+ const statusStr = isEnabled ? chalk6.green("enabled") : chalk6.dim("disabled");
2953
+ return ` ${chalk6.bold((addon.name + ":").padEnd(28))} ${statusStr}`;
2954
+ });
2955
+ p5.note(addonLines.join("\n"), "Addons");
2360
2956
  p5.note(
2361
2957
  [
2362
2958
  formatKV("Version:", manifest.version),
@@ -2367,7 +2963,7 @@ async function statusCommand(options = {}) {
2367
2963
  );
2368
2964
  const fileStatuses = [];
2369
2965
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
2370
- const filePath = join9(targetDir, relativePath);
2966
+ const filePath = join12(targetDir, relativePath);
2371
2967
  if (!fileExists(filePath)) {
2372
2968
  fileStatuses.push({ path: relativePath, status: "missing" });
2373
2969
  continue;
@@ -2413,8 +3009,8 @@ async function statusCommand(options = {}) {
2413
3009
  }
2414
3010
 
2415
3011
  // src/commands/diff.ts
2416
- import { resolve as resolve5, join as join10 } from "path";
2417
- import { existsSync as existsSync7 } from "fs";
3012
+ import { resolve as resolve5, join as join13 } from "path";
3013
+ import { existsSync as existsSync9 } from "fs";
2418
3014
  import * as p6 from "@clack/prompts";
2419
3015
  import chalk7 from "chalk";
2420
3016
  async function diffCommand(options = {}) {
@@ -2429,12 +3025,12 @@ async function diffCommand(options = {}) {
2429
3025
  }
2430
3026
  const diffs = [];
2431
3027
  for (const ide of manifest.selectedIDEs) {
2432
- const templateDir = join10(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2433
- if (!existsSync7(templateDir)) continue;
3028
+ const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
3029
+ if (!existsSync9(templateDir)) continue;
2434
3030
  const templateFiles = getAllFilesRecursive(templateDir);
2435
3031
  for (const file of templateFiles) {
2436
- const templatePath = join10(templateDir, file);
2437
- const localPath = join10(targetDir, file);
3032
+ const templatePath = join13(templateDir, file);
3033
+ const localPath = join13(targetDir, file);
2438
3034
  const templateContent = readFile(templatePath);
2439
3035
  const templateChecksum = calculateChecksum(templateContent);
2440
3036
  const manifestEntry = manifest.files[file];
@@ -2487,8 +3083,8 @@ async function diffCommand(options = {}) {
2487
3083
  }
2488
3084
 
2489
3085
  // src/commands/add.ts
2490
- import { existsSync as existsSync8 } from "fs";
2491
- import { resolve as resolve6, join as join11, dirname as dirname4 } from "path";
3086
+ import { existsSync as existsSync10 } from "fs";
3087
+ import { resolve as resolve6, join as join14, dirname as dirname6 } from "path";
2492
3088
  import * as p7 from "@clack/prompts";
2493
3089
  import chalk8 from "chalk";
2494
3090
  var ALL_IDES = [
@@ -2582,8 +3178,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2582
3178
  const generatedFiles = [];
2583
3179
  const generatedRules = generateArchitectureRules(manifest.projectConfig);
2584
3180
  const templateName = IDE_TEMPLATE_MAP[selectedIDE];
2585
- const templateDir = join11(TEMPLATES_DIR, templateName);
2586
- if (!existsSync8(templateDir)) {
3181
+ const templateDir = join14(TEMPLATES_DIR, templateName);
3182
+ if (!existsSync10(templateDir)) {
2587
3183
  spinner8.stop("Error");
2588
3184
  p7.cancel(`Template not found: ${templateName}`);
2589
3185
  process.exit(1);
@@ -2594,8 +3190,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2594
3190
  if (dynamicFiles.includes(file)) {
2595
3191
  continue;
2596
3192
  }
2597
- const sourcePath = join11(templateDir, file);
2598
- const destPath = join11(targetDir, file);
3193
+ const sourcePath = join14(templateDir, file);
3194
+ const destPath = join14(targetDir, file);
2599
3195
  const sourceContent = readFile(sourcePath);
2600
3196
  if (fileExists(destPath)) {
2601
3197
  if (conflictResolution === "keep") {
@@ -2612,7 +3208,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2612
3208
  continue;
2613
3209
  }
2614
3210
  }
2615
- ensureDir(dirname4(destPath));
3211
+ ensureDir(dirname6(destPath));
2616
3212
  writeFile(destPath, sourceContent);
2617
3213
  appliedFiles.push(file);
2618
3214
  manifest.files[file] = createManifestEntry(sourceContent);
@@ -2621,7 +3217,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2621
3217
  if (ruleContent) {
2622
3218
  const rulePath = dynamicFiles[0];
2623
3219
  if (rulePath) {
2624
- const destPath = join11(targetDir, rulePath);
3220
+ const destPath = join14(targetDir, rulePath);
2625
3221
  if (fileExists(destPath) && conflictResolution === "keep") {
2626
3222
  skippedFiles.push(rulePath);
2627
3223
  } else if (fileExists(destPath) && conflictResolution === "merge") {
@@ -2632,7 +3228,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2632
3228
  mergedFiles.push(rulePath);
2633
3229
  manifest.files[rulePath] = createManifestEntry(mergedContent);
2634
3230
  } else {
2635
- ensureDir(dirname4(destPath));
3231
+ ensureDir(dirname6(destPath));
2636
3232
  writeFile(destPath, ruleContent);
2637
3233
  generatedFiles.push(`${rulePath} (personalized)`);
2638
3234
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2673,14 +3269,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2673
3269
  }
2674
3270
 
2675
3271
  // src/commands/regenerate.ts
2676
- import { existsSync as existsSync9 } from "fs";
2677
- import { resolve as resolve7, join as join12, dirname as dirname5 } from "path";
3272
+ import { existsSync as existsSync11 } from "fs";
3273
+ import { resolve as resolve7, join as join15, dirname as dirname7 } from "path";
2678
3274
  import { fileURLToPath as fileURLToPath3 } from "url";
2679
3275
  import * as p8 from "@clack/prompts";
2680
3276
  import chalk9 from "chalk";
2681
3277
  var __filename = fileURLToPath3(import.meta.url);
2682
- var __regen_dirname = dirname5(__filename);
2683
- var TEMPLATES_DIR2 = existsSync9(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
3278
+ var __regen_dirname = dirname7(__filename);
3279
+ var TEMPLATES_DIR2 = existsSync11(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
2684
3280
  async function regenerateCommand(target, options) {
2685
3281
  ensureInteractive("regenerate");
2686
3282
  const targetDir = resolve7(options.path || ".");
@@ -2757,7 +3353,7 @@ Valid options:
2757
3353
  return;
2758
3354
  }
2759
3355
  if (regenerateClaudeMd) {
2760
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3356
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2761
3357
  if (fileExists(claudeMdPath)) {
2762
3358
  p8.log.warn(
2763
3359
  "Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
@@ -2794,7 +3390,7 @@ Valid options:
2794
3390
  spinner8.start("Regenerating files...");
2795
3391
  const regeneratedFiles = [];
2796
3392
  if (regenerateClaudeMd) {
2797
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3393
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2798
3394
  const claudeMdContent = generateClaudeMd(
2799
3395
  fullProjectConfig,
2800
3396
  analysis.scripts,
@@ -2805,7 +3401,7 @@ Valid options:
2805
3401
  manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
2806
3402
  }
2807
3403
  if (regenerateAgentsMd) {
2808
- const agentsMdPath = join12(targetDir, "AGENTS.md");
3404
+ const agentsMdPath = join15(targetDir, "AGENTS.md");
2809
3405
  const agentsMdContent = generateAgentsMdFromConfig(
2810
3406
  fullProjectConfig,
2811
3407
  analysis.scripts,
@@ -2822,8 +3418,8 @@ Valid options:
2822
3418
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2823
3419
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2824
3420
  if (ruleContent && rulePath) {
2825
- const destPath = join12(targetDir, rulePath);
2826
- ensureDir(dirname5(destPath));
3421
+ const destPath = join15(targetDir, rulePath);
3422
+ ensureDir(dirname7(destPath));
2827
3423
  writeFile(destPath, ruleContent);
2828
3424
  regeneratedFiles.push(rulePath);
2829
3425
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2837,7 +3433,7 @@ Valid options:
2837
3433
  if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
2838
3434
  p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
2839
3435
  } else {
2840
- const { generatePlugin: generatePlugin2 } = await import("./plugin-5ZMTSRIW.js");
3436
+ const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
2841
3437
  const pluginResult = generatePlugin2(
2842
3438
  targetDir,
2843
3439
  TEMPLATES_DIR2,
@@ -2872,8 +3468,8 @@ Valid options:
2872
3468
  }
2873
3469
 
2874
3470
  // src/commands/info.ts
2875
- import { resolve as resolve8, join as join13 } from "path";
2876
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
3471
+ import { resolve as resolve8, join as join16 } from "path";
3472
+ import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
2877
3473
  import * as p9 from "@clack/prompts";
2878
3474
  import chalk10 from "chalk";
2879
3475
  async function infoCommand() {
@@ -2886,33 +3482,33 @@ async function infoCommand() {
2886
3482
  let skillCount = 0;
2887
3483
  let agentCount = 0;
2888
3484
  if (manifest) {
2889
- const pluginDir = manifest.pluginPath ? join13(targetDir, manifest.pluginPath) : null;
2890
- if (pluginDir && existsSync10(pluginDir)) {
2891
- const skillsDir = join13(pluginDir, "skills");
2892
- const agentsDir = join13(pluginDir, "agents");
2893
- if (existsSync10(skillsDir)) {
2894
- skillCount = readdirSync2(skillsDir, { withFileTypes: true }).filter(
3485
+ const pluginDir = manifest.pluginPath ? join16(targetDir, manifest.pluginPath) : null;
3486
+ if (pluginDir && existsSync12(pluginDir)) {
3487
+ const skillsDir = join16(pluginDir, "skills");
3488
+ const agentsDir = join16(pluginDir, "agents");
3489
+ if (existsSync12(skillsDir)) {
3490
+ skillCount = readdirSync3(skillsDir, { withFileTypes: true }).filter(
2895
3491
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2896
3492
  ).length;
2897
3493
  }
2898
- if (existsSync10(agentsDir)) {
2899
- agentCount = readdirSync2(agentsDir, { withFileTypes: true }).filter(
3494
+ if (existsSync12(agentsDir)) {
3495
+ agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
2900
3496
  (e) => e.isFile() && e.name.endsWith(".md")
2901
3497
  ).length;
2902
3498
  }
2903
3499
  }
2904
3500
  if (skillCount === 0) {
2905
- const claudeSkills = join13(targetDir, ".claude", "skills");
2906
- if (existsSync10(claudeSkills)) {
2907
- skillCount = readdirSync2(claudeSkills, { withFileTypes: true }).filter(
3501
+ const claudeSkills = join16(targetDir, ".claude", "skills");
3502
+ if (existsSync12(claudeSkills)) {
3503
+ skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
2908
3504
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2909
3505
  ).length;
2910
3506
  }
2911
3507
  }
2912
3508
  if (agentCount === 0) {
2913
- const claudeAgents = join13(targetDir, ".claude", "agents");
2914
- if (existsSync10(claudeAgents)) {
2915
- agentCount = readdirSync2(claudeAgents, { withFileTypes: true }).filter(
3509
+ const claudeAgents = join16(targetDir, ".claude", "agents");
3510
+ if (existsSync12(claudeAgents)) {
3511
+ agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
2916
3512
  (e) => e.isFile() && e.name.endsWith(".md")
2917
3513
  ).length;
2918
3514
  }
@@ -2955,8 +3551,8 @@ async function infoCommand() {
2955
3551
  }
2956
3552
 
2957
3553
  // src/commands/list.ts
2958
- import { resolve as resolve9, join as join14 } from "path";
2959
- import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync2 } from "fs";
3554
+ import { resolve as resolve9, join as join17 } from "path";
3555
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
2960
3556
  import * as p10 from "@clack/prompts";
2961
3557
  import chalk11 from "chalk";
2962
3558
  async function listCommand(filter, options) {
@@ -2973,30 +3569,30 @@ Valid options: skills, agents`);
2973
3569
  }
2974
3570
  const skills = [];
2975
3571
  const agents = [];
2976
- const pluginDir = manifest?.pluginPath ? join14(targetDir, manifest.pluginPath) : null;
2977
- if (pluginDir && existsSync11(pluginDir)) {
3572
+ const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
3573
+ if (pluginDir && existsSync13(pluginDir)) {
2978
3574
  if (showSkills) {
2979
- const skillsDir = join14(pluginDir, "skills");
2980
- if (existsSync11(skillsDir)) {
3575
+ const skillsDir = join17(pluginDir, "skills");
3576
+ if (existsSync13(skillsDir)) {
2981
3577
  skills.push(...discoverSkills(skillsDir));
2982
3578
  }
2983
3579
  }
2984
3580
  if (showAgents) {
2985
- const agentsDir = join14(pluginDir, "agents");
2986
- if (existsSync11(agentsDir)) {
3581
+ const agentsDir = join17(pluginDir, "agents");
3582
+ if (existsSync13(agentsDir)) {
2987
3583
  agents.push(...discoverAgents(agentsDir));
2988
3584
  }
2989
3585
  }
2990
3586
  }
2991
3587
  if (showSkills && skills.length === 0) {
2992
- const claudeSkills = join14(targetDir, ".claude", "skills");
2993
- if (existsSync11(claudeSkills)) {
3588
+ const claudeSkills = join17(targetDir, ".claude", "skills");
3589
+ if (existsSync13(claudeSkills)) {
2994
3590
  skills.push(...discoverSkills(claudeSkills));
2995
3591
  }
2996
3592
  }
2997
3593
  if (showAgents && agents.length === 0) {
2998
- const claudeAgents = join14(targetDir, ".claude", "agents");
2999
- if (existsSync11(claudeAgents)) {
3594
+ const claudeAgents = join17(targetDir, ".claude", "agents");
3595
+ if (existsSync13(claudeAgents)) {
3000
3596
  agents.push(...discoverAgents(claudeAgents));
3001
3597
  }
3002
3598
  }
@@ -3020,15 +3616,15 @@ Valid options: skills, agents`);
3020
3616
  }
3021
3617
  function discoverSkills(skillsDir) {
3022
3618
  const items = [];
3023
- const entries = readdirSync3(skillsDir, { withFileTypes: true });
3619
+ const entries = readdirSync4(skillsDir, { withFileTypes: true });
3024
3620
  for (const entry of entries) {
3025
3621
  if (entry.isDirectory()) {
3026
- const skillMd = join14(skillsDir, entry.name, "SKILL.md");
3027
- const description = existsSync11(skillMd) ? extractDescription(skillMd) : "";
3622
+ const skillMd = join17(skillsDir, entry.name, "SKILL.md");
3623
+ const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
3028
3624
  items.push({ name: entry.name, description });
3029
3625
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
3030
3626
  const name = entry.name.replace(/\.md$/, "");
3031
- const description = extractDescription(join14(skillsDir, entry.name));
3627
+ const description = extractDescription(join17(skillsDir, entry.name));
3032
3628
  items.push({ name, description });
3033
3629
  }
3034
3630
  }
@@ -3036,11 +3632,11 @@ function discoverSkills(skillsDir) {
3036
3632
  }
3037
3633
  function discoverAgents(agentsDir) {
3038
3634
  const items = [];
3039
- const entries = readdirSync3(agentsDir, { withFileTypes: true });
3635
+ const entries = readdirSync4(agentsDir, { withFileTypes: true });
3040
3636
  for (const entry of entries) {
3041
3637
  if (entry.isFile() && entry.name.endsWith(".md")) {
3042
3638
  const name = entry.name.replace(/\.md$/, "");
3043
- const description = extractDescription(join14(agentsDir, entry.name));
3639
+ const description = extractDescription(join17(agentsDir, entry.name));
3044
3640
  items.push({ name, description });
3045
3641
  }
3046
3642
  }
@@ -3048,7 +3644,7 @@ function discoverAgents(agentsDir) {
3048
3644
  }
3049
3645
  function extractDescription(filePath) {
3050
3646
  try {
3051
- const content = readFileSync2(filePath, "utf-8");
3647
+ const content = readFileSync5(filePath, "utf-8");
3052
3648
  const lines = content.split("\n");
3053
3649
  let pastHeading = false;
3054
3650
  for (const line of lines) {
@@ -3227,8 +3823,8 @@ async function configResetCommand(options) {
3227
3823
  }
3228
3824
 
3229
3825
  // src/commands/doctor.ts
3230
- import { resolve as resolve11, join as join15 } from "path";
3231
- import { existsSync as existsSync12, readdirSync as readdirSync4, statSync, chmodSync as chmodSync3, mkdirSync } from "fs";
3826
+ import { resolve as resolve11, join as join18 } from "path";
3827
+ import { existsSync as existsSync14, readdirSync as readdirSync5, statSync, chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
3232
3828
  import { execSync } from "child_process";
3233
3829
  import * as p12 from "@clack/prompts";
3234
3830
  import chalk13 from "chalk";
@@ -3329,7 +3925,7 @@ function checkManifestFiles(targetDir, manifest) {
3329
3925
  let existCount = 0;
3330
3926
  const missing = [];
3331
3927
  for (const relativePath of Object.keys(manifest.files)) {
3332
- if (fileExists(join15(targetDir, relativePath))) {
3928
+ if (fileExists(join18(targetDir, relativePath))) {
3333
3929
  existCount++;
3334
3930
  } else {
3335
3931
  missing.push(relativePath);
@@ -3349,15 +3945,15 @@ function checkManifestFiles(targetDir, manifest) {
3349
3945
  };
3350
3946
  }
3351
3947
  function checkScriptPermissions(targetDir, manifest) {
3352
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3948
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
3353
3949
  const shFiles = [];
3354
- if (pluginDir && existsSync12(pluginDir)) {
3355
- const scriptsDir = join15(pluginDir, "scripts");
3356
- if (existsSync12(scriptsDir)) {
3357
- const entries = readdirSync4(scriptsDir);
3950
+ if (pluginDir && existsSync14(pluginDir)) {
3951
+ const scriptsDir = join18(pluginDir, "scripts");
3952
+ if (existsSync14(scriptsDir)) {
3953
+ const entries = readdirSync5(scriptsDir);
3358
3954
  for (const entry of entries) {
3359
3955
  if (entry.endsWith(".sh")) {
3360
- shFiles.push(join15(scriptsDir, entry));
3956
+ shFiles.push(join18(scriptsDir, entry));
3361
3957
  }
3362
3958
  }
3363
3959
  }
@@ -3420,28 +4016,28 @@ function checkPluginRegistered(targetDir) {
3420
4016
  };
3421
4017
  }
3422
4018
  function checkHookScripts(targetDir, manifest) {
3423
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3424
- if (!pluginDir || !existsSync12(pluginDir)) {
4019
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
4020
+ if (!pluginDir || !existsSync14(pluginDir)) {
3425
4021
  return {
3426
4022
  name: "hooks",
3427
4023
  status: "pass",
3428
4024
  message: "No hooks directory to check"
3429
4025
  };
3430
4026
  }
3431
- const hooksDir = join15(pluginDir, "hooks");
3432
- if (!existsSync12(hooksDir)) {
4027
+ const hooksDir = join18(pluginDir, "hooks");
4028
+ if (!existsSync14(hooksDir)) {
3433
4029
  return {
3434
4030
  name: "hooks",
3435
4031
  status: "pass",
3436
4032
  message: "No hooks directory found"
3437
4033
  };
3438
4034
  }
3439
- const hookFiles = readdirSync4(hooksDir).filter((f) => f.endsWith(".json"));
4035
+ const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
3440
4036
  let total = 0;
3441
4037
  let valid = 0;
3442
4038
  for (const hookFile of hookFiles) {
3443
4039
  try {
3444
- const hookContent = JSON.parse(readFile(join15(hooksDir, hookFile)));
4040
+ const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
3445
4041
  const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
3446
4042
  for (const hook of hooks) {
3447
4043
  if (hook.command) {
@@ -3455,8 +4051,8 @@ function checkHookScripts(targetDir, manifest) {
3455
4051
  /\$\{[A-Z_]+\}\//g,
3456
4052
  ""
3457
4053
  );
3458
- const scriptPath = join15(pluginDir, resolved);
3459
- if (existsSync12(scriptPath)) {
4054
+ const scriptPath = join18(pluginDir, resolved);
4055
+ if (existsSync14(scriptPath)) {
3460
4056
  valid++;
3461
4057
  }
3462
4058
  } else {
@@ -3480,7 +4076,7 @@ function checkHookScripts(targetDir, manifest) {
3480
4076
  };
3481
4077
  }
3482
4078
  function checkQualityScripts(targetDir) {
3483
- const pkgPath = join15(targetDir, "package.json");
4079
+ const pkgPath = join18(targetDir, "package.json");
3484
4080
  if (!fileExists(pkgPath)) {
3485
4081
  return { name: "quality", status: "warn", message: "No package.json found" };
3486
4082
  }
@@ -3506,8 +4102,8 @@ function checkQualityScripts(targetDir) {
3506
4102
  }
3507
4103
  }
3508
4104
  function checkThoughtsDir(targetDir) {
3509
- const thoughtsDir = join15(targetDir, "thoughts");
3510
- if (existsSync12(thoughtsDir)) {
4105
+ const thoughtsDir = join18(targetDir, "thoughts");
4106
+ if (existsSync14(thoughtsDir)) {
3511
4107
  return {
3512
4108
  name: "thoughts",
3513
4109
  status: "pass",
@@ -3531,15 +4127,15 @@ function checkThoughtsDir(targetDir) {
3531
4127
  "thoughts/archive/backlog"
3532
4128
  ];
3533
4129
  for (const dir of dirs) {
3534
- mkdirSync(join15(targetDir, dir), { recursive: true });
4130
+ mkdirSync3(join18(targetDir, dir), { recursive: true });
3535
4131
  }
3536
4132
  }
3537
4133
  };
3538
4134
  }
3539
4135
  function checkEslint(targetDir) {
3540
4136
  try {
3541
- const eslintLocal = join15(targetDir, "node_modules", ".bin", "eslint");
3542
- if (existsSync12(eslintLocal)) {
4137
+ const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
4138
+ if (existsSync14(eslintLocal)) {
3543
4139
  return { name: "eslint", status: "pass", message: "eslint is available" };
3544
4140
  }
3545
4141
  execSync("which eslint", { stdio: "pipe" });
@@ -3554,8 +4150,8 @@ function checkEslint(targetDir) {
3554
4150
  }
3555
4151
 
3556
4152
  // src/commands/uninstall.ts
3557
- import { existsSync as existsSync13, rmSync, readdirSync as readdirSync5 } from "fs";
3558
- import { resolve as resolve12, join as join16, dirname as dirname6 } from "path";
4153
+ import { existsSync as existsSync15, rmSync as rmSync2, readdirSync as readdirSync6 } from "fs";
4154
+ import { resolve as resolve12, join as join19, dirname as dirname8 } from "path";
3559
4155
  import * as p13 from "@clack/prompts";
3560
4156
  import chalk14 from "chalk";
3561
4157
  var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
@@ -3573,12 +4169,12 @@ async function uninstallCommand(options) {
3573
4169
  return;
3574
4170
  }
3575
4171
  const managedFiles = Object.keys(manifest.files);
3576
- const existingFiles = managedFiles.filter((f) => fileExists(join16(targetDir, f)));
3577
- const missingFiles = managedFiles.filter((f) => !fileExists(join16(targetDir, f)));
3578
- const hasPlugin = manifest.installMode === "plugin" && existsSync13(join16(targetDir, PLUGIN_DIR, PLUGIN_NAME));
3579
- const hasThoughts = existsSync13(join16(targetDir, "thoughts"));
3580
- const hasClaudeMd = fileExists(join16(targetDir, "CLAUDE.md"));
3581
- const hasAgentsMd = fileExists(join16(targetDir, "AGENTS.md"));
4172
+ const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
4173
+ const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
4174
+ const hasPlugin = manifest.installMode === "plugin" && existsSync15(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
4175
+ const hasThoughts = existsSync15(join19(targetDir, "thoughts"));
4176
+ const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
4177
+ const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
3582
4178
  p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
3583
4179
  p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
3584
4180
  p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
@@ -3664,18 +4260,18 @@ async function uninstallCommand(options) {
3664
4260
  }
3665
4261
  if (hasPlugin) {
3666
4262
  try {
3667
- rmSync(join16(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
4263
+ rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
3668
4264
  removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
3669
- const marketplaceDescDir = join16(targetDir, PLUGIN_DIR, ".claude-plugin");
3670
- if (existsSync13(marketplaceDescDir)) {
3671
- rmSync(marketplaceDescDir, { recursive: true, force: true });
4265
+ const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
4266
+ if (existsSync15(marketplaceDescDir)) {
4267
+ rmSync2(marketplaceDescDir, { recursive: true, force: true });
3672
4268
  removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
3673
4269
  }
3674
- const pluginsDir = join16(targetDir, PLUGIN_DIR);
3675
- if (existsSync13(pluginsDir)) {
4270
+ const pluginsDir = join19(targetDir, PLUGIN_DIR);
4271
+ if (existsSync15(pluginsDir)) {
3676
4272
  const remaining = readdirSafe(pluginsDir);
3677
4273
  if (remaining.length === 0) {
3678
- rmSync(pluginsDir, { recursive: true, force: true });
4274
+ rmSync2(pluginsDir, { recursive: true, force: true });
3679
4275
  removed.push(`${PLUGIN_DIR}/ (empty)`);
3680
4276
  }
3681
4277
  }
@@ -3688,11 +4284,11 @@ async function uninstallCommand(options) {
3688
4284
  if (file.startsWith("thoughts/")) continue;
3689
4285
  if (file.startsWith(PLUGIN_DIR + "/")) continue;
3690
4286
  try {
3691
- const filePath = join16(targetDir, file);
3692
- if (existsSync13(filePath)) {
3693
- rmSync(filePath, { force: true });
4287
+ const filePath = join19(targetDir, file);
4288
+ if (existsSync15(filePath)) {
4289
+ rmSync2(filePath, { force: true });
3694
4290
  removed.push(file);
3695
- cleanEmptyParents(targetDir, dirname6(file));
4291
+ cleanEmptyParents(targetDir, dirname8(file));
3696
4292
  }
3697
4293
  } catch (err) {
3698
4294
  errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3701,7 +4297,7 @@ async function uninstallCommand(options) {
3701
4297
  if (hasClaudeMd) {
3702
4298
  if (removeClaudeMd) {
3703
4299
  try {
3704
- rmSync(join16(targetDir, "CLAUDE.md"), { force: true });
4300
+ rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
3705
4301
  removed.push("CLAUDE.md");
3706
4302
  } catch (err) {
3707
4303
  errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3713,7 +4309,7 @@ async function uninstallCommand(options) {
3713
4309
  if (hasAgentsMd) {
3714
4310
  if (removeAgentsMd) {
3715
4311
  try {
3716
- rmSync(join16(targetDir, "AGENTS.md"), { force: true });
4312
+ rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
3717
4313
  removed.push("AGENTS.md");
3718
4314
  } catch (err) {
3719
4315
  errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3725,7 +4321,7 @@ async function uninstallCommand(options) {
3725
4321
  if (hasThoughts) {
3726
4322
  if (removeThoughts) {
3727
4323
  try {
3728
- rmSync(join16(targetDir, "thoughts"), { recursive: true, force: true });
4324
+ rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
3729
4325
  removed.push("thoughts/");
3730
4326
  } catch (err) {
3731
4327
  errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
@@ -3735,9 +4331,9 @@ async function uninstallCommand(options) {
3735
4331
  }
3736
4332
  }
3737
4333
  try {
3738
- const manifestDir = join16(targetDir, MANIFEST_DIR);
3739
- if (existsSync13(manifestDir)) {
3740
- rmSync(manifestDir, { recursive: true, force: true });
4334
+ const manifestDir = join19(targetDir, MANIFEST_DIR);
4335
+ if (existsSync15(manifestDir)) {
4336
+ rmSync2(manifestDir, { recursive: true, force: true });
3741
4337
  removed.push(`${MANIFEST_DIR}/`);
3742
4338
  }
3743
4339
  } catch (err) {
@@ -3782,33 +4378,59 @@ async function uninstallCommand(options) {
3782
4378
  }
3783
4379
  function cleanEmptyParents(targetDir, relDir) {
3784
4380
  if (!relDir || relDir === ".") return;
3785
- const absDir = join16(targetDir, relDir);
3786
- if (!existsSync13(absDir)) return;
4381
+ const absDir = join19(targetDir, relDir);
4382
+ if (!existsSync15(absDir)) return;
3787
4383
  const entries = readdirSafe(absDir);
3788
4384
  if (entries.length === 0) {
3789
4385
  try {
3790
- rmSync(absDir, { recursive: true, force: true });
3791
- cleanEmptyParents(targetDir, dirname6(relDir));
4386
+ rmSync2(absDir, { recursive: true, force: true });
4387
+ cleanEmptyParents(targetDir, dirname8(relDir));
3792
4388
  } catch {
3793
4389
  }
3794
4390
  }
3795
4391
  }
3796
4392
  function readdirSafe(dir) {
3797
4393
  try {
3798
- return readdirSync5(dir);
4394
+ return readdirSync6(dir);
3799
4395
  } catch {
3800
4396
  return [];
3801
4397
  }
3802
4398
  }
3803
4399
 
3804
4400
  // src/commands/addon.ts
3805
- import { resolve as resolve13, join as join17, dirname as dirname7 } from "path";
3806
- import { existsSync as existsSync14, unlinkSync as unlinkSync2, rmSync as rmSync2 } from "fs";
4401
+ import { resolve as resolve13, join as join20, dirname as dirname9 } from "path";
4402
+ import { existsSync as existsSync16, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
3807
4403
  import * as p14 from "@clack/prompts";
3808
4404
  import chalk15 from "chalk";
4405
+ function isFileBasedAddon(addonName) {
4406
+ return addonName !== "orchestration";
4407
+ }
3809
4408
  async function addonCommand(action, addonName, options) {
3810
4409
  const targetDir = resolve13(options.path || ".");
3811
4410
  p14.intro(introTitle(`Addon ${action}`));
4411
+ const validAddons = Object.keys(ADDONS);
4412
+ if (!validAddons.includes(addonName)) {
4413
+ p14.cancel(`Unknown addon: ${addonName}
4414
+
4415
+ Valid addons: ${validAddons.join(", ")}`);
4416
+ process.exit(1);
4417
+ }
4418
+ const typedName = addonName;
4419
+ const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
4420
+ if (action === "add" || action === "remove") {
4421
+ const canonical = action === "add" ? "enable" : "disable";
4422
+ p14.log.warn(
4423
+ `"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
4424
+ );
4425
+ }
4426
+ if (isFileBasedAddon(typedName)) {
4427
+ if (canonicalAction === "add") {
4428
+ await addFileBasedAddon(targetDir, typedName, options);
4429
+ } else {
4430
+ await removeFileBasedAddon(targetDir, typedName, options);
4431
+ }
4432
+ return;
4433
+ }
3812
4434
  const manifest = readManifest(targetDir);
3813
4435
  if (!manifest) {
3814
4436
  p14.log.warn("No devtronic installation found.");
@@ -3822,16 +4444,9 @@ async function addonCommand(action, addonName, options) {
3822
4444
  p14.outro("");
3823
4445
  return;
3824
4446
  }
3825
- const validAddons = Object.keys(ADDONS);
3826
- if (!validAddons.includes(addonName)) {
3827
- p14.cancel(`Unknown addon: ${addonName}
3828
-
3829
- Valid addons: ${validAddons.join(", ")}`);
3830
- process.exit(1);
3831
- }
3832
- const addon = ADDONS[addonName];
4447
+ const addon = ADDONS[typedName];
3833
4448
  const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
3834
- if (action === "add") {
4449
+ if (canonicalAction === "add") {
3835
4450
  await addAddon(targetDir, manifest, addon.name, currentAddons);
3836
4451
  } else {
3837
4452
  await removeAddon(targetDir, manifest, addon.name, currentAddons);
@@ -3845,7 +4460,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3845
4460
  }
3846
4461
  const addon = ADDONS[addonName];
3847
4462
  const pluginRoot = manifest.pluginPath;
3848
- const skillsSourceDir = join17(TEMPLATES_DIR, "claude-code", ".claude", "skills");
4463
+ const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
3849
4464
  p14.note(
3850
4465
  [
3851
4466
  ` ${chalk15.dim("Name:")} ${addon.label}`,
@@ -3864,18 +4479,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3864
4479
  spinner8.start(`Adding ${addon.label}...`);
3865
4480
  const addedFiles = [];
3866
4481
  for (const skillDir of addon.skills) {
3867
- const sourceDir = join17(skillsSourceDir, skillDir);
3868
- if (!existsSync14(sourceDir)) {
4482
+ const sourceDir = join20(skillsSourceDir, skillDir);
4483
+ if (!existsSync16(sourceDir)) {
3869
4484
  spinner8.stop(`Template not found for skill: ${skillDir}`);
3870
4485
  p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
3871
4486
  continue;
3872
4487
  }
3873
4488
  const templateFiles = getAllFilesRecursive(sourceDir);
3874
4489
  for (const file of templateFiles) {
3875
- const content = readFile(join17(sourceDir, file));
3876
- const destRelPath = join17(pluginRoot, "skills", skillDir, file);
3877
- const destAbsPath = join17(targetDir, destRelPath);
3878
- ensureDir(dirname7(destAbsPath));
4490
+ const content = readFile(join20(sourceDir, file));
4491
+ const destRelPath = join20(pluginRoot, "skills", skillDir, file);
4492
+ const destAbsPath = join20(targetDir, destRelPath);
4493
+ ensureDir(dirname9(destAbsPath));
3879
4494
  writeFile(destAbsPath, content);
3880
4495
  manifest.files[destRelPath] = createManifestEntry(content);
3881
4496
  addedFiles.push(destRelPath);
@@ -3920,11 +4535,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3920
4535
  }
3921
4536
  const modifiedFiles = [];
3922
4537
  for (const skillDir of addon.skills) {
3923
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
4538
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
3924
4539
  for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
3925
4540
  if (!filePath.startsWith(skillRelBase)) continue;
3926
- const absPath = join17(targetDir, filePath);
3927
- if (!existsSync14(absPath)) continue;
4541
+ const absPath = join20(targetDir, filePath);
4542
+ if (!existsSync16(absPath)) continue;
3928
4543
  const current = calculateChecksum(readFile(absPath));
3929
4544
  if (current !== fileInfo.originalChecksum) {
3930
4545
  modifiedFiles.push(filePath);
@@ -3947,18 +4562,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3947
4562
  const spinner8 = p14.spinner();
3948
4563
  spinner8.start(`Removing ${addon.label}...`);
3949
4564
  for (const skillDir of addon.skills) {
3950
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
3951
- const skillAbsDir = join17(targetDir, skillRelBase);
4565
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
4566
+ const skillAbsDir = join20(targetDir, skillRelBase);
3952
4567
  for (const filePath of Object.keys(manifest.files)) {
3953
4568
  if (filePath.startsWith(skillRelBase)) {
3954
- const absPath = join17(targetDir, filePath);
3955
- if (existsSync14(absPath)) unlinkSync2(absPath);
4569
+ const absPath = join20(targetDir, filePath);
4570
+ if (existsSync16(absPath)) unlinkSync3(absPath);
3956
4571
  delete manifest.files[filePath];
3957
4572
  }
3958
4573
  }
3959
- if (existsSync14(skillAbsDir)) {
4574
+ if (existsSync16(skillAbsDir)) {
3960
4575
  try {
3961
- rmSync2(skillAbsDir, { recursive: true });
4576
+ rmSync3(skillAbsDir, { recursive: true });
3962
4577
  } catch {
3963
4578
  }
3964
4579
  }
@@ -3975,32 +4590,198 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3975
4590
  );
3976
4591
  p14.outro("Done. Restart Claude Code to apply the changes.");
3977
4592
  }
4593
+ async function addFileBasedAddon(targetDir, addonName, _options) {
4594
+ const addon = ADDONS[addonName];
4595
+ const config = readAddonConfig(targetDir);
4596
+ if (config.installed[addonName]) {
4597
+ p14.log.warn(`Addon "${addonName}" is already installed.`);
4598
+ p14.outro("");
4599
+ return;
4600
+ }
4601
+ p14.note(
4602
+ [
4603
+ ` ${chalk15.dim("Name:")} ${addon.label}`,
4604
+ ` ${chalk15.dim("Description:")} ${addon.description}`,
4605
+ ` ${chalk15.dim("Skills:")} ${addon.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`,
4606
+ addon.agents.length ? ` ${chalk15.dim("Agents:")} ${addon.agents.map((a) => chalk15.cyan(a)).join(", ")}` : ` ${chalk15.dim("Agents:")} ${chalk15.dim("\u2014")}`
4607
+ ].join("\n"),
4608
+ "Adding addon"
4609
+ );
4610
+ const confirmed = await p14.confirm({ message: "Add this addon?" });
4611
+ if (p14.isCancel(confirmed) || !confirmed) {
4612
+ p14.cancel("Addon installation cancelled.");
4613
+ process.exit(0);
4614
+ }
4615
+ const spinner8 = p14.spinner();
4616
+ spinner8.start(`Adding ${addon.label}...`);
4617
+ const addonSourceDir = getAddonSourceDir(addonName);
4618
+ const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
4619
+ const addonManifest = JSON.parse(
4620
+ readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
4621
+ );
4622
+ const fileList = [
4623
+ ...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
4624
+ ...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
4625
+ ...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
4626
+ ];
4627
+ writeAddonToConfig(targetDir, addonName, {
4628
+ version: addonManifest.version,
4629
+ files: fileList,
4630
+ checksums: result.checksums ?? {}
4631
+ });
4632
+ spinner8.stop(`${symbols.pass} ${addon.label} added (${result.written} files written)`);
4633
+ p14.note(
4634
+ addon.skills.map((s) => ` ${chalk15.cyan(`/${s}`)}`).join("\n"),
4635
+ "New skills available"
4636
+ );
4637
+ p14.outro("Done. Skills are now available in your agent directories.");
4638
+ }
4639
+ async function removeFileBasedAddon(targetDir, addonName, _options) {
4640
+ const addon = ADDONS[addonName];
4641
+ const config = readAddonConfig(targetDir);
4642
+ if (!config.installed[addonName]) {
4643
+ p14.log.warn(`Addon "${addonName}" is not currently installed.`);
4644
+ p14.outro("");
4645
+ return;
4646
+ }
4647
+ const modified = detectModifiedAddonFiles(targetDir, addonName);
4648
+ if (modified.length > 0) {
4649
+ p14.log.warn("The following files have been customized:");
4650
+ for (const f of modified) {
4651
+ p14.log.message(` ${chalk15.yellow(f)}`);
4652
+ }
4653
+ const confirm9 = await p14.confirm({
4654
+ message: "Remove them anyway? (customizations will be lost)"
4655
+ });
4656
+ if (p14.isCancel(confirm9) || !confirm9) {
4657
+ p14.cancel("Addon removal cancelled.");
4658
+ process.exit(0);
4659
+ }
4660
+ }
4661
+ const spinner8 = p14.spinner();
4662
+ spinner8.start(`Removing ${addon.label}...`);
4663
+ removeAddonFiles(targetDir, addonName, config.agents);
4664
+ removeAddonFromConfig(targetDir, addonName);
4665
+ spinner8.stop(`${symbols.pass} ${addon.label} removed`);
4666
+ p14.note(
4667
+ addon.skills.map((s) => ` ${chalk15.dim(`/${s}`)}`).join("\n"),
4668
+ "Skills removed"
4669
+ );
4670
+ p14.outro("Done.");
4671
+ }
4672
+ function getAddonListInfo(targetDir) {
4673
+ const config = readAddonConfig(targetDir);
4674
+ const manifest = readManifest(targetDir);
4675
+ const manifestAddons = manifest?.projectConfig?.enabledAddons ?? [];
4676
+ return getAvailableAddons().map((addon) => ({
4677
+ name: addon.name,
4678
+ label: addon.label,
4679
+ description: addon.description,
4680
+ installed: !!config.installed[addon.name] || manifestAddons.includes(addon.name),
4681
+ agents: config.installed[addon.name] ? config.agents : void 0
4682
+ }));
4683
+ }
4684
+ async function addonListCommand(options) {
4685
+ const targetDir = resolve13(options.path || ".");
4686
+ p14.intro(introTitle("Addon List"));
4687
+ const items = getAddonListInfo(targetDir);
4688
+ const lines = items.map((item) => {
4689
+ const status = item.installed ? chalk15.green("\u2713 installed") : chalk15.dim("available");
4690
+ const agents = item.agents ? chalk15.dim(` \u2192 ${item.agents.join(", ")}`) : "";
4691
+ return ` ${chalk15.bold(item.name)} ${status}${agents}
4692
+ ${chalk15.dim(item.description)}`;
4693
+ });
4694
+ p14.note(lines.join("\n\n"), "Addons");
4695
+ p14.outro(`Use ${chalk15.cyan("devtronic addon add <name>")} to install.`);
4696
+ }
4697
+ async function addonSyncCommand(options) {
4698
+ const targetDir = resolve13(options.path || ".");
4699
+ p14.intro(introTitle("Addon Sync"));
4700
+ const config = readAddonConfig(targetDir);
4701
+ const installedNames = Object.keys(config.installed);
4702
+ if (installedNames.length === 0) {
4703
+ p14.log.info("No addons installed. Nothing to sync.");
4704
+ p14.outro("");
4705
+ return;
4706
+ }
4707
+ const spinner8 = p14.spinner();
4708
+ spinner8.start("Syncing addon files...");
4709
+ let totalWritten = 0;
4710
+ let totalConflicts = [];
4711
+ for (const name of installedNames) {
4712
+ const addonSourceDir = getAddonSourceDir(name);
4713
+ const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
4714
+ totalWritten += result.written + (result.updated ?? 0);
4715
+ totalConflicts = totalConflicts.concat(result.conflicts);
4716
+ }
4717
+ spinner8.stop(`${symbols.pass} Sync complete (${totalWritten} files updated)`);
4718
+ if (totalConflicts.length > 0) {
4719
+ p14.log.warn("Customized files were preserved:");
4720
+ for (const f of totalConflicts) {
4721
+ p14.log.message(` ${chalk15.yellow(f)}`);
4722
+ }
4723
+ }
4724
+ p14.outro("Done.");
4725
+ }
3978
4726
  function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
3979
4727
  const cliVersion2 = getCliVersion();
3980
4728
  const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
3981
- const pluginJsonRelPath = join17(pluginRoot, ".claude-plugin", "plugin.json");
3982
- writeFile(join17(targetDir, pluginJsonRelPath), pluginJsonContent);
4729
+ const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
4730
+ writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
3983
4731
  manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
3984
4732
  const marketplaceContent = generateMarketplaceJson(addonSkillCount);
3985
- const marketplaceRelPath = join17(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
3986
- writeFile(join17(targetDir, marketplaceRelPath), marketplaceContent);
4733
+ const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
4734
+ writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
3987
4735
  manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
3988
4736
  }
3989
4737
 
4738
+ // src/commands/mode.ts
4739
+ import { resolve as resolve14, join as join21 } from "path";
4740
+ import { existsSync as existsSync17 } from "fs";
4741
+ import * as p15 from "@clack/prompts";
4742
+ import chalk16 from "chalk";
4743
+ async function modeCommand(action, options) {
4744
+ const targetDir = resolve14(options.path || ".");
4745
+ p15.intro(introTitle("Mode"));
4746
+ if (action === "show") {
4747
+ const configPath = join21(targetDir, ".claude", "devtronic.json");
4748
+ const hasConfig = existsSync17(configPath);
4749
+ const config = readAddonConfig(targetDir);
4750
+ const currentMode = config.mode;
4751
+ const isDefault = !hasConfig || currentMode === void 0;
4752
+ const displayMode = currentMode ?? "hitl";
4753
+ p15.log.info(
4754
+ `Mode: ${chalk16.cyan(displayMode)}${isDefault ? chalk16.dim(" (default)") : ""}`
4755
+ );
4756
+ p15.log.info(`Config: ${chalk16.dim(".claude/devtronic.json")}`);
4757
+ p15.outro("");
4758
+ return;
4759
+ }
4760
+ const newMode = action;
4761
+ writeMode(targetDir, newMode);
4762
+ const description = newMode === "afk" ? "Fully autonomous \u2014 no human gates" : "Human-in-the-loop \u2014 pauses for approval at key stages";
4763
+ p15.log.success(`Mode set to ${chalk16.cyan(newMode)}`);
4764
+ p15.log.info(chalk16.dim(description));
4765
+ p15.outro(
4766
+ `Change back anytime with ${chalk16.cyan(`npx devtronic mode ${newMode === "afk" ? "hitl" : "afk"}`)}`
4767
+ );
4768
+ }
4769
+
3990
4770
  // src/index.ts
3991
4771
  var cliVersion = getCliVersion();
3992
4772
  var program = new Command();
3993
4773
  program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
3994
4774
  showLogo();
3995
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}`));
4775
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
3996
4776
  console.log();
3997
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic init")} ${chalk16.dim("[path]")} ${chalk16.dim("Initialize in a project")}`);
3998
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic info")} ${chalk16.dim("Version & config summary")}`);
3999
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic doctor")} ${chalk16.dim("Health diagnostics")}`);
4000
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic status")} ${chalk16.dim("File status overview")}`);
4777
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic init")} ${chalk17.dim("[path]")} ${chalk17.dim("Initialize in a project")}`);
4778
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic info")} ${chalk17.dim("Version & config summary")}`);
4779
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic doctor")} ${chalk17.dim("Health diagnostics")}`);
4780
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic status")} ${chalk17.dim("File status overview")}`);
4781
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic mode")} ${chalk17.dim("<afk|hitl|show>")} ${chalk17.dim("Get or set execution mode")}`);
4001
4782
  console.log();
4002
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic help")} ${chalk16.dim("Show all commands")}`);
4003
- console.log(` ${chalk16.dim("$")} ${chalk16.white("devtronic help --all")} ${chalk16.dim("Full reference with all options")}`);
4783
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help")} ${chalk17.dim("Show all commands")}`);
4784
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help --all")} ${chalk17.dim("Full reference with all options")}`);
4004
4785
  console.log();
4005
4786
  });
4006
4787
  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(
@@ -4059,13 +4840,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
4059
4840
  program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
4060
4841
  await uninstallCommand({ path: options.path });
4061
4842
  });
4843
+ 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) => {
4844
+ if (!["afk", "hitl", "show"].includes(mode)) {
4845
+ console.error(`Invalid mode: "${mode}". Valid values: afk, hitl, show`);
4846
+ process.exit(1);
4847
+ }
4848
+ await modeCommand(mode, { path: options.path });
4849
+ });
4062
4850
  program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
4063
4851
  if (!options.all) {
4064
4852
  program.outputHelp();
4065
4853
  return;
4066
4854
  }
4067
4855
  showLogo();
4068
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}
4856
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
4069
4857
  `));
4070
4858
  const sections = [
4071
4859
  {
@@ -4089,7 +4877,7 @@ program.command("help").description("Show help (use --all for full reference)").
4089
4877
  {
4090
4878
  title: "Addons",
4091
4879
  usage: "addon add <name>",
4092
- desc: "Add an optional skill pack (e.g., orchestration)",
4880
+ desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
4093
4881
  opts: ["--path <path> Target directory"]
4094
4882
  },
4095
4883
  {
@@ -4098,6 +4886,30 @@ program.command("help").description("Show help (use --all for full reference)").
4098
4886
  desc: "Remove an addon skill pack",
4099
4887
  opts: ["--path <path> Target directory"]
4100
4888
  },
4889
+ {
4890
+ title: "",
4891
+ usage: "addon list",
4892
+ desc: "List available and installed addons",
4893
+ opts: ["--path <path> Target directory"]
4894
+ },
4895
+ {
4896
+ title: "",
4897
+ usage: "addon sync",
4898
+ desc: "Regenerate addon files for current agent configuration",
4899
+ opts: ["--path <path> Target directory"]
4900
+ },
4901
+ {
4902
+ title: "",
4903
+ usage: "addon enable <name>",
4904
+ desc: "Enable an addon (copies files to .claude/)",
4905
+ opts: ["--path <path> Target directory"]
4906
+ },
4907
+ {
4908
+ title: "",
4909
+ usage: "addon disable <name>",
4910
+ desc: "Disable an addon (removes files from .claude/)",
4911
+ opts: ["--path <path> Target directory"]
4912
+ },
4101
4913
  {
4102
4914
  title: "Maintenance",
4103
4915
  usage: "update",
@@ -4151,6 +4963,12 @@ program.command("help").description("Show help (use --all for full reference)").
4151
4963
  usage: "presets",
4152
4964
  desc: "List available configuration presets"
4153
4965
  },
4966
+ {
4967
+ title: "Mode",
4968
+ usage: "mode <afk|hitl|show>",
4969
+ desc: "Set or show the persistent execution mode for /auto-devtronic",
4970
+ opts: ["--path <path> Target directory"]
4971
+ },
4154
4972
  {
4155
4973
  title: "Diagnostics",
4156
4974
  usage: "status",
@@ -4186,14 +5004,14 @@ program.command("help").description("Show help (use --all for full reference)").
4186
5004
  ];
4187
5005
  for (const section of sections) {
4188
5006
  if (section.title) {
4189
- console.log(` ${chalk16.bold.underline(section.title)}
5007
+ console.log(` ${chalk17.bold.underline(section.title)}
4190
5008
  `);
4191
5009
  }
4192
- console.log(` ${chalk16.white("devtronic " + section.usage)}`);
4193
- console.log(` ${chalk16.dim(section.desc)}`);
5010
+ console.log(` ${chalk17.white("devtronic " + section.usage)}`);
5011
+ console.log(` ${chalk17.dim(section.desc)}`);
4194
5012
  if (section.opts) {
4195
5013
  for (const opt of section.opts) {
4196
- console.log(` ${chalk16.yellow(opt)}`);
5014
+ console.log(` ${chalk17.yellow(opt)}`);
4197
5015
  }
4198
5016
  }
4199
5017
  console.log();
@@ -4206,21 +5024,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
4206
5024
  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) => {
4207
5025
  await addonCommand("remove", name, { path: options.path });
4208
5026
  });
5027
+ addonCmd.command("list").description("List available and installed addons").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5028
+ await addonListCommand({ path: options.path });
5029
+ });
5030
+ 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) => {
5031
+ await addonCommand("enable", name, { path: options.path });
5032
+ });
5033
+ 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) => {
5034
+ await addonCommand("disable", name, { path: options.path });
5035
+ });
5036
+ addonCmd.command("sync").description("Regenerate addon files for current agent configuration").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5037
+ await addonSyncCommand({ path: options.path });
5038
+ });
4209
5039
  program.command("presets").description("List available configuration presets").action(() => {
4210
- p15.intro(introTitle("Presets"));
5040
+ p16.intro(introTitle("Presets"));
4211
5041
  const lines = Object.entries(PRESETS).map(([name, preset]) => {
4212
- const parts = [` ${chalk16.bold(name)}`];
5042
+ const parts = [` ${chalk17.bold(name)}`];
4213
5043
  parts.push(` ${preset.description}`);
4214
5044
  if (preset.config.architecture) {
4215
- parts.push(` Architecture: ${chalk16.cyan(preset.config.architecture)}`);
5045
+ parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
4216
5046
  }
4217
5047
  if (preset.config.layers) {
4218
- parts.push(` Layers: ${chalk16.cyan(preset.config.layers.join(", "))}`);
5048
+ parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
4219
5049
  }
4220
5050
  return parts.join("\n");
4221
5051
  });
4222
- p15.note(lines.join("\n\n"), "Available Presets");
4223
- p15.outro(`Usage: ${chalk16.cyan("npx devtronic init --preset <name>")}`);
5052
+ p16.note(lines.join("\n\n"), "Available Presets");
5053
+ p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
4224
5054
  });
4225
5055
  program.parseAsync().catch((err) => {
4226
5056
  console.error(`