devtronic 1.2.4 → 1.2.5

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 (3) hide show
  1. package/README.md +7 -7
  2. package/dist/index.js +55 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,8 +20,8 @@ The CLI analyzes your project (framework, architecture, stack) and generates per
20
20
  | `doctor [--fix]` | Run health diagnostics |
21
21
  | `list [skills\|agents]` | List installed skills and agents |
22
22
  | `config` | View or manage project configuration |
23
- | `addon enable <name>` | Add an addon skill pack |
24
- | `addon disable <name>` | Remove an addon skill pack |
23
+ | `addon add <name>` | Add an addon skill pack |
24
+ | `addon remove <name>` | Remove an addon skill pack |
25
25
  | `addon list` | List available and installed addons |
26
26
  | `addon sync` | Regenerate addon files for current agents |
27
27
  | `add <ide>` | Add another IDE |
@@ -45,11 +45,11 @@ The CLI analyzes your project (framework, architecture, stack) and generates per
45
45
  Three optional addon packs extend the core toolkit. Select them during `init` or manage them at any time:
46
46
 
47
47
  ```bash
48
- npx devtronic addon list # See available addons and status
49
- npx devtronic addon enable orchestration # Install
50
- npx devtronic addon enable design-best-practices
51
- npx devtronic addon enable auto-devtronic
52
- npx devtronic addon disable <name> # Uninstall
48
+ npx devtronic addon list # See available addons and status
49
+ npx devtronic addon add orchestration # Install
50
+ npx devtronic addon add design-best-practices
51
+ npx devtronic addon add auto-devtronic
52
+ npx devtronic addon remove <name> # Uninstall
53
53
  ```
54
54
 
55
55
  | Addon | Skills | Description |
package/dist/index.js CHANGED
@@ -1469,7 +1469,7 @@ Valid addons: ${validAddons.join(", ")}`);
1469
1469
  p3.log.warn(`Template not found: ${templateName}`);
1470
1470
  continue;
1471
1471
  }
1472
- const resolution = conflictResolutions.get(ide) || "overwrite";
1472
+ const resolution = conflictResolutions.get(ide) || "replace";
1473
1473
  const files = getAllFilesRecursive(templateDir);
1474
1474
  const dynamicFiles = DYNAMIC_RULE_FILES[ide] || [];
1475
1475
  for (const file of files) {
@@ -1507,7 +1507,7 @@ Valid addons: ${validAddons.join(", ")}`);
1507
1507
  const rulePath = dynamicFiles[0];
1508
1508
  if (rulePath) {
1509
1509
  const destPath = join7(targetDir, rulePath);
1510
- const resolution2 = conflictResolutions.get(ide) || "overwrite";
1510
+ const resolution2 = conflictResolutions.get(ide) || "replace";
1511
1511
  if (fileExists(destPath) && resolution2 === "keep") {
1512
1512
  skippedFiles.push(rulePath);
1513
1513
  } else if (fileExists(destPath) && resolution2 === "merge") {
@@ -1795,14 +1795,18 @@ function readAddonConfig(targetDir) {
1795
1795
  if (!existsSync7(configPath)) {
1796
1796
  return { agents: ["claude"], installed: {} };
1797
1797
  }
1798
- const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1799
- const data = raw.addons ?? raw;
1800
- return {
1801
- version: 1,
1802
- mode: data.mode,
1803
- agents: data.agents ?? ["claude"],
1804
- installed: data.installed ?? {}
1805
- };
1798
+ try {
1799
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1800
+ const data = raw.addons ?? raw;
1801
+ return {
1802
+ version: 1,
1803
+ mode: data.mode,
1804
+ agents: data.agents ?? ["claude"],
1805
+ installed: data.installed ?? {}
1806
+ };
1807
+ } catch {
1808
+ return { agents: ["claude"], installed: {} };
1809
+ }
1806
1810
  }
1807
1811
  function writeAddonConfig(targetDir, config) {
1808
1812
  const configPath = getConfigPath(targetDir);
@@ -1990,9 +1994,9 @@ function generateAddonFiles(projectDir, addonSourceDir, agents) {
1990
1994
  const existing = readFileSync4(destPath, "utf-8");
1991
1995
  if (existing === content) {
1992
1996
  result.skipped++;
1993
- continue;
1997
+ } else {
1998
+ result.conflicts.push(relPath);
1994
1999
  }
1995
- result.skipped++;
1996
2000
  continue;
1997
2001
  }
1998
2002
  ensureDir2(dirname5(destPath));
@@ -2120,8 +2124,10 @@ function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
2120
2124
  }
2121
2125
  }
2122
2126
  }
2123
- const noticePath = join10(projectDir, "NOTICE.md");
2124
- if (existsSync8(noticePath)) unlinkSync(noticePath);
2127
+ if (manifest.attribution) {
2128
+ const noticePath = join10(projectDir, "NOTICE.md");
2129
+ if (existsSync8(noticePath)) unlinkSync(noticePath);
2130
+ }
2125
2131
  }
