devtronic 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +5 -3
  2. package/dist/{chunk-5V4PXTP6.js → chunk-V4QEAL7Y.js} +43 -19
  3. package/dist/index.js +1049 -214
  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";
@@ -1207,9 +1207,9 @@ function getCliVersion() {
1207
1207
  resolve(__dirname, "../package.json"),
1208
1208
  resolve(__dirname, "../../package.json")
1209
1209
  ];
1210
- for (const p16 of paths) {
1210
+ for (const p17 of paths) {
1211
1211
  try {
1212
- const pkg = JSON.parse(readFileSync(p16, "utf-8"));
1212
+ const pkg = JSON.parse(readFileSync(p17, "utf-8"));
1213
1213
  if (pkg.name === "devtronic" && pkg.version) {
1214
1214
  return pkg.version;
1215
1215
  }
@@ -1374,7 +1374,18 @@ Valid addons: ${validAddons.join(", ")}`);
1374
1374
  process.exit(0);
1375
1375
  }
1376
1376
  if (wantOrchestration) {
1377
- enabledAddons = ["orchestration"];
1377
+ enabledAddons.push("orchestration");
1378
+ }
1379
+ const wantDesign = await p3.confirm({
1380
+ message: "Enable design best practices? (design-init \u2192 design-review \u2192 design-refine \u2192 design-harden)",
1381
+ initialValue: false
1382
+ });
1383
+ if (p3.isCancel(wantDesign)) {
1384
+ p3.cancel("Operation cancelled");
1385
+ process.exit(0);
1386
+ }
1387
+ if (wantDesign) {
1388
+ enabledAddons.push("design-best-practices");
1378
1389
  }
1379
1390
  }
1380
1391
  if (enabledAddons.length > 0) {
@@ -1604,6 +1615,13 @@ Valid addons: ${validAddons.join(", ")}`);
1604
1615
  ].join("\n"),
1605
1616
  "Next Steps"
1606
1617
  );
1618
+ p3.note(
1619
+ [
1620
+ `${chalk4.dim("Execution mode:")} HITL ${chalk4.dim("(default \u2014 safe for unfamiliar codebases)")}`,
1621
+ `${chalk4.dim("Change anytime:")} ${chalk4.cyan("npx devtronic mode afk")}`
1622
+ ].join("\n"),
1623
+ "Autonomous Mode"
1624
+ );
1607
1625
  p3.outro(chalk4.green("Setup complete!"));
1608
1626
  } catch (err) {
1609
1627
  spinner8.stop("Configuration failed");
@@ -1706,8 +1724,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
1706
1724
  }
1707
1725
 
1708
1726
  // 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";
1727
+ import { resolve as resolve3, join as join11, dirname as dirname5 } from "path";
1728
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, lstatSync, readdirSync as readdirSync2, rmdirSync as rmdirSync2, chmodSync as chmodSync2 } from "fs";
1711
1729
  import * as p4 from "@clack/prompts";
1712
1730
  import chalk5 from "chalk";
1713
1731
 
@@ -1735,6 +1753,553 @@ var REMOVED_FILES = {
1735
1753
  }
1736
1754
  };
1737
1755
 
