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.
- package/README.md +7 -7
- package/dist/index.js +55 -28
- 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
|
|
24
|
-
| `addon
|
|
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
|
|
49
|
-
npx devtronic addon
|
|
50
|
-
npx devtronic addon
|
|
51
|
-
npx devtronic addon
|
|
52
|
-
npx devtronic addon
|
|
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) || "
|
|
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) || "
|
|
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
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 === "
|
|
4429
|
-
const canonical = action === "
|
|
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
|
|
4628
|
-
readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
|
|
4629
|
-
);
|
|
4638
|
+
const addonMeta = getAddonManifest(addonName);
|
|
4630
4639
|
const fileList = [
|
|
4631
|
-
...(
|
|
4632
|
-
...(
|
|
4633
|
-
...(
|
|
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:
|
|
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
|
|
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,
|
|
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