2126
2132
  function syncAddonFiles(projectDir, addonSourceDir, agents) {
2127
2133
  const fileMap = buildFileMap(addonSourceDir);
@@ -2276,18 +2282,22 @@ function syncAddonFiles(projectDir, addonSourceDir, agents) {
2276
2282
  }
2277
2283
  function detectModifiedAddonFiles(projectDir, addonName) {
2278
2284
  let installedChecksums = {};
2285
+ let agents = ["claude"];
2279
2286
  try {
2280
2287
  const config = readAddonConfig(projectDir);
2281
2288
  const installed = config.installed?.[addonName];
2282
2289
  if (!installed?.checksums) return [];
2283
2290
  installedChecksums = installed.checksums;
2291
+ agents = config.agents ?? ["claude"];
2284
2292
  } catch {
2285
2293
  return [];
2286
2294
  }
2287
2295
  const modified = [];
2288
- for (const agentDir of Object.values(AGENT_PATHS)) {
2296
+ for (const agent of agents) {
2297
+ const spec = RUNTIME_SPECS[agent];
2298
+ const baseDir = spec?.baseDir ?? AGENT_PATHS[agent] ?? `.${agent}`;
2289
2299
  for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
2290
- const absPath = join10(projectDir, agentDir, relPath);
2300
+ const absPath = join10(projectDir, baseDir, relPath);
2291
2301
  if (!existsSync8(absPath)) continue;
2292
2302
  const current = checksum(readFileSync4(absPath, "utf-8"));
2293
2303
  if (current !== originalHash) {
@@ -2629,6 +2639,7 @@ async function updateCommand(options) {
2629
2639
  syncSpinner.start("Updating enabled addon files...");
2630
2640
  let totalUpdated = 0;
2631
2641
  for (const name of enabledAddons) {
2642
+ if (name === "orchestration") continue;
2632
2643
  const addonSourceDir = getAddonSourceDir(name);
2633
2644
  const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
2634
2645
  totalUpdated += (result.updated ?? 0) + result.written;
@@ -4407,7 +4418,7 @@ function readdirSafe(dir) {
4407
4418
 
4408
4419
  // src/commands/addon.ts
4409
4420
  import { resolve as resolve14, join as join20, dirname as dirname10 } from "path";
4410
- import { existsSync as existsSync17, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
4421
+ import { existsSync as existsSync17, unlinkSync as unlinkSync3, rmSync as rmSync3 } from "fs";
4411
4422
  import * as p14 from "@clack/prompts";
4412
4423
  import chalk15 from "chalk";
4413
4424
  function isFileBasedAddon(addonName) {
@@ -4425,8 +4436,8 @@ Valid addons: ${validAddons.join(", ")}`);
4425
4436
  }
4426
4437
  const typedName = addonName;
4427
4438
  const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
4428
- if (action === "add" || action === "remove") {
4429
- const canonical = action === "add" ? "enable" : "disable";
4439
+ if (action === "enable" || action === "disable") {
4440
+ const canonical = action === "enable" ? "add" : "remove";
4430
4441
  p14.log.warn(
4431
4442
  `"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
4432
4443
  );
@@ -4624,16 +4635,14 @@ async function addFileBasedAddon(targetDir, addonName, _options) {
4624
4635
  spinner8.start(`Adding ${addon.label}...`);
4625
4636
  const addonSourceDir = getAddonSourceDir(addonName);
4626
4637
  const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
4627
- const addonManifest = JSON.parse(
4628
- readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
4629
- );
4638
+ const addonMeta = getAddonManifest(addonName);
4630
4639
  const fileList = [
4631
- ...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
4632
- ...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
4633
- ...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
4640
+ ...(addonMeta.files.skills ?? []).map((s) => `skills/${s}`),
4641
+ ...(addonMeta.files.agents ?? []).map((a) => `agents/${a}.md`),
4642
+ ...(addonMeta.files.rules ?? []).map((r) => `rules/${r}`)
4634
4643
  ];
4635
4644
  writeAddonToConfig(targetDir, addonName, {
4636
- version: addonManifest.version,
4645
+ version: addonMeta.version,
4637
4646
  files: fileList,
4638
4647
  checksums: result.checksums ?? {}
4639
4648
  });
@@ -4706,7 +4715,24 @@ async function addonSyncCommand(options) {
4706
4715
  const targetDir = resolve14(options.path || ".");
4707
4716
  p14.intro(introTitle("Addon Sync"));
4708
4717
  const config = readAddonConfig(targetDir);
4709
- const installedNames = Object.keys(config.installed);
4718
+ const manifest = readManifest(targetDir);
4719
+ const manifestAddons = manifest?.projectConfig?.enabledAddons ?? [];
4720
+ for (const name of manifestAddons) {
4721
+ if (!config.installed[name] && isFileBasedAddon(name)) {
4722
+ const addonMeta = getAddonManifest(name);
4723
+ const fileList = [
4724
+ ...(addonMeta.files.skills ?? []).map((s) => `skills/${s}`),
4725
+ ...(addonMeta.files.agents ?? []).map((a) => `agents/${a}.md`),
4726
+ ...(addonMeta.files.rules ?? []).map((r) => `rules/${r}`)
4727
+ ];
4728
+ writeAddonToConfig(targetDir, name, {
4729
+ version: addonMeta.version,
4730
+ files: fileList
4731
+ });
4732
+ }
4733
+ }
4734
+ const freshConfig = readAddonConfig(targetDir);
4735
+ const installedNames = Object.keys(freshConfig.installed);
4710
4736
  if (installedNames.length === 0) {
4711
4737
  p14.log.info("No addons installed. Nothing to sync.");
4712
4738
  p14.outro("");
@@ -4717,8 +4743,9 @@ async function addonSyncCommand(options) {
4717
4743
  let totalWritten = 0;
4718
4744
  let totalConflicts = [];
4719
4745
  for (const name of installedNames) {
4746
+ if (!isFileBasedAddon(name)) continue;
4720
4747
  const addonSourceDir = getAddonSourceDir(name);
4721
- const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
4748
+ const result = syncAddonFiles(targetDir, addonSourceDir, freshConfig.agents);
4722
4749
  totalWritten += result.written + (result.updated ?? 0);
4723
4750
  totalConflicts = totalConflicts.concat(result.conflicts);
4724
4751
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devtronic",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "AI-assisted development toolkit — skills, agents, quality gates, and rules for Claude Code, Cursor, Copilot, and Antigravity",
5
5
  "type": "module",
6
6
  "bin": {