1756
+ // src/utils/addonConfig.ts
1757
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync, mkdirSync, renameSync } from "fs";
1758
+ import { join as join9, dirname as dirname3 } from "path";
1759
+
1760
+ // src/addons/registry.ts
1761
+ import { readFileSync as readFileSync2 } from "fs";
1762
+ import { join as join8 } from "path";
1763
+ function getAddonSourceDir(name) {
1764
+ const addonsDir = new URL(".", import.meta.url).pathname;
1765
+ return join8(addonsDir, name);
1766
+ }
1767
+ function getAddonManifest(name) {
1768
+ const sourceDir = getAddonSourceDir(name);
1769
+ const manifestPath = join8(sourceDir, "manifest.json");
1770
+ const raw = readFileSync2(manifestPath, "utf-8");
1771
+ const manifest = JSON.parse(raw);
1772
+ if (!manifest.name) throw new Error(`Addon manifest missing "name": ${manifestPath}`);
1773
+ if (!manifest.version) throw new Error(`Addon manifest missing "version": ${manifestPath}`);
1774
+ if (!manifest.files?.skills?.length) throw new Error(`Addon manifest has empty skills: ${manifestPath}`);
1775
+ return manifest;
1776
+ }
1777
+ function getAvailableAddons() {
1778
+ return Object.values(ADDONS);
1779
+ }
1780
+
1781
+ // src/utils/addonConfig.ts
1782
+ var CONFIG_DIR = ".claude";
1783
+ var CONFIG_FILE = "devtronic.json";
1784
+ function getConfigPath(targetDir) {
1785
+ return join9(targetDir, CONFIG_DIR, CONFIG_FILE);
1786
+ }
1787
+ function getLegacyConfigPath(targetDir) {
1788
+ return join9(targetDir, CONFIG_FILE);
1789
+ }
1790
+ function readAddonConfig(targetDir) {
1791
+ const configPath = getConfigPath(targetDir);
1792
+ const legacyPath = getLegacyConfigPath(targetDir);
1793
+ if (!existsSync6(configPath) && existsSync6(legacyPath)) {
1794
+ mkdirSync(dirname3(configPath), { recursive: true });
1795
+ renameSync(legacyPath, configPath);
1796
+ }
1797
+ if (!existsSync6(configPath)) {
1798
+ return { agents: ["claude"], installed: {} };
1799
+ }
1800
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1801
+ const data = raw.addons ?? raw;
1802
+ return {
1803
+ version: 1,
1804
+ mode: data.mode,
1805
+ agents: data.agents ?? ["claude"],
1806
+ installed: data.installed ?? {}
1807
+ };
1808
+ }
1809
+ function writeAddonConfig(targetDir, config) {
1810
+ const configPath = getConfigPath(targetDir);
1811
+ mkdirSync(dirname3(configPath), { recursive: true });
1812
+ const payload = { version: 1, ...config };
1813
+ writeFileSync(configPath, JSON.stringify(payload, null, 2) + "\n");
1814
+ }
1815
+ function writeAddonToConfig(targetDir, name, entry) {
1816
+ const config = readAddonConfig(targetDir);
1817
+ config.installed[name] = entry;
1818
+ writeAddonConfig(targetDir, config);
1819
+ }
1820
+ function removeAddonFromConfig(targetDir, name) {
1821
+ const config = readAddonConfig(targetDir);
1822
+ delete config.installed[name];
1823
+ writeAddonConfig(targetDir, config);
1824
+ }
1825
+ function readMode(targetDir) {
1826
+ const config = readAddonConfig(targetDir);
1827
+ return config.mode ?? "hitl";
1828
+ }
1829
+ function writeMode(targetDir, mode) {
1830
+ const config = readAddonConfig(targetDir);
1831
+ config.mode = mode;
1832
+ writeAddonConfig(targetDir, config);
1833
+ }
1834
+ function detectOrphanedAddonFiles(targetDir) {
1835
+ const config = readAddonConfig(targetDir);
1836
+ const orphaned = [];
1837
+ for (const addon of getAvailableAddons()) {
1838
+ if (config.installed[addon.name]) continue;
1839
+ const skillDir = join9(targetDir, ".claude", "skills", addon.name);
1840
+ if (existsSync6(skillDir)) {
1841
+ orphaned.push(addon.name);
1842
+ }
1843
+ }
1844
+ return orphaned;
1845
+ }
1846
+ function registerAddonInConfig(targetDir, addonName) {
1847
+ const manifest = getAddonManifest(addonName);
1848
+ const fileList = [
1849
+ ...(manifest.files.skills ?? []).map((s) => `skills/${s}`),
1850
+ ...(manifest.files.agents ?? []).map((a) => `agents/${a}.md`),
1851
+ ...(manifest.files.rules ?? []).map((r) => `rules/${r}`)
1852
+ ];
1853
+ writeAddonToConfig(targetDir, addonName, {
1854
+ version: manifest.version,
1855
+ files: fileList
1856
+ });
1857
+ }
1858
+
1859
+ // src/generators/addonFiles.ts
1860
+ import {
1861
+ existsSync as existsSync7,
1862
+ readFileSync as readFileSync4,
1863
+ writeFileSync as writeFileSync2,
1864
+ mkdirSync as mkdirSync2,
1865
+ rmSync,
1866
+ unlinkSync,
1867
+ readdirSync,
1868
+ rmdirSync
1869
+ } from "fs";
1870
+ import { join as join10, dirname as dirname4 } from "path";
1871
+ import { createHash } from "crypto";
1872
+ var AGENT_PATHS = {
1873
+ claude: ".claude",
1874
+ cursor: ".cursor",
1875
+ gemini: ".gemini"
1876
+ };
1877
+ function stripFrontmatterName(content) {
1878
+ return content.replace(/^(---\n)([\s\S]*?)(---)/m, (_match, open, body, close) => {
1879
+ const cleaned = body.split("\n").filter((l) => !l.startsWith("name:")).join("\n");
1880
+ return `${open}${cleaned}${close}`;
1881
+ });
1882
+ }
1883
+ var RUNTIME_SPECS = {
1884
+ claude: {
1885
+ baseDir: ".claude",
1886
+ skillAdapter: (name, content) => ({
1887
+ relPath: `skills/${name}/SKILL.md`,
1888
+ content
1889
+ }),
1890
+ rulesDir: "rules"
1891
+ },
1892
+ gemini: {
1893
+ baseDir: ".gemini",
1894
+ skillAdapter: (name, content) => ({
1895
+ relPath: `skills/${name}/SKILL.md`,
1896
+ content
1897
+ }),
1898
+ rulesDir: "rules"
1899
+ },
1900
+ opencode: {
1901
+ baseDir: ".opencode",
1902
+ skillAdapter: (name, content) => ({
1903
+ relPath: `command/${name}.md`,
1904
+ content: stripFrontmatterName(content)
1905
+ })
1906
+ },
1907
+ cursor: {
1908
+ baseDir: ".cursor",
1909
+ skillAdapter: (name, content) => ({
1910
+ relPath: `skills/${name}/SKILL.md`,
1911
+ content
1912
+ }),
1913
+ rulesDir: "rules"
1914
+ },
1915
+ codex: {
1916
+ baseDir: ".codex",
1917
+ skillAdapter: (name, content) => ({
1918
+ relPath: `skills/${name}/SKILL.md`,
1919
+ content
1920
+ })
1921
+ }
1922
+ };
1923
+ function checksum(content) {
1924
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
1925
+ }
1926
+ function ensureDir2(dir) {
1927
+ if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1928
+ }
1929
+ function readManifest2(addonSourceDir) {
1930
+ const manifestPath = join10(addonSourceDir, "manifest.json");
1931
+ return JSON.parse(readFileSync4(manifestPath, "utf-8"));
1932
+ }
1933
+ function buildFileMap(addonSourceDir) {
1934
+ const manifest = readManifest2(addonSourceDir);
1935
+ const files = /* @__PURE__ */ new Map();
1936
+ for (const skill of manifest.files.skills ?? []) {
1937
+ const skillDir = join10(addonSourceDir, "skills", skill);
1938
+ if (!existsSync7(skillDir)) continue;
1939
+ const skillFile = join10(skillDir, "SKILL.md");
1940
+ if (existsSync7(skillFile)) {
1941
+ files.set(`skills/${skill}/SKILL.md`, readFileSync4(skillFile, "utf-8"));
1942
+ }
1943
+ }
1944
+ for (const agent of manifest.files.agents ?? []) {
1945
+ const agentFile = join10(addonSourceDir, "agents", `${agent}.md`);
1946
+ if (existsSync7(agentFile)) {
1947
+ files.set(`agents/${agent}.md`, readFileSync4(agentFile, "utf-8"));
1948
+ }
1949
+ }
1950
+ for (const ref of manifest.files.reference ?? []) {
1951
+ const refPath = join10(addonSourceDir, "reference", ref);
1952
+ if (existsSync7(refPath)) {
1953
+ files.set(`skills/design-harden/reference/${ref}`, readFileSync4(refPath, "utf-8"));
1954
+ }
1955
+ }
1956
+ for (const rule of manifest.files.rules ?? []) {
1957
+ const rulePath = join10(addonSourceDir, "rules", rule);
1958
+ if (existsSync7(rulePath)) {
1959
+ files.set(`rules/${rule}`, readFileSync4(rulePath, "utf-8"));
1960
+ }
1961
+ }
1962
+ return files;
1963
+ }
1964
+ function generateAddonFiles(projectDir, addonSourceDir, agents) {
1965
+ const fileMap = buildFileMap(addonSourceDir);
1966
+ const manifest = readManifest2(addonSourceDir);
1967
+ const result = { written: 0, skipped: 0, conflicts: [], checksums: {} };
1968
+ for (const agent of agents) {
1969
+ const spec = RUNTIME_SPECS[agent];
1970
+ if (!spec) {
1971
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
1972
+ for (const [relPath, content] of fileMap) {
1973
+ const destPath = join10(projectDir, basePath, relPath);
1974
+ if (existsSync7(destPath)) {
1975
+ result.skipped++;
1976
+ continue;
1977
+ }
1978
+ ensureDir2(dirname4(destPath));
1979
+ writeFileSync2(destPath, content);
1980
+ result.written++;
1981
+ result.checksums[relPath] = checksum(content);
1982
+ }
1983
+ continue;
1984
+ }
1985
+ for (const skillName of manifest.files.skills ?? []) {
1986
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
1987
+ if (!existsSync7(skillFile)) continue;
1988
+ const rawContent = readFileSync4(skillFile, "utf-8");
1989
+ const { relPath, content } = spec.skillAdapter(skillName, rawContent);
1990
+ const destPath = join10(projectDir, spec.baseDir, relPath);
1991
+ if (existsSync7(destPath)) {
1992
+ const existing = readFileSync4(destPath, "utf-8");
1993
+ if (existing === content) {
1994
+ result.skipped++;
1995
+ continue;
1996
+ }
1997
+ result.skipped++;
1998
+ continue;
1999
+ }
2000
+ ensureDir2(dirname4(destPath));
2001
+ writeFileSync2(destPath, content);
2002
+ result.written++;
2003
+ result.checksums[relPath] = checksum(content);
2004
+ }
2005
+ for (const agentName of manifest.files.agents ?? []) {
2006
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2007
+ if (!existsSync7(agentFile)) continue;
2008
+ const content = readFileSync4(agentFile, "utf-8");
2009
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2010
+ if (!existsSync7(destPath)) {
2011
+ ensureDir2(dirname4(destPath));
2012
+ writeFileSync2(destPath, content);
2013
+ result.written++;
2014
+ result.checksums[`agents/${agentName}.md`] = checksum(content);
2015
+ } else {
2016
+ result.skipped++;
2017
+ }
2018
+ }
2019
+ if (spec.rulesDir) {
2020
+ for (const rule of manifest.files.rules ?? []) {
2021
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2022
+ if (!existsSync7(ruleSrcPath)) continue;
2023
+ const content = readFileSync4(ruleSrcPath, "utf-8");
2024
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2025
+ if (!existsSync7(destPath)) {
2026
+ ensureDir2(dirname4(destPath));
2027
+ writeFileSync2(destPath, content);
2028
+ result.written++;
2029
+ result.checksums[`rules/${rule}`] = checksum(content);
2030
+ } else {
2031
+ result.skipped++;
2032
+ }
2033
+ }
2034
+ }
2035
+ for (const ref of manifest.files.reference ?? []) {
2036
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2037
+ if (!existsSync7(refSrcPath)) continue;
2038
+ const content = readFileSync4(refSrcPath, "utf-8");
2039
+ const relPath = `skills/design-harden/reference/${ref}`;
2040
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2041
+ if (!existsSync7(destPath)) {
2042
+ ensureDir2(dirname4(destPath));
2043
+ writeFileSync2(destPath, content);
2044
+ result.written++;
2045
+ result.checksums[relPath] = checksum(content);
2046
+ } else {
2047
+ result.skipped++;
2048
+ }
2049
+ }
2050
+ }
2051
+ if (manifest.attribution) {
2052
+ const noticePath = join10(projectDir, "NOTICE.md");
2053
+ const noticeContent = [
2054
+ "# NOTICE",
2055
+ "",
2056
+ "This project includes materials from third-party sources.",
2057
+ "",
2058
+ `## ${manifest.name}`,
2059
+ "",
2060
+ manifest.attribution,
2061
+ "",
2062
+ "Original source: Anthropic frontend-design skill",
2063
+ "License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)",
2064
+ ""
2065
+ ].join("\n");
2066
+ writeFileSync2(noticePath, noticeContent);
2067
+ }
2068
+ return result;
2069
+ }
2070
+ function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
2071
+ const sourceDir = addonSourceDir ?? getAddonSourceDir(addonName);
2072
+ const manifest = readManifest2(sourceDir);
2073
+ const knownSkills = manifest.files.skills ?? [];
2074
+ const knownAgents = manifest.files.agents ?? [];
2075
+ for (const agent of agents) {
2076
+ const spec = RUNTIME_SPECS[agent];
2077
+ if (!spec) {
2078
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2079
+ for (const skill of knownSkills) {
2080
+ const skillDir = join10(projectDir, basePath, "skills", skill);
2081
+ if (existsSync7(skillDir)) rmSync(skillDir, { recursive: true, force: true });
2082
+ }
2083
+ for (const agentName of knownAgents) {
2084
+ const agentPath = join10(projectDir, basePath, "agents", `${agentName}.md`);
2085
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2086
+ }
2087
+ continue;
2088
+ }
2089
+ for (const skillName of knownSkills) {
2090
+ const { relPath } = spec.skillAdapter(skillName, "");
2091
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2092
+ if (existsSync7(destPath)) unlinkSync(destPath);
2093
+ const parentDir = dirname4(destPath);
2094
+ if (existsSync7(parentDir)) {
2095
+ try {
2096
+ const entries = readdirSync(parentDir);
2097
+ if (entries.length === 0) rmdirSync(parentDir);
2098
+ } catch {
2099
+ }
2100
+ }
2101
+ }
2102
+ for (const agentName of knownAgents) {
2103
+ const agentPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2104
+ if (existsSync7(agentPath)) unlinkSync(agentPath);
2105
+ }
2106
+ if (spec.rulesDir) {
2107
+ for (const rule of manifest.files.rules ?? []) {
2108
+ const rulePath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2109
+ if (existsSync7(rulePath)) unlinkSync(rulePath);
2110
+ }
2111
+ }
2112
+ for (const ref of manifest.files.reference ?? []) {
2113
+ const refPath = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference", ref);
2114
+ if (existsSync7(refPath)) unlinkSync(refPath);
2115
+ }
2116
+ const refDir = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference");
2117
+ if (existsSync7(refDir)) {
2118
+ try {
2119
+ const entries = readdirSync(refDir);
2120
+ if (entries.length === 0) rmdirSync(refDir);
2121
+ } catch {
2122
+ }
2123
+ }
2124
+ }
2125
+ const noticePath = join10(projectDir, "NOTICE.md");
2126
+ if (existsSync7(noticePath)) unlinkSync(noticePath);
2127
+ }
2128
+ function syncAddonFiles(projectDir, addonSourceDir, agents) {
2129
+ const fileMap = buildFileMap(addonSourceDir);
2130
+ const manifest = readManifest2(addonSourceDir);
2131
+ const addonName = manifest.name;
2132
+ const result = { written: 0, skipped: 0, conflicts: [], updated: 0 };
2133
+ let installedChecksums = {};
2134
+ try {
2135
+ const config = readAddonConfig(projectDir);
2136
+ const installed = config.installed?.[addonName];
2137
+ if (installed?.checksums) {
2138
+ installedChecksums = installed.checksums;
2139
+ }
2140
+ } catch {
2141
+ }
2142
+ for (const agent of agents) {
2143
+ const spec = RUNTIME_SPECS[agent];
2144
+ if (!spec) {
2145
+ const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
2146
+ for (const [relPath, newContent] of fileMap) {
2147
+ const destPath = join10(projectDir, basePath, relPath);
2148
+ if (!existsSync7(destPath)) {
2149
+ ensureDir2(dirname4(destPath));
2150
+ writeFileSync2(destPath, newContent);
2151
+ result.written++;
2152
+ continue;
2153
+ }
2154
+ const existing = readFileSync4(destPath, "utf-8");
2155
+ const existingChecksum = checksum(existing);
2156
+ const originalChecksum = installedChecksums[relPath];
2157
+ if (existing === newContent) {
2158
+ result.skipped++;
2159
+ continue;
2160
+ }
2161
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2162
+ result.conflicts.push(relPath);
2163
+ continue;
2164
+ }
2165
+ writeFileSync2(destPath, newContent);
2166
+ result.updated = (result.updated ?? 0) + 1;
2167
+ }
2168
+ continue;
2169
+ }
2170
+ for (const skillName of manifest.files.skills ?? []) {
2171
+ const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
2172
+ if (!existsSync7(skillFile)) continue;
2173
+ const rawContent = readFileSync4(skillFile, "utf-8");
2174
+ const { relPath, content: newContent } = spec.skillAdapter(skillName, rawContent);
2175
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2176
+ if (!existsSync7(destPath)) {
2177
+ ensureDir2(dirname4(destPath));
2178
+ writeFileSync2(destPath, newContent);
2179
+ result.written++;
2180
+ continue;
2181
+ }
2182
+ const existing = readFileSync4(destPath, "utf-8");
2183
+ const existingChecksum = checksum(existing);
2184
+ const originalChecksum = installedChecksums[relPath];
2185
+ if (existing === newContent) {
2186
+ result.skipped++;
2187
+ continue;
2188
+ }
2189
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2190
+ result.conflicts.push(relPath);
2191
+ continue;
2192
+ }
2193
+ writeFileSync2(destPath, newContent);
2194
+ result.updated = (result.updated ?? 0) + 1;
2195
+ }
2196
+ for (const agentName of manifest.files.agents ?? []) {
2197
+ const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
2198
+ if (!existsSync7(agentFile)) continue;
2199
+ const newContent = readFileSync4(agentFile, "utf-8");
2200
+ const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
2201
+ const relPath = `agents/${agentName}.md`;
2202
+ if (!existsSync7(destPath)) {
2203
+ ensureDir2(dirname4(destPath));
2204
+ writeFileSync2(destPath, newContent);
2205
+ result.written++;
2206
+ continue;
2207
+ }
2208
+ const existing = readFileSync4(destPath, "utf-8");
2209
+ const existingChecksum = checksum(existing);
2210
+ const originalChecksum = installedChecksums[relPath];
2211
+ if (existing === newContent) {
2212
+ result.skipped++;
2213
+ continue;
2214
+ }
2215
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2216
+ result.conflicts.push(relPath);
2217
+ continue;
2218
+ }
2219
+ writeFileSync2(destPath, newContent);
2220
+ result.updated = (result.updated ?? 0) + 1;
2221
+ }
2222
+ if (spec.rulesDir) {
2223
+ for (const rule of manifest.files.rules ?? []) {
2224
+ const ruleSrcPath = join10(addonSourceDir, "rules", rule);
2225
+ if (!existsSync7(ruleSrcPath)) continue;
2226
+ const newContent = readFileSync4(ruleSrcPath, "utf-8");
2227
+ const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
2228
+ const relPath = `rules/${rule}`;
2229
+ if (!existsSync7(destPath)) {
2230
+ ensureDir2(dirname4(destPath));
2231
+ writeFileSync2(destPath, newContent);
2232
+ result.written++;
2233
+ continue;
2234
+ }
2235
+ const existing = readFileSync4(destPath, "utf-8");
2236
+ const existingChecksum = checksum(existing);
2237
+ const originalChecksum = installedChecksums[relPath];
2238
+ if (existing === newContent) {
2239
+ result.skipped++;
2240
+ continue;
2241
+ }
2242
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2243
+ result.conflicts.push(relPath);
2244
+ continue;
2245
+ }
2246
+ writeFileSync2(destPath, newContent);
2247
+ result.updated = (result.updated ?? 0) + 1;
2248
+ }
2249
+ }
2250
+ for (const ref of manifest.files.reference ?? []) {
2251
+ const refSrcPath = join10(addonSourceDir, "reference", ref);
2252
+ if (!existsSync7(refSrcPath)) continue;
2253
+ const newContent = readFileSync4(refSrcPath, "utf-8");
2254
+ const relPath = `skills/design-harden/reference/${ref}`;
2255
+ const destPath = join10(projectDir, spec.baseDir, relPath);
2256
+ if (!existsSync7(destPath)) {
2257
+ ensureDir2(dirname4(destPath));
2258
+ writeFileSync2(destPath, newContent);
2259
+ result.written++;
2260
+ continue;
2261
+ }
2262
+ const existing = readFileSync4(destPath, "utf-8");
2263
+ const existingChecksum = checksum(existing);
2264
+ const originalChecksum = installedChecksums[relPath];
2265
+ if (existing === newContent) {
2266
+ result.skipped++;
2267
+ continue;
2268
+ }
2269
+ if (originalChecksum && existingChecksum !== originalChecksum) {
2270
+ result.conflicts.push(relPath);
2271
+ continue;
2272
+ }
2273
+ writeFileSync2(destPath, newContent);
2274
+ result.updated = (result.updated ?? 0) + 1;
2275
+ }
2276
+ }
2277
+ return result;
2278
+ }
2279
+ function detectModifiedAddonFiles(projectDir, addonName) {
2280
+ let installedChecksums = {};
2281
+ try {
2282
+ const config = readAddonConfig(projectDir);
2283
+ const installed = config.installed?.[addonName];
2284
+ if (!installed?.checksums) return [];
2285
+ installedChecksums = installed.checksums;
2286
+ } catch {
2287
+ return [];
2288
+ }
2289
+ const modified = [];
2290
+ for (const agentDir of Object.values(AGENT_PATHS)) {
2291
+ for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
2292
+ const absPath = join10(projectDir, agentDir, relPath);
2293
+ if (!existsSync7(absPath)) continue;
2294
+ const current = checksum(readFileSync4(absPath, "utf-8"));
2295
+ if (current !== originalHash) {
2296
+ modified.push(relPath);
2297
+ }
2298
+ }
2299
+ }
2300
+ return [...new Set(modified)];
2301
+ }
2302
+
1738
2303
  // src/commands/update.ts
1739
2304
  async function updateCommand(options) {
1740
2305
  if (!options.check && !options.dryRun) {
@@ -1800,7 +2365,7 @@ async function updateCommand(options) {
1800
2365
  const modifiedFiles = [];
1801
2366
  const outdatedFiles = [];
1802
2367
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
1803
- const filePath = join8(targetDir, relativePath);
2368
+ const filePath = join11(targetDir, relativePath);
1804
2369
  if (!fileExists(filePath)) {
1805
2370
  continue;
1806
2371
  }
@@ -1812,11 +2377,11 @@ async function updateCommand(options) {
1812
2377
  }
1813
2378
  const newFiles = [];
1814
2379
  for (const ide of manifest.selectedIDEs) {
1815
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1816
- if (!existsSync6(templateDir)) continue;
2380
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2381
+ if (!existsSync8(templateDir)) continue;
1817
2382
  const files = getAllFilesRecursive(templateDir);
1818
2383
  for (const file of files) {
1819
- const templatePath = join8(templateDir, file);
2384
+ const templatePath = join11(templateDir, file);
1820
2385
  const templateContent = readFile(templatePath);
1821
2386
  const templateChecksum = calculateChecksum(templateContent);
1822
2387
  const fileInfo = manifest.files[file];
@@ -1832,14 +2397,14 @@ async function updateCommand(options) {
1832
2397
  if (fileInfo.ignored) continue;
1833
2398
  let foundInAnyTemplate = false;
1834
2399
  for (const ide of manifest.selectedIDEs) {
1835
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1836
- if (existsSync6(join8(templateDir, relativePath))) {
2400
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2401
+ if (existsSync8(join11(templateDir, relativePath))) {
1837
2402
  foundInAnyTemplate = true;
1838
2403
  break;
1839
2404
  }
1840
2405
  }
1841
2406
  if (!foundInAnyTemplate) {
1842
- const localPath = join8(targetDir, relativePath);
2407
+ const localPath = join11(targetDir, relativePath);
1843
2408
  if (fileExists(localPath)) {
1844
2409
  removedFromTemplate.push({
1845
2410
  path: relativePath,
@@ -1967,8 +2532,8 @@ async function updateCommand(options) {
1967
2532
  implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1968
2533
  };
1969
2534
  for (const ide of manifest.selectedIDEs) {
1970
- const templateDir = join8(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
1971
- if (!existsSync6(templateDir)) continue;
2535
+ const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2536
+ if (!existsSync8(templateDir)) continue;
1972
2537
  const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
1973
2538
  const files = getAllFilesRecursive(templateDir);
1974
2539
  for (const file of files) {
@@ -1978,10 +2543,10 @@ async function updateCommand(options) {
1978
2543
  if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
1979
2544
  continue;
1980
2545
  }
1981
- const templatePath = join8(templateDir, file);
1982
- const destPath = join8(targetDir, file);
2546
+ const templatePath = join11(templateDir, file);
2547
+ const destPath = join11(targetDir, file);
1983
2548
  const templateContent = readFile(templatePath);
1984
- ensureDir(dirname3(destPath));
2549
+ ensureDir(dirname5(destPath));
1985
2550
  writeFile(destPath, templateContent);
1986
2551
  updatedManifest.files[file] = createManifestEntry(templateContent);
1987
2552
  }
@@ -1989,7 +2554,7 @@ async function updateCommand(options) {
1989
2554
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
1990
2555
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
1991
2556
  for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
1992
- const filePath = join8(targetDir, relPath);
2557
+ const filePath = join11(targetDir, relPath);
1993
2558
  if (!fileExists(filePath)) continue;
1994
2559
  const diskContent = readFile(filePath);
1995
2560
  const diskChecksum = calculateChecksum(diskContent);
@@ -2006,13 +2571,13 @@ async function updateCommand(options) {
2006
2571
  analysis.packageManager
2007
2572
  );
2008
2573
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2009
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2010
- if (existsSync6(scriptPath)) {
2574
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2575
+ if (existsSync8(scriptPath)) {
2011
2576
  chmodSync2(scriptPath, 493);
2012
2577
  }
2013
2578
  }
2014
2579
  for (const [relPath, content] of userModifiedPluginFiles) {
2015
- writeFile(join8(targetDir, relPath), content);
2580
+ writeFile(join11(targetDir, relPath), content);
2016
2581
  }
2017
2582
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2018
2583
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2023,9 +2588,9 @@ async function updateCommand(options) {
2023
2588
  updatedManifest.pluginPath = pluginResult.pluginPath;
2024
2589
  }
2025
2590
  for (const filePath of filesToDelete) {
2026
- const localPath = join8(targetDir, filePath);
2591
+ const localPath = join11(targetDir, filePath);
2027
2592
  if (fileExists(localPath)) {
2028
- unlinkSync(localPath);
2593
+ unlinkSync2(localPath);
2029
2594
  }
2030
2595
  delete updatedManifest.files[filePath];
2031
2596
  }
@@ -2034,12 +2599,12 @@ async function updateCommand(options) {
2034
2599
  updatedManifest.files[filePath].ignored = true;
2035
2600
  }
2036
2601
  }
2037
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2602
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2038
2603
  if (fileExists(claudeMdPath)) {
2039
2604
  const stat = lstatSync(claudeMdPath);
2040
2605
  if (stat.isSymbolicLink()) {
2041
2606
  const content = readFile(claudeMdPath);
2042
- unlinkSync(claudeMdPath);
2607
+ unlinkSync2(claudeMdPath);
2043
2608
  writeFile(claudeMdPath, content);
2044
2609
  updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
2045
2610
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2047,6 +2612,28 @@ async function updateCommand(options) {
2047
2612
  }
2048
2613
  writeManifest(targetDir, updatedManifest);
2049
2614
  spinner8.stop("Updates applied");
2615
+ const orphaned = detectOrphanedAddonFiles(targetDir);
2616
+ if (orphaned.length > 0) {
2617
+ const migSpinner = p4.spinner();
2618
+ migSpinner.start("Migrating detected addon files to config...");
2619
+ for (const addonName of orphaned) {
2620
+ registerAddonInConfig(targetDir, addonName);
2621
+ }
2622
+ migSpinner.stop(`${symbols.pass} Migrated: ${orphaned.join(", ")}`);
2623
+ }
2624
+ const addonConfig = readAddonConfig(targetDir);
2625
+ const enabledAddons = Object.keys(addonConfig.installed);
2626
+ if (enabledAddons.length > 0 && !options.check) {
2627
+ const syncSpinner = p4.spinner();
2628
+ syncSpinner.start("Updating enabled addon files...");
2629
+ let totalUpdated = 0;
2630
+ for (const name of enabledAddons) {
2631
+ const addonSourceDir = getAddonSourceDir(name);
2632
+ const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
2633
+ totalUpdated += (result.updated ?? 0) + result.written;
2634
+ }
2635
+ syncSpinner.stop(`${symbols.pass} Addon files updated (${totalUpdated} files)`);
2636
+ }
2050
2637
  p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
2051
2638
  }
2052
2639
  function detectStackChanges(savedConfig, analysis) {
@@ -2153,7 +2740,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2153
2740
  const spinner8 = p4.spinner();
2154
2741
  spinner8.start("Regenerating with new stack...");
2155
2742
  const regeneratedFiles = [];
2156
- const agentsMdPath = join8(targetDir, "AGENTS.md");
2743
+ const agentsMdPath = join11(targetDir, "AGENTS.md");
2157
2744
  if (fileExists(agentsMdPath)) {
2158
2745
  const agentsMdContent = generateAgentsMdFromConfig(
2159
2746
  newConfig,
@@ -2164,12 +2751,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2164
2751
  regeneratedFiles.push("AGENTS.md");
2165
2752
  manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
2166
2753
  }
2167
- const claudeMdPath = join8(targetDir, "CLAUDE.md");
2754
+ const claudeMdPath = join11(targetDir, "CLAUDE.md");
2168
2755
  if (fileExists(claudeMdPath)) {
2169
2756
  const stat = lstatSync(claudeMdPath);
2170
2757
  if (stat.isSymbolicLink()) {
2171
2758
  const content = readFile(claudeMdPath);
2172
- unlinkSync(claudeMdPath);
2759
+ unlinkSync2(claudeMdPath);
2173
2760
  writeFile(claudeMdPath, content);
2174
2761
  manifest.files["CLAUDE.md"] = createManifestEntry(content);
2175
2762
  p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
@@ -2181,8 +2768,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2181
2768
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2182
2769
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2183
2770
  if (ruleContent && rulePath) {
2184
- const destPath = join8(targetDir, rulePath);
2185
- ensureDir(dirname3(destPath));
2771
+ const destPath = join11(targetDir, rulePath);
2772
+ ensureDir(dirname5(destPath));
2186
2773
  writeFile(destPath, ruleContent);
2187
2774
  regeneratedFiles.push(rulePath);
2188
2775
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2192,7 +2779,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2192
2779
  if (manifest.installMode === "plugin" && manifest.pluginPath) {
2193
2780
  const userModifiedPluginFiles = /* @__PURE__ */ new Map();
2194
2781
  for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
2195
- const filePath = join8(targetDir, relPath);
2782
+ const filePath = join11(targetDir, relPath);
2196
2783
  if (!fileExists(filePath)) continue;
2197
2784
  const diskContent = readFile(filePath);
2198
2785
  const diskChecksum = calculateChecksum(diskContent);
@@ -2208,13 +2795,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
2208
2795
  analysis.packageManager
2209
2796
  );
2210
2797
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2211
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2212
- if (existsSync6(scriptPath)) {
2798
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2799
+ if (existsSync8(scriptPath)) {
2213
2800
  chmodSync2(scriptPath, 493);
2214
2801
  }
2215
2802
  }
2216
2803
  for (const [relPath, content] of userModifiedPluginFiles) {
2217
- writeFile(join8(targetDir, relPath), content);
2804
+ writeFile(join11(targetDir, relPath), content);
2218
2805
  }
2219
2806
  for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
2220
2807
  if (userModifiedPluginFiles.has(relPath)) {
@@ -2257,8 +2844,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2257
2844
  analysis.packageManager
2258
2845
  );
2259
2846
  for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
2260
- const scriptPath = join8(targetDir, pluginResult.pluginPath, "scripts", script);
2261
- if (existsSync6(scriptPath)) {
2847
+ const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
2848
+ if (existsSync8(scriptPath)) {
2262
2849
  chmodSync2(scriptPath, 493);
2263
2850
  }
2264
2851
  }
@@ -2267,19 +2854,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
2267
2854
  const preserved = [];
2268
2855
  for (const [path, fileInfo] of Object.entries(manifest.files)) {
2269
2856
  if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
2270
- const filePath = join8(targetDir, path);
2857
+ const filePath = join11(targetDir, path);
2271
2858
  if (!fileExists(filePath)) continue;
2272
2859
  const current = calculateChecksum(readFile(filePath));
2273
2860
  if (current === fileInfo.originalChecksum) {
2274
- unlinkSync(filePath);
2861
+ unlinkSync2(filePath);
2275
2862
  delete manifest.files[path];
2276
2863
  removed.push(path);
2277
2864
  } else {
2278
2865
  preserved.push(path);
2279
2866
  }
2280
2867
  }
2281
- cleanEmptyDirs(join8(targetDir, ".claude", "skills"));
2282
- cleanEmptyDirs(join8(targetDir, ".claude", "agents"));
2868
+ cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
2869
+ cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
2283
2870
  Object.assign(manifest.files, pluginResult.files);
2284
2871
  manifest.installMode = "plugin";
2285
2872
  manifest.pluginPath = pluginResult.pluginPath;
@@ -2328,22 +2915,22 @@ function buildDefaultConfig(analysis) {
2328
2915
  };
2329
2916
  }
2330
2917
  function cleanEmptyDirs(dirPath) {
2331
- if (!existsSync6(dirPath)) return;
2332
- const entries = readdirSync(dirPath);
2918
+ if (!existsSync8(dirPath)) return;
2919
+ const entries = readdirSync2(dirPath);
2333
2920
  for (const entry of entries) {
2334
- const fullPath = join8(dirPath, entry);
2335
- if (existsSync6(fullPath) && lstatSync(fullPath).isDirectory()) {
2921
+ const fullPath = join11(dirPath, entry);
2922
+ if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
2336
2923
  cleanEmptyDirs(fullPath);
2337
2924
  }
2338
2925
  }
2339
- const remaining = readdirSync(dirPath);
2926
+ const remaining = readdirSync2(dirPath);
2340
2927
  if (remaining.length === 0) {
2341
- rmdirSync(dirPath);
2928
+ rmdirSync2(dirPath);
2342
2929
  }
2343
2930
  }
2344
2931
 
2345
2932
  // src/commands/status.ts
2346
- import { resolve as resolve4, join as join9 } from "path";
2933
+ import { resolve as resolve4, join as join12 } from "path";
2347
2934
  import * as p5 from "@clack/prompts";
2348
2935
  import chalk6 from "chalk";
2349
2936
  async function statusCommand(options = {}) {
@@ -2357,6 +2944,22 @@ async function statusCommand(options = {}) {
2357
2944
  p5.outro("");
2358
2945
  return;
2359
2946
  }
2947
+ const mode = readMode(targetDir);
2948
+ const addonConfig = readAddonConfig(targetDir);
2949
+ const allAddons = getAvailableAddons();
2950
+ p5.note(
2951
+ [
2952
+ `${formatKV("Mode:", chalk6.cyan(mode))}`,
2953
+ `${formatKV("Config:", chalk6.dim(".claude/devtronic.json"))}`
2954
+ ].join("\n"),
2955
+ "Execution Mode"
2956
+ );
2957
+ const addonLines = allAddons.map((addon) => {
2958
+ const isEnabled = !!addonConfig.installed[addon.name];
2959
+ const statusStr = isEnabled ? chalk6.green("enabled") : chalk6.dim("disabled");
2960
+ return ` ${chalk6.bold((addon.name + ":").padEnd(28))} ${statusStr}`;
2961
+ });
2962
+ p5.note(addonLines.join("\n"), "Addons");
2360
2963
  p5.note(
2361
2964
  [
2362
2965
  formatKV("Version:", manifest.version),
@@ -2367,7 +2970,7 @@ async function statusCommand(options = {}) {
2367
2970
  );
2368
2971
  const fileStatuses = [];
2369
2972
  for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
2370
- const filePath = join9(targetDir, relativePath);
2973
+ const filePath = join12(targetDir, relativePath);
2371
2974
  if (!fileExists(filePath)) {
2372
2975
  fileStatuses.push({ path: relativePath, status: "missing" });
2373
2976
  continue;
@@ -2413,8 +3016,8 @@ async function statusCommand(options = {}) {
2413
3016
  }
2414
3017
 
2415
3018
  // src/commands/diff.ts
2416
- import { resolve as resolve5, join as join10 } from "path";
2417
- import { existsSync as existsSync7 } from "fs";
3019
+ import { resolve as resolve5, join as join13 } from "path";
3020
+ import { existsSync as existsSync9 } from "fs";
2418
3021
  import * as p6 from "@clack/prompts";
2419
3022
  import chalk7 from "chalk";
2420
3023
  async function diffCommand(options = {}) {
@@ -2429,12 +3032,12 @@ async function diffCommand(options = {}) {
2429
3032
  }
2430
3033
  const diffs = [];
2431
3034
  for (const ide of manifest.selectedIDEs) {
2432
- const templateDir = join10(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
2433
- if (!existsSync7(templateDir)) continue;
3035
+ const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
3036
+ if (!existsSync9(templateDir)) continue;
2434
3037
  const templateFiles = getAllFilesRecursive(templateDir);
2435
3038
  for (const file of templateFiles) {
2436
- const templatePath = join10(templateDir, file);
2437
- const localPath = join10(targetDir, file);
3039
+ const templatePath = join13(templateDir, file);
3040
+ const localPath = join13(targetDir, file);
2438
3041
  const templateContent = readFile(templatePath);
2439
3042
  const templateChecksum = calculateChecksum(templateContent);
2440
3043
  const manifestEntry = manifest.files[file];
@@ -2487,8 +3090,8 @@ async function diffCommand(options = {}) {
2487
3090
  }
2488
3091
 
2489
3092
  // src/commands/add.ts
2490
- import { existsSync as existsSync8 } from "fs";
2491
- import { resolve as resolve6, join as join11, dirname as dirname4 } from "path";
3093
+ import { existsSync as existsSync10 } from "fs";
3094
+ import { resolve as resolve6, join as join14, dirname as dirname6 } from "path";
2492
3095
  import * as p7 from "@clack/prompts";
2493
3096
  import chalk8 from "chalk";
2494
3097
  var ALL_IDES = [
@@ -2582,8 +3185,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2582
3185
  const generatedFiles = [];
2583
3186
  const generatedRules = generateArchitectureRules(manifest.projectConfig);
2584
3187
  const templateName = IDE_TEMPLATE_MAP[selectedIDE];
2585
- const templateDir = join11(TEMPLATES_DIR, templateName);
2586
- if (!existsSync8(templateDir)) {
3188
+ const templateDir = join14(TEMPLATES_DIR, templateName);
3189
+ if (!existsSync10(templateDir)) {
2587
3190
  spinner8.stop("Error");
2588
3191
  p7.cancel(`Template not found: ${templateName}`);
2589
3192
  process.exit(1);
@@ -2594,8 +3197,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2594
3197
  if (dynamicFiles.includes(file)) {
2595
3198
  continue;
2596
3199
  }
2597
- const sourcePath = join11(templateDir, file);
2598
- const destPath = join11(targetDir, file);
3200
+ const sourcePath = join14(templateDir, file);
3201
+ const destPath = join14(targetDir, file);
2599
3202
  const sourceContent = readFile(sourcePath);
2600
3203
  if (fileExists(destPath)) {
2601
3204
  if (conflictResolution === "keep") {
@@ -2612,7 +3215,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2612
3215
  continue;
2613
3216
  }
2614
3217
  }
2615
- ensureDir(dirname4(destPath));
3218
+ ensureDir(dirname6(destPath));
2616
3219
  writeFile(destPath, sourceContent);
2617
3220
  appliedFiles.push(file);
2618
3221
  manifest.files[file] = createManifestEntry(sourceContent);
@@ -2621,7 +3224,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2621
3224
  if (ruleContent) {
2622
3225
  const rulePath = dynamicFiles[0];
2623
3226
  if (rulePath) {
2624
- const destPath = join11(targetDir, rulePath);
3227
+ const destPath = join14(targetDir, rulePath);
2625
3228
  if (fileExists(destPath) && conflictResolution === "keep") {
2626
3229
  skippedFiles.push(rulePath);
2627
3230
  } else if (fileExists(destPath) && conflictResolution === "merge") {
@@ -2632,7 +3235,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2632
3235
  mergedFiles.push(rulePath);
2633
3236
  manifest.files[rulePath] = createManifestEntry(mergedContent);
2634
3237
  } else {
2635
- ensureDir(dirname4(destPath));
3238
+ ensureDir(dirname6(destPath));
2636
3239
  writeFile(destPath, ruleContent);
2637
3240
  generatedFiles.push(`${rulePath} (personalized)`);
2638
3241
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2673,14 +3276,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
2673
3276
  }
2674
3277
 
2675
3278
  // src/commands/regenerate.ts
2676
- import { existsSync as existsSync9 } from "fs";
2677
- import { resolve as resolve7, join as join12, dirname as dirname5 } from "path";
3279
+ import { existsSync as existsSync11 } from "fs";
3280
+ import { resolve as resolve7, join as join15, dirname as dirname7 } from "path";
2678
3281
  import { fileURLToPath as fileURLToPath3 } from "url";
2679
3282
  import * as p8 from "@clack/prompts";
2680
3283
  import chalk9 from "chalk";
2681
3284
  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");
3285
+ var __regen_dirname = dirname7(__filename);
3286
+ var TEMPLATES_DIR2 = existsSync11(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
2684
3287
  async function regenerateCommand(target, options) {
2685
3288
  ensureInteractive("regenerate");
2686
3289
  const targetDir = resolve7(options.path || ".");
@@ -2757,7 +3360,7 @@ Valid options:
2757
3360
  return;
2758
3361
  }
2759
3362
  if (regenerateClaudeMd) {
2760
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3363
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2761
3364
  if (fileExists(claudeMdPath)) {
2762
3365
  p8.log.warn(
2763
3366
  "Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
@@ -2794,7 +3397,7 @@ Valid options:
2794
3397
  spinner8.start("Regenerating files...");
2795
3398
  const regeneratedFiles = [];
2796
3399
  if (regenerateClaudeMd) {
2797
- const claudeMdPath = join12(targetDir, "CLAUDE.md");
3400
+ const claudeMdPath = join15(targetDir, "CLAUDE.md");
2798
3401
  const claudeMdContent = generateClaudeMd(
2799
3402
  fullProjectConfig,
2800
3403
  analysis.scripts,
@@ -2805,7 +3408,7 @@ Valid options:
2805
3408
  manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
2806
3409
  }
2807
3410
  if (regenerateAgentsMd) {
2808
- const agentsMdPath = join12(targetDir, "AGENTS.md");
3411
+ const agentsMdPath = join15(targetDir, "AGENTS.md");
2809
3412
  const agentsMdContent = generateAgentsMdFromConfig(
2810
3413
  fullProjectConfig,
2811
3414
  analysis.scripts,
@@ -2822,8 +3425,8 @@ Valid options:
2822
3425
  const ruleContent = getRuleContentForIDE(ide, generatedRules);
2823
3426
  const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
2824
3427
  if (ruleContent && rulePath) {
2825
- const destPath = join12(targetDir, rulePath);
2826
- ensureDir(dirname5(destPath));
3428
+ const destPath = join15(targetDir, rulePath);
3429
+ ensureDir(dirname7(destPath));
2827
3430
  writeFile(destPath, ruleContent);
2828
3431
  regeneratedFiles.push(rulePath);
2829
3432
  manifest.files[rulePath] = createManifestEntry(ruleContent);
@@ -2837,7 +3440,7 @@ Valid options:
2837
3440
  if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
2838
3441
  p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
2839
3442
  } else {
2840
- const { generatePlugin: generatePlugin2 } = await import("./plugin-5ZMTSRIW.js");
3443
+ const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
2841
3444
  const pluginResult = generatePlugin2(
2842
3445
  targetDir,
2843
3446
  TEMPLATES_DIR2,
@@ -2872,8 +3475,8 @@ Valid options:
2872
3475
  }
2873
3476
 
2874
3477
  // src/commands/info.ts
2875
- import { resolve as resolve8, join as join13 } from "path";
2876
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
3478
+ import { resolve as resolve8, join as join16 } from "path";
3479
+ import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
2877
3480
  import * as p9 from "@clack/prompts";
2878
3481
  import chalk10 from "chalk";
2879
3482
  async function infoCommand() {
@@ -2886,33 +3489,33 @@ async function infoCommand() {
2886
3489
  let skillCount = 0;
2887
3490
  let agentCount = 0;
2888
3491
  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(
3492
+ const pluginDir = manifest.pluginPath ? join16(targetDir, manifest.pluginPath) : null;
3493
+ if (pluginDir && existsSync12(pluginDir)) {
3494
+ const skillsDir = join16(pluginDir, "skills");
3495
+ const agentsDir = join16(pluginDir, "agents");
3496
+ if (existsSync12(skillsDir)) {
3497
+ skillCount = readdirSync3(skillsDir, { withFileTypes: true }).filter(
2895
3498
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2896
3499
  ).length;
2897
3500
  }
2898
- if (existsSync10(agentsDir)) {
2899
- agentCount = readdirSync2(agentsDir, { withFileTypes: true }).filter(
3501
+ if (existsSync12(agentsDir)) {
3502
+ agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
2900
3503
  (e) => e.isFile() && e.name.endsWith(".md")
2901
3504
  ).length;
2902
3505
  }
2903
3506
  }
2904
3507
  if (skillCount === 0) {
2905
- const claudeSkills = join13(targetDir, ".claude", "skills");
2906
- if (existsSync10(claudeSkills)) {
2907
- skillCount = readdirSync2(claudeSkills, { withFileTypes: true }).filter(
3508
+ const claudeSkills = join16(targetDir, ".claude", "skills");
3509
+ if (existsSync12(claudeSkills)) {
3510
+ skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
2908
3511
  (e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
2909
3512
  ).length;
2910
3513
  }
2911
3514
  }
2912
3515
  if (agentCount === 0) {
2913
- const claudeAgents = join13(targetDir, ".claude", "agents");
2914
- if (existsSync10(claudeAgents)) {
2915
- agentCount = readdirSync2(claudeAgents, { withFileTypes: true }).filter(
3516
+ const claudeAgents = join16(targetDir, ".claude", "agents");
3517
+ if (existsSync12(claudeAgents)) {
3518
+ agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
2916
3519
  (e) => e.isFile() && e.name.endsWith(".md")
2917
3520
  ).length;
2918
3521
  }
@@ -2955,8 +3558,8 @@ async function infoCommand() {
2955
3558
  }
2956
3559
 
2957
3560
  // 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";
3561
+ import { resolve as resolve9, join as join17 } from "path";
3562
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
2960
3563
  import * as p10 from "@clack/prompts";
2961
3564
  import chalk11 from "chalk";
2962
3565
  async function listCommand(filter, options) {
@@ -2973,30 +3576,30 @@ Valid options: skills, agents`);
2973
3576
  }
2974
3577
  const skills = [];
2975
3578
  const agents = [];
2976
- const pluginDir = manifest?.pluginPath ? join14(targetDir, manifest.pluginPath) : null;
2977
- if (pluginDir && existsSync11(pluginDir)) {
3579
+ const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
3580
+ if (pluginDir && existsSync13(pluginDir)) {
2978
3581
  if (showSkills) {
2979
- const skillsDir = join14(pluginDir, "skills");
2980
- if (existsSync11(skillsDir)) {
3582
+ const skillsDir = join17(pluginDir, "skills");
3583
+ if (existsSync13(skillsDir)) {
2981
3584
  skills.push(...discoverSkills(skillsDir));
2982
3585
  }
2983
3586
  }
2984
3587
  if (showAgents) {
2985
- const agentsDir = join14(pluginDir, "agents");
2986
- if (existsSync11(agentsDir)) {
3588
+ const agentsDir = join17(pluginDir, "agents");
3589
+ if (existsSync13(agentsDir)) {
2987
3590
  agents.push(...discoverAgents(agentsDir));
2988
3591
  }
2989
3592
  }
2990
3593
  }
2991
3594
  if (showSkills && skills.length === 0) {
2992
- const claudeSkills = join14(targetDir, ".claude", "skills");
2993
- if (existsSync11(claudeSkills)) {
3595
+ const claudeSkills = join17(targetDir, ".claude", "skills");
3596
+ if (existsSync13(claudeSkills)) {
2994
3597
  skills.push(...discoverSkills(claudeSkills));
2995
3598
  }
2996
3599
  }
2997
3600
  if (showAgents && agents.length === 0) {
2998
- const claudeAgents = join14(targetDir, ".claude", "agents");
2999
- if (existsSync11(claudeAgents)) {
3601
+ const claudeAgents = join17(targetDir, ".claude", "agents");
3602
+ if (existsSync13(claudeAgents)) {
3000
3603
  agents.push(...discoverAgents(claudeAgents));
3001
3604
  }
3002
3605
  }
@@ -3020,15 +3623,15 @@ Valid options: skills, agents`);
3020
3623
  }
3021
3624
  function discoverSkills(skillsDir) {
3022
3625
  const items = [];
3023
- const entries = readdirSync3(skillsDir, { withFileTypes: true });
3626
+ const entries = readdirSync4(skillsDir, { withFileTypes: true });
3024
3627
  for (const entry of entries) {
3025
3628
  if (entry.isDirectory()) {
3026
- const skillMd = join14(skillsDir, entry.name, "SKILL.md");
3027
- const description = existsSync11(skillMd) ? extractDescription(skillMd) : "";
3629
+ const skillMd = join17(skillsDir, entry.name, "SKILL.md");
3630
+ const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
3028
3631
  items.push({ name: entry.name, description });
3029
3632
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
3030
3633
  const name = entry.name.replace(/\.md$/, "");
3031
- const description = extractDescription(join14(skillsDir, entry.name));
3634
+ const description = extractDescription(join17(skillsDir, entry.name));
3032
3635
  items.push({ name, description });
3033
3636
  }
3034
3637
  }
@@ -3036,11 +3639,11 @@ function discoverSkills(skillsDir) {
3036
3639
  }
3037
3640
  function discoverAgents(agentsDir) {
3038
3641
  const items = [];
3039
- const entries = readdirSync3(agentsDir, { withFileTypes: true });
3642
+ const entries = readdirSync4(agentsDir, { withFileTypes: true });
3040
3643
  for (const entry of entries) {
3041
3644
  if (entry.isFile() && entry.name.endsWith(".md")) {
3042
3645
  const name = entry.name.replace(/\.md$/, "");
3043
- const description = extractDescription(join14(agentsDir, entry.name));
3646
+ const description = extractDescription(join17(agentsDir, entry.name));
3044
3647
  items.push({ name, description });
3045
3648
  }
3046
3649
  }
@@ -3048,7 +3651,7 @@ function discoverAgents(agentsDir) {
3048
3651
  }
3049
3652
  function extractDescription(filePath) {
3050
3653
  try {
3051
- const content = readFileSync2(filePath, "utf-8");
3654
+ const content = readFileSync5(filePath, "utf-8");
3052
3655
  const lines = content.split("\n");
3053
3656
  let pastHeading = false;
3054
3657
  for (const line of lines) {
@@ -3227,8 +3830,8 @@ async function configResetCommand(options) {
3227
3830
  }
3228
3831
 
3229
3832
  // 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";
3833
+ import { resolve as resolve11, join as join18 } from "path";
3834
+ import { existsSync as existsSync14, readdirSync as readdirSync5, statSync, chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
3232
3835
  import { execSync } from "child_process";
3233
3836
  import * as p12 from "@clack/prompts";
3234
3837
  import chalk13 from "chalk";
@@ -3329,7 +3932,7 @@ function checkManifestFiles(targetDir, manifest) {
3329
3932
  let existCount = 0;
3330
3933
  const missing = [];
3331
3934
  for (const relativePath of Object.keys(manifest.files)) {
3332
- if (fileExists(join15(targetDir, relativePath))) {
3935
+ if (fileExists(join18(targetDir, relativePath))) {
3333
3936
  existCount++;
3334
3937
  } else {
3335
3938
  missing.push(relativePath);
@@ -3349,15 +3952,15 @@ function checkManifestFiles(targetDir, manifest) {
3349
3952
  };
3350
3953
  }
3351
3954
  function checkScriptPermissions(targetDir, manifest) {
3352
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3955
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
3353
3956
  const shFiles = [];
3354
- if (pluginDir && existsSync12(pluginDir)) {
3355
- const scriptsDir = join15(pluginDir, "scripts");
3356
- if (existsSync12(scriptsDir)) {
3357
- const entries = readdirSync4(scriptsDir);
3957
+ if (pluginDir && existsSync14(pluginDir)) {
3958
+ const scriptsDir = join18(pluginDir, "scripts");
3959
+ if (existsSync14(scriptsDir)) {
3960
+ const entries = readdirSync5(scriptsDir);
3358
3961
  for (const entry of entries) {
3359
3962
  if (entry.endsWith(".sh")) {
3360
- shFiles.push(join15(scriptsDir, entry));
3963
+ shFiles.push(join18(scriptsDir, entry));
3361
3964
  }
3362
3965
  }
3363
3966
  }
@@ -3420,28 +4023,28 @@ function checkPluginRegistered(targetDir) {
3420
4023
  };
3421
4024
  }
3422
4025
  function checkHookScripts(targetDir, manifest) {
3423
- const pluginDir = manifest.pluginPath ? join15(targetDir, manifest.pluginPath) : null;
3424
- if (!pluginDir || !existsSync12(pluginDir)) {
4026
+ const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
4027
+ if (!pluginDir || !existsSync14(pluginDir)) {
3425
4028
  return {
3426
4029
  name: "hooks",
3427
4030
  status: "pass",
3428
4031
  message: "No hooks directory to check"
3429
4032
  };
3430
4033
  }
3431
- const hooksDir = join15(pluginDir, "hooks");
3432
- if (!existsSync12(hooksDir)) {
4034
+ const hooksDir = join18(pluginDir, "hooks");
4035
+ if (!existsSync14(hooksDir)) {
3433
4036
  return {
3434
4037
  name: "hooks",
3435
4038
  status: "pass",
3436
4039
  message: "No hooks directory found"
3437
4040
  };
3438
4041
  }
3439
- const hookFiles = readdirSync4(hooksDir).filter((f) => f.endsWith(".json"));
4042
+ const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
3440
4043
  let total = 0;
3441
4044
  let valid = 0;
3442
4045
  for (const hookFile of hookFiles) {
3443
4046
  try {
3444
- const hookContent = JSON.parse(readFile(join15(hooksDir, hookFile)));
4047
+ const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
3445
4048
  const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
3446
4049
  for (const hook of hooks) {
3447
4050
  if (hook.command) {
@@ -3455,8 +4058,8 @@ function checkHookScripts(targetDir, manifest) {
3455
4058
  /\$\{[A-Z_]+\}\//g,
3456
4059
  ""
3457
4060
  );
3458
- const scriptPath = join15(pluginDir, resolved);
3459
- if (existsSync12(scriptPath)) {
4061
+ const scriptPath = join18(pluginDir, resolved);
4062
+ if (existsSync14(scriptPath)) {
3460
4063
  valid++;
3461
4064
  }
3462
4065
  } else {
@@ -3480,7 +4083,7 @@ function checkHookScripts(targetDir, manifest) {
3480
4083
  };
3481
4084
  }
3482
4085
  function checkQualityScripts(targetDir) {
3483
- const pkgPath = join15(targetDir, "package.json");
4086
+ const pkgPath = join18(targetDir, "package.json");
3484
4087
  if (!fileExists(pkgPath)) {
3485
4088
  return { name: "quality", status: "warn", message: "No package.json found" };
3486
4089
  }
@@ -3506,8 +4109,8 @@ function checkQualityScripts(targetDir) {
3506
4109
  }
3507
4110
  }
3508
4111
  function checkThoughtsDir(targetDir) {
3509
- const thoughtsDir = join15(targetDir, "thoughts");
3510
- if (existsSync12(thoughtsDir)) {
4112
+ const thoughtsDir = join18(targetDir, "thoughts");
4113
+ if (existsSync14(thoughtsDir)) {
3511
4114
  return {
3512
4115
  name: "thoughts",
3513
4116
  status: "pass",
@@ -3531,15 +4134,15 @@ function checkThoughtsDir(targetDir) {
3531
4134
  "thoughts/archive/backlog"
3532
4135
  ];
3533
4136
  for (const dir of dirs) {
3534
- mkdirSync(join15(targetDir, dir), { recursive: true });
4137
+ mkdirSync3(join18(targetDir, dir), { recursive: true });
3535
4138
  }
3536
4139
  }
3537
4140
  };
3538
4141
  }
3539
4142
  function checkEslint(targetDir) {
3540
4143
  try {
3541
- const eslintLocal = join15(targetDir, "node_modules", ".bin", "eslint");
3542
- if (existsSync12(eslintLocal)) {
4144
+ const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
4145
+ if (existsSync14(eslintLocal)) {
3543
4146
  return { name: "eslint", status: "pass", message: "eslint is available" };
3544
4147
  }
3545
4148
  execSync("which eslint", { stdio: "pipe" });
@@ -3554,8 +4157,8 @@ function checkEslint(targetDir) {
3554
4157
  }
3555
4158
 
3556
4159
  // 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";
4160
+ import { existsSync as existsSync15, rmSync as rmSync2, readdirSync as readdirSync6 } from "fs";
4161
+ import { resolve as resolve12, join as join19, dirname as dirname8 } from "path";
3559
4162
  import * as p13 from "@clack/prompts";
3560
4163
  import chalk14 from "chalk";
3561
4164
  var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
@@ -3573,12 +4176,12 @@ async function uninstallCommand(options) {
3573
4176
  return;
3574
4177
  }
3575
4178
  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"));
4179
+ const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
4180
+ const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
4181
+ const hasPlugin = manifest.installMode === "plugin" && existsSync15(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
4182
+ const hasThoughts = existsSync15(join19(targetDir, "thoughts"));
4183
+ const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
4184
+ const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
3582
4185
  p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
3583
4186
  p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
3584
4187
  p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
@@ -3664,18 +4267,18 @@ async function uninstallCommand(options) {
3664
4267
  }
3665
4268
  if (hasPlugin) {
3666
4269
  try {
3667
- rmSync(join16(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
4270
+ rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
3668
4271
  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 });
4272
+ const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
4273
+ if (existsSync15(marketplaceDescDir)) {
4274
+ rmSync2(marketplaceDescDir, { recursive: true, force: true });
3672
4275
  removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
3673
4276
  }
3674
- const pluginsDir = join16(targetDir, PLUGIN_DIR);
3675
- if (existsSync13(pluginsDir)) {
4277
+ const pluginsDir = join19(targetDir, PLUGIN_DIR);
4278
+ if (existsSync15(pluginsDir)) {
3676
4279
  const remaining = readdirSafe(pluginsDir);
3677
4280
  if (remaining.length === 0) {
3678
- rmSync(pluginsDir, { recursive: true, force: true });
4281
+ rmSync2(pluginsDir, { recursive: true, force: true });
3679
4282
  removed.push(`${PLUGIN_DIR}/ (empty)`);
3680
4283
  }
3681
4284
  }
@@ -3688,11 +4291,11 @@ async function uninstallCommand(options) {
3688
4291
  if (file.startsWith("thoughts/")) continue;
3689
4292
  if (file.startsWith(PLUGIN_DIR + "/")) continue;
3690
4293
  try {
3691
- const filePath = join16(targetDir, file);
3692
- if (existsSync13(filePath)) {
3693
- rmSync(filePath, { force: true });
4294
+ const filePath = join19(targetDir, file);
4295
+ if (existsSync15(filePath)) {
4296
+ rmSync2(filePath, { force: true });
3694
4297
  removed.push(file);
3695
- cleanEmptyParents(targetDir, dirname6(file));
4298
+ cleanEmptyParents(targetDir, dirname8(file));
3696
4299
  }
3697
4300
  } catch (err) {
3698
4301
  errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3701,7 +4304,7 @@ async function uninstallCommand(options) {
3701
4304
  if (hasClaudeMd) {
3702
4305
  if (removeClaudeMd) {
3703
4306
  try {
3704
- rmSync(join16(targetDir, "CLAUDE.md"), { force: true });
4307
+ rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
3705
4308
  removed.push("CLAUDE.md");
3706
4309
  } catch (err) {
3707
4310
  errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3713,7 +4316,7 @@ async function uninstallCommand(options) {
3713
4316
  if (hasAgentsMd) {
3714
4317
  if (removeAgentsMd) {
3715
4318
  try {
3716
- rmSync(join16(targetDir, "AGENTS.md"), { force: true });
4319
+ rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
3717
4320
  removed.push("AGENTS.md");
3718
4321
  } catch (err) {
3719
4322
  errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
@@ -3725,7 +4328,7 @@ async function uninstallCommand(options) {
3725
4328
  if (hasThoughts) {
3726
4329
  if (removeThoughts) {
3727
4330
  try {
3728
- rmSync(join16(targetDir, "thoughts"), { recursive: true, force: true });
4331
+ rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
3729
4332
  removed.push("thoughts/");
3730
4333
  } catch (err) {
3731
4334
  errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
@@ -3735,9 +4338,9 @@ async function uninstallCommand(options) {
3735
4338
  }
3736
4339
  }
3737
4340
  try {
3738
- const manifestDir = join16(targetDir, MANIFEST_DIR);
3739
- if (existsSync13(manifestDir)) {
3740
- rmSync(manifestDir, { recursive: true, force: true });
4341
+ const manifestDir = join19(targetDir, MANIFEST_DIR);
4342
+ if (existsSync15(manifestDir)) {
4343
+ rmSync2(manifestDir, { recursive: true, force: true });
3741
4344
  removed.push(`${MANIFEST_DIR}/`);
3742
4345
  }
3743
4346
  } catch (err) {
@@ -3782,33 +4385,59 @@ async function uninstallCommand(options) {
3782
4385
  }
3783
4386
  function cleanEmptyParents(targetDir, relDir) {
3784
4387
  if (!relDir || relDir === ".") return;
3785
- const absDir = join16(targetDir, relDir);
3786
- if (!existsSync13(absDir)) return;
4388
+ const absDir = join19(targetDir, relDir);
4389
+ if (!existsSync15(absDir)) return;
3787
4390
  const entries = readdirSafe(absDir);
3788
4391
  if (entries.length === 0) {
3789
4392
  try {
3790
- rmSync(absDir, { recursive: true, force: true });
3791
- cleanEmptyParents(targetDir, dirname6(relDir));
4393
+ rmSync2(absDir, { recursive: true, force: true });
4394
+ cleanEmptyParents(targetDir, dirname8(relDir));
3792
4395
  } catch {
3793
4396
  }
3794
4397
  }
3795
4398
  }
3796
4399
  function readdirSafe(dir) {
3797
4400
  try {
3798
- return readdirSync5(dir);
4401
+ return readdirSync6(dir);
3799
4402
  } catch {
3800
4403
  return [];
3801
4404
  }
3802
4405
  }
3803
4406
 
3804
4407
  // 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";
4408
+ import { resolve as resolve13, join as join20, dirname as dirname9 } from "path";
4409
+ import { existsSync as existsSync16, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
3807
4410
  import * as p14 from "@clack/prompts";
3808
4411
  import chalk15 from "chalk";
4412
+ function isFileBasedAddon(addonName) {
4413
+ return addonName !== "orchestration";
4414
+ }
3809
4415
  async function addonCommand(action, addonName, options) {
3810
4416
  const targetDir = resolve13(options.path || ".");
3811
4417
  p14.intro(introTitle(`Addon ${action}`));
4418
+ const validAddons = Object.keys(ADDONS);
4419
+ if (!validAddons.includes(addonName)) {
4420
+ p14.cancel(`Unknown addon: ${addonName}
4421
+
4422
+ Valid addons: ${validAddons.join(", ")}`);
4423
+ process.exit(1);
4424
+ }
4425
+ const typedName = addonName;
4426
+ const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
4427
+ if (action === "add" || action === "remove") {
4428
+ const canonical = action === "add" ? "enable" : "disable";
4429
+ p14.log.warn(
4430
+ `"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
4431
+ );
4432
+ }
4433
+ if (isFileBasedAddon(typedName)) {
4434
+ if (canonicalAction === "add") {
4435
+ await addFileBasedAddon(targetDir, typedName, options);
4436
+ } else {
4437
+ await removeFileBasedAddon(targetDir, typedName, options);
4438
+ }
4439
+ return;
4440
+ }
3812
4441
  const manifest = readManifest(targetDir);
3813
4442
  if (!manifest) {
3814
4443
  p14.log.warn("No devtronic installation found.");
@@ -3822,16 +4451,9 @@ async function addonCommand(action, addonName, options) {
3822
4451
  p14.outro("");
3823
4452
  return;
3824
4453
  }
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];
4454
+ const addon = ADDONS[typedName];
3833
4455
  const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
3834
- if (action === "add") {
4456
+ if (canonicalAction === "add") {
3835
4457
  await addAddon(targetDir, manifest, addon.name, currentAddons);
3836
4458
  } else {
3837
4459
  await removeAddon(targetDir, manifest, addon.name, currentAddons);
@@ -3845,7 +4467,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3845
4467
  }
3846
4468
  const addon = ADDONS[addonName];
3847
4469
  const pluginRoot = manifest.pluginPath;
3848
- const skillsSourceDir = join17(TEMPLATES_DIR, "claude-code", ".claude", "skills");
4470
+ const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
3849
4471
  p14.note(
3850
4472
  [
3851
4473
  ` ${chalk15.dim("Name:")} ${addon.label}`,
@@ -3864,18 +4486,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
3864
4486
  spinner8.start(`Adding ${addon.label}...`);
3865
4487
  const addedFiles = [];
3866
4488
  for (const skillDir of addon.skills) {
3867
- const sourceDir = join17(skillsSourceDir, skillDir);
3868
- if (!existsSync14(sourceDir)) {
4489
+ const sourceDir = join20(skillsSourceDir, skillDir);
4490
+ if (!existsSync16(sourceDir)) {
3869
4491
  spinner8.stop(`Template not found for skill: ${skillDir}`);
3870
4492
  p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
3871
4493
  continue;
3872
4494
  }
3873
4495
  const templateFiles = getAllFilesRecursive(sourceDir);
3874
4496
  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));
4497
+ const content = readFile(join20(sourceDir, file));
4498
+ const destRelPath = join20(pluginRoot, "skills", skillDir, file);
4499
+ const destAbsPath = join20(targetDir, destRelPath);
4500
+ ensureDir(dirname9(destAbsPath));
3879
4501
  writeFile(destAbsPath, content);
3880
4502
  manifest.files[destRelPath] = createManifestEntry(content);
3881
4503
  addedFiles.push(destRelPath);
@@ -3920,11 +4542,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3920
4542
  }
3921
4543
  const modifiedFiles = [];
3922
4544
  for (const skillDir of addon.skills) {
3923
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
4545
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
3924
4546
  for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
3925
4547
  if (!filePath.startsWith(skillRelBase)) continue;
3926
- const absPath = join17(targetDir, filePath);
3927
- if (!existsSync14(absPath)) continue;
4548
+ const absPath = join20(targetDir, filePath);
4549
+ if (!existsSync16(absPath)) continue;
3928
4550
  const current = calculateChecksum(readFile(absPath));
3929
4551
  if (current !== fileInfo.originalChecksum) {
3930
4552
  modifiedFiles.push(filePath);
@@ -3947,18 +4569,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3947
4569
  const spinner8 = p14.spinner();
3948
4570
  spinner8.start(`Removing ${addon.label}...`);
3949
4571
  for (const skillDir of addon.skills) {
3950
- const skillRelBase = join17(pluginRoot, "skills", skillDir);
3951
- const skillAbsDir = join17(targetDir, skillRelBase);
4572
+ const skillRelBase = join20(pluginRoot, "skills", skillDir);
4573
+ const skillAbsDir = join20(targetDir, skillRelBase);
3952
4574
  for (const filePath of Object.keys(manifest.files)) {
3953
4575
  if (filePath.startsWith(skillRelBase)) {
3954
- const absPath = join17(targetDir, filePath);
3955
- if (existsSync14(absPath)) unlinkSync2(absPath);
4576
+ const absPath = join20(targetDir, filePath);
4577
+ if (existsSync16(absPath)) unlinkSync3(absPath);
3956
4578
  delete manifest.files[filePath];
3957
4579
  }
3958
4580
  }
3959
- if (existsSync14(skillAbsDir)) {
4581
+ if (existsSync16(skillAbsDir)) {
3960
4582
  try {
3961
- rmSync2(skillAbsDir, { recursive: true });
4583
+ rmSync3(skillAbsDir, { recursive: true });
3962
4584
  } catch {
3963
4585
  }
3964
4586
  }
@@ -3975,32 +4597,196 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
3975
4597
  );
3976
4598
  p14.outro("Done. Restart Claude Code to apply the changes.");
3977
4599
  }
4600
+ async function addFileBasedAddon(targetDir, addonName, _options) {
4601
+ const addon = ADDONS[addonName];
4602
+ const config = readAddonConfig(targetDir);
4603
+ if (config.installed[addonName]) {
4604
+ p14.log.warn(`Addon "${addonName}" is already installed.`);
4605
+ p14.outro("");
4606
+ return;
4607
+ }
4608
+ p14.note(
4609
+ [
4610
+ ` ${chalk15.dim("Name:")} ${addon.label}`,
4611
+ ` ${chalk15.dim("Description:")} ${addon.description}`,
4612
+ ` ${chalk15.dim("Skills:")} ${addon.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`,
4613
+ addon.agents.length ? ` ${chalk15.dim("Agents:")} ${addon.agents.map((a) => chalk15.cyan(a)).join(", ")}` : ` ${chalk15.dim("Agents:")} ${chalk15.dim("\u2014")}`
4614
+ ].join("\n"),
4615
+ "Adding addon"
4616
+ );
4617
+ const confirmed = await p14.confirm({ message: "Add this addon?" });
4618
+ if (p14.isCancel(confirmed) || !confirmed) {
4619
+ p14.cancel("Addon installation cancelled.");
4620
+ process.exit(0);
4621
+ }
4622
+ const spinner8 = p14.spinner();
4623
+ spinner8.start(`Adding ${addon.label}...`);
4624
+ const addonSourceDir = getAddonSourceDir(addonName);
4625
+ const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
4626
+ const addonManifest = JSON.parse(
4627
+ readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
4628
+ );
4629
+ const fileList = [
4630
+ ...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
4631
+ ...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
4632
+ ...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
4633
+ ];
4634
+ writeAddonToConfig(targetDir, addonName, {
4635
+ version: addonManifest.version,
4636
+ files: fileList,
4637
+ checksums: result.checksums ?? {}
4638
+ });
4639
+ spinner8.stop(`${symbols.pass} ${addon.label} added (${result.written} files written)`);
4640
+ p14.note(
4641
+ addon.skills.map((s) => ` ${chalk15.cyan(`/${s}`)}`).join("\n"),
4642
+ "New skills available"
4643
+ );
4644
+ p14.outro("Done. Skills are now available in your agent directories.");
4645
+ }
4646
+ async function removeFileBasedAddon(targetDir, addonName, _options) {
4647
+ const addon = ADDONS[addonName];
4648
+ const config = readAddonConfig(targetDir);
4649
+ if (!config.installed[addonName]) {
4650
+ p14.log.warn(`Addon "${addonName}" is not currently installed.`);
4651
+ p14.outro("");
4652
+ return;
4653
+ }
4654
+ const modified = detectModifiedAddonFiles(targetDir, addonName);
4655
+ if (modified.length > 0) {
4656
+ p14.log.warn("The following files have been customized:");
4657
+ for (const f of modified) {
4658
+ p14.log.message(` ${chalk15.yellow(f)}`);
4659
+ }
4660
+ const confirm9 = await p14.confirm({
4661
+ message: "Remove them anyway? (customizations will be lost)"
4662
+ });
4663
+ if (p14.isCancel(confirm9) || !confirm9) {
4664
+ p14.cancel("Addon removal cancelled.");
4665
+ process.exit(0);
4666
+ }
4667
+ }
4668
+ const spinner8 = p14.spinner();
4669
+ spinner8.start(`Removing ${addon.label}...`);
4670
+ removeAddonFiles(targetDir, addonName, config.agents);
4671
+ removeAddonFromConfig(targetDir, addonName);
4672
+ spinner8.stop(`${symbols.pass} ${addon.label} removed`);
4673
+ p14.note(
4674
+ addon.skills.map((s) => ` ${chalk15.dim(`/${s}`)}`).join("\n"),
4675
+ "Skills removed"
4676
+ );
4677
+ p14.outro("Done.");
4678
+ }
4679
+ function getAddonListInfo(targetDir) {
4680
+ const config = readAddonConfig(targetDir);
4681
+ return getAvailableAddons().map((addon) => ({
4682
+ name: addon.name,
4683
+ label: addon.label,
4684
+ description: addon.description,
4685
+ installed: !!config.installed[addon.name],
4686
+ agents: config.installed[addon.name] ? config.agents : void 0
4687
+ }));
4688
+ }
4689
+ async function addonListCommand(options) {
4690
+ const targetDir = resolve13(options.path || ".");
4691
+ p14.intro(introTitle("Addon List"));
4692
+ const items = getAddonListInfo(targetDir);
4693
+ const lines = items.map((item) => {
4694
+ const status = item.installed ? chalk15.green("\u2713 installed") : chalk15.dim("available");
4695
+ const agents = item.agents ? chalk15.dim(` \u2192 ${item.agents.join(", ")}`) : "";
4696
+ return ` ${chalk15.bold(item.name)} ${status}${agents}
4697
+ ${chalk15.dim(item.description)}`;
4698
+ });
4699
+ p14.note(lines.join("\n\n"), "Addons");
4700
+ p14.outro(`Use ${chalk15.cyan("devtronic addon add <name>")} to install.`);
4701
+ }
4702
+ async function addonSyncCommand(options) {
4703
+ const targetDir = resolve13(options.path || ".");
4704
+ p14.intro(introTitle("Addon Sync"));
4705
+ const config = readAddonConfig(targetDir);
4706
+ const installedNames = Object.keys(config.installed);
4707
+ if (installedNames.length === 0) {
4708
+ p14.log.info("No addons installed. Nothing to sync.");
4709
+ p14.outro("");
4710
+ return;
4711
+ }
4712
+ const spinner8 = p14.spinner();
4713
+ spinner8.start("Syncing addon files...");
4714
+ let totalWritten = 0;
4715
+ let totalConflicts = [];
4716
+ for (const name of installedNames) {
4717
+ const addonSourceDir = getAddonSourceDir(name);
4718
+ const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
4719
+ totalWritten += result.written + (result.updated ?? 0);
4720
+ totalConflicts = totalConflicts.concat(result.conflicts);
4721
+ }
4722
+ spinner8.stop(`${symbols.pass} Sync complete (${totalWritten} files updated)`);
4723
+ if (totalConflicts.length > 0) {
4724
+ p14.log.warn("Customized files were preserved:");
4725
+ for (const f of totalConflicts) {
4726
+ p14.log.message(` ${chalk15.yellow(f)}`);
4727
+ }
4728
+ }
4729
+ p14.outro("Done.");
4730
+ }
3978
4731
  function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
3979
4732
  const cliVersion2 = getCliVersion();
3980
4733
  const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
3981
- const pluginJsonRelPath = join17(pluginRoot, ".claude-plugin", "plugin.json");
3982
- writeFile(join17(targetDir, pluginJsonRelPath), pluginJsonContent);
4734
+ const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
4735
+ writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
3983
4736
  manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
3984
4737
  const marketplaceContent = generateMarketplaceJson(addonSkillCount);
3985
- const marketplaceRelPath = join17(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
3986
- writeFile(join17(targetDir, marketplaceRelPath), marketplaceContent);
4738
+ const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
4739
+ writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
3987
4740
  manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
3988
4741
  }
3989
4742
 
4743
+ // src/commands/mode.ts
4744
+ import { resolve as resolve14, join as join21 } from "path";
4745
+ import { existsSync as existsSync17 } from "fs";
4746
+ import * as p15 from "@clack/prompts";
4747
+ import chalk16 from "chalk";
4748
+ async function modeCommand(action, options) {
4749
+ const targetDir = resolve14(options.path || ".");
4750
+ p15.intro(introTitle("Mode"));
4751
+ if (action === "show") {
4752
+ const configPath = join21(targetDir, ".claude", "devtronic.json");
4753
+ const hasConfig = existsSync17(configPath);
4754
+ const config = readAddonConfig(targetDir);
4755
+ const currentMode = config.mode;
4756
+ const isDefault = !hasConfig || currentMode === void 0;
4757
+ const displayMode = currentMode ?? "hitl";
4758
+ p15.log.info(
4759
+ `Mode: ${chalk16.cyan(displayMode)}${isDefault ? chalk16.dim(" (default)") : ""}`
4760
+ );
4761
+ p15.log.info(`Config: ${chalk16.dim(".claude/devtronic.json")}`);
4762
+ p15.outro("");
4763
+ return;
4764
+ }
4765
+ const newMode = action;
4766
+ writeMode(targetDir, newMode);
4767
+ const description = newMode === "afk" ? "Fully autonomous \u2014 no human gates" : "Human-in-the-loop \u2014 pauses for approval at key stages";
4768
+ p15.log.success(`Mode set to ${chalk16.cyan(newMode)}`);
4769
+ p15.log.info(chalk16.dim(description));
4770
+ p15.outro(
4771
+ `Change back anytime with ${chalk16.cyan(`npx devtronic mode ${newMode === "afk" ? "hitl" : "afk"}`)}`
4772
+ );
4773
+ }
4774
+
3990
4775
  // src/index.ts
3991
4776
  var cliVersion = getCliVersion();
3992
4777
  var program = new Command();
3993
4778
  program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
3994
4779
  showLogo();
3995
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}`));
4780
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
3996
4781
  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")}`);
4782
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic init")} ${chalk17.dim("[path]")} ${chalk17.dim("Initialize in a project")}`);
4783
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic info")} ${chalk17.dim("Version & config summary")}`);
4784
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic doctor")} ${chalk17.dim("Health diagnostics")}`);
4785
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic status")} ${chalk17.dim("File status overview")}`);
4786
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic mode")} ${chalk17.dim("<afk|hitl|show>")} ${chalk17.dim("Get or set execution mode")}`);
4001
4787
  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")}`);
4788
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help")} ${chalk17.dim("Show all commands")}`);
4789
+ console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help --all")} ${chalk17.dim("Full reference with all options")}`);
4004
4790
  console.log();
4005
4791
  });
4006
4792
  program.command("init").description("Initialize devtronic in your project").argument("[path]", "Target directory (default: current directory)").option("--ide <ides>", "Comma-separated list of IDEs to configure").option("-y, --yes", "Skip prompts and use defaults").option("--preview", "Show what would be generated without making changes").option(
@@ -4059,13 +4845,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
4059
4845
  program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
4060
4846
  await uninstallCommand({ path: options.path });
4061
4847
  });
4848
+ program.command("mode").description("Set or show the execution mode (hitl or afk)").argument("<mode>", "Mode: afk, hitl, or show").option("--path <path>", "Target directory (default: current directory)").action(async (mode, options) => {
4849
+ if (!["afk", "hitl", "show"].includes(mode)) {
4850
+ console.error(`Invalid mode: "${mode}". Valid values: afk, hitl, show`);
4851
+ process.exit(1);
4852
+ }
4853
+ await modeCommand(mode, { path: options.path });
4854
+ });
4062
4855
  program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
4063
4856
  if (!options.all) {
4064
4857
  program.outputHelp();
4065
4858
  return;
4066
4859
  }
4067
4860
  showLogo();
4068
- console.log(chalk16.dim(` Agentic development toolkit v${cliVersion}
4861
+ console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
4069
4862
  `));
4070
4863
  const sections = [
4071
4864
  {
@@ -4089,7 +4882,7 @@ program.command("help").description("Show help (use --all for full reference)").
4089
4882
  {
4090
4883
  title: "Addons",
4091
4884
  usage: "addon add <name>",
4092
- desc: "Add an optional skill pack (e.g., orchestration)",
4885
+ desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
4093
4886
  opts: ["--path <path> Target directory"]
4094
4887
  },
4095
4888
  {
@@ -4098,6 +4891,30 @@ program.command("help").description("Show help (use --all for full reference)").
4098
4891
  desc: "Remove an addon skill pack",
4099
4892
  opts: ["--path <path> Target directory"]
4100
4893
  },
4894
+ {
4895
+ title: "",
4896
+ usage: "addon list",
4897
+ desc: "List available and installed addons",
4898
+ opts: ["--path <path> Target directory"]
4899
+ },
4900
+ {
4901
+ title: "",
4902
+ usage: "addon sync",
4903
+ desc: "Regenerate addon files for current agent configuration",
4904
+ opts: ["--path <path> Target directory"]
4905
+ },
4906
+ {
4907
+ title: "",
4908
+ usage: "addon enable <name>",
4909
+ desc: "Enable an addon (copies files to .claude/)",
4910
+ opts: ["--path <path> Target directory"]
4911
+ },
4912
+ {
4913
+ title: "",
4914
+ usage: "addon disable <name>",
4915
+ desc: "Disable an addon (removes files from .claude/)",
4916
+ opts: ["--path <path> Target directory"]
4917
+ },
4101
4918
  {
4102
4919
  title: "Maintenance",
4103
4920
  usage: "update",
@@ -4151,6 +4968,12 @@ program.command("help").description("Show help (use --all for full reference)").
4151
4968
  usage: "presets",
4152
4969
  desc: "List available configuration presets"
4153
4970
  },
4971
+ {
4972
+ title: "Mode",
4973
+ usage: "mode <afk|hitl|show>",
4974
+ desc: "Set or show the persistent execution mode for /auto-devtronic",
4975
+ opts: ["--path <path> Target directory"]
4976
+ },
4154
4977
  {
4155
4978
  title: "Diagnostics",
4156
4979
  usage: "status",
@@ -4186,14 +5009,14 @@ program.command("help").description("Show help (use --all for full reference)").
4186
5009
  ];
4187
5010
  for (const section of sections) {
4188
5011
  if (section.title) {
4189
- console.log(` ${chalk16.bold.underline(section.title)}
5012
+ console.log(` ${chalk17.bold.underline(section.title)}
4190
5013
  `);
4191
5014
  }
4192
- console.log(` ${chalk16.white("devtronic " + section.usage)}`);
4193
- console.log(` ${chalk16.dim(section.desc)}`);
5015
+ console.log(` ${chalk17.white("devtronic " + section.usage)}`);
5016
+ console.log(` ${chalk17.dim(section.desc)}`);
4194
5017
  if (section.opts) {
4195
5018
  for (const opt of section.opts) {
4196
- console.log(` ${chalk16.yellow(opt)}`);
5019
+ console.log(` ${chalk17.yellow(opt)}`);
4197
5020
  }
4198
5021
  }
4199
5022
  console.log();
@@ -4206,21 +5029,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
4206
5029
  addonCmd.command("remove").description("Remove an addon from the devtronic plugin").argument("<name>", "Addon name (e.g., orchestration)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
4207
5030
  await addonCommand("remove", name, { path: options.path });
4208
5031
  });
5032
+ addonCmd.command("list").description("List available and installed addons").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5033
+ await addonListCommand({ path: options.path });
5034
+ });
5035
+ addonCmd.command("enable").description("Enable an addon (copies files to .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
5036
+ await addonCommand("enable", name, { path: options.path });
5037
+ });
5038
+ addonCmd.command("disable").description("Disable an addon (removes files from .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
5039
+ await addonCommand("disable", name, { path: options.path });
5040
+ });
5041
+ addonCmd.command("sync").description("Regenerate addon files for current agent configuration").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
5042
+ await addonSyncCommand({ path: options.path });
5043
+ });
4209
5044
  program.command("presets").description("List available configuration presets").action(() => {
4210
- p15.intro(introTitle("Presets"));
5045
+ p16.intro(introTitle("Presets"));
4211
5046
  const lines = Object.entries(PRESETS).map(([name, preset]) => {
4212
- const parts = [` ${chalk16.bold(name)}`];
5047
+ const parts = [` ${chalk17.bold(name)}`];
4213
5048
  parts.push(` ${preset.description}`);
4214
5049
  if (preset.config.architecture) {
4215
- parts.push(` Architecture: ${chalk16.cyan(preset.config.architecture)}`);
5050
+ parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
4216
5051
  }
4217
5052
  if (preset.config.layers) {
4218
- parts.push(` Layers: ${chalk16.cyan(preset.config.layers.join(", "))}`);
5053
+ parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
4219
5054
  }
4220
5055
  return parts.join("\n");
4221
5056
  });
4222
- p15.note(lines.join("\n\n"), "Available Presets");
4223
- p15.outro(`Usage: ${chalk16.cyan("npx devtronic init --preset <name>")}`);
5057
+ p16.note(lines.join("\n\n"), "Available Presets");
5058
+ p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
4224
5059
  });
4225
5060
  program.parseAsync().catch((err) => {
4226
5061
  console.error(`