devtronic 1.2.6 → 1.3.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.
- package/README.md +2 -2
- package/dist/{chunk-WM7R52TC.js → chunk-YBT2XOOQ.js} +9 -12
- package/dist/index.js +301 -93
- package/dist/{plugin-KQQASBQX.js → plugin-XSNKI5VI.js} +5 -1
- package/package.json +1 -1
- package/templates/marketplace/auto-lint.sh +26 -0
- package/templates/marketplace/checkpoint.sh +39 -0
- package/templates/marketplace/hooks.json +75 -0
- package/templates/marketplace/stop-guard.sh +54 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The CLI analyzes your project (framework, architecture, stack) and generates per
|
|
|
36
36
|
- **AGENTS.md** — Universal AI context personalized to your stack
|
|
37
37
|
- **Architecture rules** — IDE-specific format (`.claude/rules/`, `.cursor/rules/`, etc.)
|
|
38
38
|
- **Skills** (20 core + 12 design + 9 addon) — Reusable workflows (`/brief`, `/spec`, `/create-plan`, `/summary`, `/audit`, `/devtronic-help`, etc.)
|
|
39
|
-
- **Agents** (15 +
|
|
39
|
+
- **Agents** (15 + 4 addon) — Specialized subagents (code-reviewer, quality-runner, etc.)
|
|
40
40
|
- **Hooks** (5) — Automated workflow (lint-on-save, checkpoint, etc.)
|
|
41
41
|
- **thoughts/** — Structured directory for AI working documents
|
|
42
42
|
|
|
@@ -55,7 +55,7 @@ npx devtronic addon remove <name> # Uninstall
|
|
|
55
55
|
| Addon | Skills | Description |
|
|
56
56
|
|-------|--------|-------------|
|
|
57
57
|
| `orchestration` | `briefing`, `recap`, `handoff` | Pre-planning alignment, session recaps, context rotation for multi-session work |
|
|
58
|
-
| `design-best-practices` | `design-init`, `design-
|
|
58
|
+
| `design-best-practices` | `design-init`, `design-critique`, `design-refine`, `design-tokens`, `design-harden` | Frontend design quality: typography, color, layout, accessibility, motion, UX writing |
|
|
59
59
|
| `auto-devtronic` | `auto-devtronic`, `validate-task-afk` | Autonomous engineering loop — takes a GitHub issue, runs spec → tests → plan → implement → PR, self-corrects via failing tests |
|
|
60
60
|
|
|
61
61
|
During `npx devtronic init` (Claude Code only), a multiselect prompt lets you enable any combination of addons upfront.
|
|
@@ -22,7 +22,7 @@ var ADDONS = {
|
|
|
22
22
|
label: "auto-devtronic \u2014 Autonomous Engineering Loop",
|
|
23
23
|
description: "Runs the full spec\u2192test\u2192plan\u2192execute\u2192PR pipeline autonomously. Self-corrects via failing tests. HITL and AFK modes.",
|
|
24
24
|
skills: ["auto-devtronic"],
|
|
25
|
-
agents: ["issue-parser", "failure-analyst", "quality-executor"]
|
|
25
|
+
agents: ["issue-parser", "failure-analyst", "quality-executor", "afk-task-validator"]
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
var PRESETS = {
|
|
@@ -622,6 +622,8 @@ function generateSkillsSection(enabledAddons) {
|
|
|
622
622
|
var PLUGIN_NAME = "devtronic";
|
|
623
623
|
var MARKETPLACE_NAME = "devtronic-local";
|
|
624
624
|
var PLUGIN_DIR = ".claude-plugins";
|
|
625
|
+
var GITHUB_MARKETPLACE_REPO = "r-bart/devtronic-plugin";
|
|
626
|
+
var GITHUB_MARKETPLACE_NAME = "devtronic";
|
|
625
627
|
var BASE_SKILL_COUNT = CORE_SKILLS.length;
|
|
626
628
|
var DESIGN_SKILL_COUNT = 12;
|
|
627
629
|
var BASE_AGENT_COUNT = 15;
|
|
@@ -631,18 +633,11 @@ function generatePluginJson(cliVersion, addonSkillCount = 0) {
|
|
|
631
633
|
return JSON.stringify(
|
|
632
634
|
{
|
|
633
635
|
name: PLUGIN_NAME,
|
|
636
|
+
description: `Agentic development toolkit \u2014 ${skillLabel}, ${BASE_AGENT_COUNT} agents, workflow hooks`,
|
|
634
637
|
version: cliVersion,
|
|
635
|
-
description: `devtronic \u2014 ${skillLabel}, ${BASE_AGENT_COUNT} agents, full workflow hooks`,
|
|
636
638
|
author: {
|
|
637
|
-
name: "r-bart"
|
|
638
|
-
|
|
639
|
-
},
|
|
640
|
-
repository: "https://github.com/r-bart/devtronic",
|
|
641
|
-
license: "MIT",
|
|
642
|
-
keywords: ["agentic", "architecture", "clean-architecture", "ddd", "workflow", "skills"],
|
|
643
|
-
skills: "./skills/",
|
|
644
|
-
agents: "./agents/",
|
|
645
|
-
hooks: "./hooks/hooks.json"
|
|
639
|
+
name: "r-bart"
|
|
640
|
+
}
|
|
646
641
|
},
|
|
647
642
|
null,
|
|
648
643
|
2
|
|
@@ -661,7 +656,7 @@ function generateMarketplaceJson(addonSkillCount = 0) {
|
|
|
661
656
|
plugins: [
|
|
662
657
|
{
|
|
663
658
|
name: PLUGIN_NAME,
|
|
664
|
-
source:
|
|
659
|
+
source: `./plugins/${PLUGIN_NAME}`,
|
|
665
660
|
description: `devtronic \u2014 ${skillLabel}, ${BASE_AGENT_COUNT} agents, full workflow hooks`
|
|
666
661
|
}
|
|
667
662
|
]
|
|
@@ -752,6 +747,8 @@ export {
|
|
|
752
747
|
PLUGIN_NAME,
|
|
753
748
|
MARKETPLACE_NAME,
|
|
754
749
|
PLUGIN_DIR,
|
|
750
|
+
GITHUB_MARKETPLACE_REPO,
|
|
751
|
+
GITHUB_MARKETPLACE_NAME,
|
|
755
752
|
BASE_SKILL_COUNT,
|
|
756
753
|
DESIGN_SKILL_COUNT,
|
|
757
754
|
BASE_AGENT_COUNT,
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
BASE_AGENT_COUNT,
|
|
5
5
|
BASE_SKILL_COUNT,
|
|
6
6
|
DESIGN_SKILL_COUNT,
|
|
7
|
+
GITHUB_MARKETPLACE_NAME,
|
|
8
|
+
GITHUB_MARKETPLACE_REPO,
|
|
7
9
|
MANIFEST_DIR,
|
|
8
10
|
MARKETPLACE_NAME,
|
|
9
11
|
PLUGIN_DIR,
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
readManifest,
|
|
25
27
|
writeFile,
|
|
26
28
|
writeManifest
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-YBT2XOOQ.js";
|
|
28
30
|
|
|
29
31
|
// src/index.ts
|
|
30
32
|
import { Command } from "commander";
|
|
@@ -32,7 +34,7 @@ import * as p16 from "@clack/prompts";
|
|
|
32
34
|
import chalk17 from "chalk";
|
|
33
35
|
|
|
34
36
|
// src/commands/init.ts
|
|
35
|
-
import { existsSync as existsSync5
|
|
37
|
+
import { existsSync as existsSync5 } from "fs";
|
|
36
38
|
import { resolve as resolve2, join as join7, dirname as dirname2 } from "path";
|
|
37
39
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
38
40
|
import * as p3 from "@clack/prompts";
|
|
@@ -1128,7 +1130,7 @@ function writeClaudeSettings(targetDir, settings) {
|
|
|
1128
1130
|
writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
1129
1131
|
}
|
|
1130
1132
|
var LEGACY_PLUGIN_NAMES = ["dev-ai", "ai-agentic"];
|
|
1131
|
-
var LEGACY_MARKETPLACE_NAMES = ["dev-ai-local", "ai-agentic-local"];
|
|
1133
|
+
var LEGACY_MARKETPLACE_NAMES = ["dev-ai-local", "ai-agentic-local", "devtronic-local"];
|
|
1132
1134
|
function registerPlugin(targetDir, pluginName, marketplaceName, marketplacePath) {
|
|
1133
1135
|
const settings = readClaudeSettings(targetDir);
|
|
1134
1136
|
if (settings.extraKnownMarketplaces) {
|
|
@@ -1160,6 +1162,38 @@ function registerPlugin(targetDir, pluginName, marketplaceName, marketplacePath)
|
|
|
1160
1162
|
}
|
|
1161
1163
|
writeClaudeSettings(targetDir, settings);
|
|
1162
1164
|
}
|
|
1165
|
+
function registerGitHubPlugin(targetDir, pluginName, marketplaceName, githubRepo) {
|
|
1166
|
+
const settings = readClaudeSettings(targetDir);
|
|
1167
|
+
if (settings.extraKnownMarketplaces) {
|
|
1168
|
+
for (const legacy of LEGACY_MARKETPLACE_NAMES) {
|
|
1169
|
+
delete settings.extraKnownMarketplaces[legacy];
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (settings.enabledPlugins) {
|
|
1173
|
+
for (const key of Object.keys(settings.enabledPlugins)) {
|
|
1174
|
+
if (LEGACY_PLUGIN_NAMES.some((lp) => key.startsWith(`${lp}@`))) {
|
|
1175
|
+
delete settings.enabledPlugins[key];
|
|
1176
|
+
}
|
|
1177
|
+
if (key === `${pluginName}@devtronic-local`) {
|
|
1178
|
+
delete settings.enabledPlugins[key];
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (!settings.extraKnownMarketplaces) {
|
|
1183
|
+
settings.extraKnownMarketplaces = {};
|
|
1184
|
+
}
|
|
1185
|
+
settings.extraKnownMarketplaces[marketplaceName] = {
|
|
1186
|
+
source: { source: "github", repo: githubRepo }
|
|
1187
|
+
};
|
|
1188
|
+
if (!settings.enabledPlugins) {
|
|
1189
|
+
settings.enabledPlugins = {};
|
|
1190
|
+
}
|
|
1191
|
+
const pluginKey = `${pluginName}@${marketplaceName}`;
|
|
1192
|
+
if (settings.enabledPlugins[pluginKey] === void 0) {
|
|
1193
|
+
settings.enabledPlugins[pluginKey] = true;
|
|
1194
|
+
}
|
|
1195
|
+
writeClaudeSettings(targetDir, settings);
|
|
1196
|
+
}
|
|
1163
1197
|
function unregisterPlugin(targetDir, pluginName, marketplaceName) {
|
|
1164
1198
|
const settings = readClaudeSettings(targetDir);
|
|
1165
1199
|
const pluginKey = `${pluginName}@${marketplaceName}`;
|
|
@@ -1454,27 +1488,17 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1454
1488
|
const generatedFiles = [];
|
|
1455
1489
|
const usePluginMode = selectedIDEs.includes("claude-code");
|
|
1456
1490
|
if (usePluginMode) {
|
|
1457
|
-
|
|
1491
|
+
registerGitHubPlugin(
|
|
1458
1492
|
targetDir,
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
analysis.packageManager
|
|
1493
|
+
PLUGIN_NAME,
|
|
1494
|
+
GITHUB_MARKETPLACE_NAME,
|
|
1495
|
+
GITHUB_MARKETPLACE_REPO
|
|
1463
1496
|
);
|
|
1464
|
-
|
|
1465
|
-
const scriptPath = join7(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
1466
|
-
if (existsSync5(scriptPath)) {
|
|
1467
|
-
chmodSync(scriptPath, 493);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
registerPlugin(targetDir, PLUGIN_NAME, MARKETPLACE_NAME, PLUGIN_DIR);
|
|
1471
|
-
Object.assign(manifest.files, pluginResult.files);
|
|
1472
|
-
manifest.installMode = "plugin";
|
|
1473
|
-
manifest.pluginPath = pluginResult.pluginPath;
|
|
1497
|
+
manifest.installMode = "marketplace";
|
|
1474
1498
|
const addonSkillCount = (projectConfig.enabledAddons || []).reduce((sum, a) => sum + (ADDONS[a]?.skills.length ?? 0), 0);
|
|
1475
1499
|
const baseTotal = BASE_SKILL_COUNT + DESIGN_SKILL_COUNT;
|
|
1476
1500
|
const skillLabel = addonSkillCount > 0 ? `${baseTotal} + ${addonSkillCount} addon skills` : `${baseTotal} skills`;
|
|
1477
|
-
generatedFiles.push(`Plugin ${PLUGIN_NAME} (${skillLabel}, ${BASE_AGENT_COUNT} agents
|
|
1501
|
+
generatedFiles.push(`Plugin ${PLUGIN_NAME} (${skillLabel}, ${BASE_AGENT_COUNT} agents) via GitHub marketplace`);
|
|
1478
1502
|
}
|
|
1479
1503
|
for (const ide of selectedIDEs) {
|
|
1480
1504
|
const templateName = IDE_TEMPLATE_MAP[ide];
|
|
@@ -1601,11 +1625,14 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1601
1625
|
if (usePluginMode) {
|
|
1602
1626
|
p3.note(
|
|
1603
1627
|
[
|
|
1604
|
-
`
|
|
1605
|
-
`
|
|
1606
|
-
`
|
|
1628
|
+
` Marketplace: ${chalk4.cyan(GITHUB_MARKETPLACE_REPO)}`,
|
|
1629
|
+
` Plugin: ${chalk4.cyan(PLUGIN_NAME)}`,
|
|
1630
|
+
` Skills: /devtronic:brief, /devtronic:spec, ... (auto-namespaced)`,
|
|
1631
|
+
` Hooks: SessionStart, PostToolUse, Stop, SubagentStop, PreCompact`,
|
|
1632
|
+
``,
|
|
1633
|
+
` ${chalk4.dim("Restart Claude Code or run /reload-plugins to activate.")}`
|
|
1607
1634
|
].join("\n"),
|
|
1608
|
-
"
|
|
1635
|
+
"GitHub Marketplace Registered"
|
|
1609
1636
|
);
|
|
1610
1637
|
}
|
|
1611
1638
|
p3.note(
|
|
@@ -1739,7 +1766,7 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
|
|
|
1739
1766
|
|
|
1740
1767
|
// src/commands/update.ts
|
|
1741
1768
|
import { resolve as resolve4, join as join11, dirname as dirname6 } from "path";
|
|
1742
|
-
import { existsSync as existsSync9, unlinkSync as unlinkSync2, lstatSync as lstatSync2, readdirSync as readdirSync2, rmdirSync as rmdirSync2,
|
|
1769
|
+
import { existsSync as existsSync9, unlinkSync as unlinkSync2, lstatSync as lstatSync2, readdirSync as readdirSync2, rmdirSync as rmdirSync2, rmSync as rmSync2, chmodSync } from "fs";
|
|
1743
1770
|
import * as p4 from "@clack/prompts";
|
|
1744
1771
|
import chalk5 from "chalk";
|
|
1745
1772
|
|
|
@@ -2374,18 +2401,37 @@ async function updateCommand(options) {
|
|
|
2374
2401
|
);
|
|
2375
2402
|
if (!options.check) {
|
|
2376
2403
|
const migrate = await p4.confirm({
|
|
2377
|
-
message: "Migrate to
|
|
2404
|
+
message: "Migrate to GitHub marketplace? (standalone \u2192 devtronic marketplace plugin)",
|
|
2405
|
+
initialValue: true
|
|
2406
|
+
});
|
|
2407
|
+
if (!p4.isCancel(migrate) && migrate) {
|
|
2408
|
+
await migrateStandaloneToMarketplace(targetDir, manifest, analysis, options.dryRun);
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const shouldMigrateToMarketplace = manifest.installMode === "plugin" && manifest.pluginPath;
|
|
2414
|
+
if (shouldMigrateToMarketplace) {
|
|
2415
|
+
p4.note(
|
|
2416
|
+
"Claude Code plugin detected as local directory.\nThe new version uses a GitHub marketplace for reliable plugin distribution.\nThis fixes cache issues and enables proper skill namespacing.",
|
|
2417
|
+
"Marketplace Migration Available"
|
|
2418
|
+
);
|
|
2419
|
+
if (!options.check) {
|
|
2420
|
+
const migrate = await p4.confirm({
|
|
2421
|
+
message: "Migrate to GitHub marketplace? (recommended)",
|
|
2378
2422
|
initialValue: true
|
|
2379
2423
|
});
|
|
2380
2424
|
if (!p4.isCancel(migrate) && migrate) {
|
|
2381
|
-
await
|
|
2425
|
+
await migrateToMarketplace(targetDir, manifest, options.dryRun);
|
|
2382
2426
|
return;
|
|
2383
2427
|
}
|
|
2384
2428
|
}
|
|
2385
2429
|
}
|
|
2386
2430
|
if (options.check) {
|
|
2387
|
-
if (installedVersion === currentVersion && stackChanges.length === 0 && !shouldMigrate) {
|
|
2431
|
+
if (installedVersion === currentVersion && stackChanges.length === 0 && !shouldMigrate && !shouldMigrateToMarketplace) {
|
|
2388
2432
|
p4.log.success("Already up to date!");
|
|
2433
|
+
} else if (shouldMigrateToMarketplace) {
|
|
2434
|
+
p4.log.info("Marketplace migration available: local plugin \u2192 GitHub marketplace");
|
|
2389
2435
|
} else if (shouldMigrate) {
|
|
2390
2436
|
p4.log.info("Plugin migration available: standalone \u2192 devtronic plugin");
|
|
2391
2437
|
} else if (installedVersion !== currentVersion) {
|
|
@@ -2569,7 +2615,7 @@ async function updateCommand(options) {
|
|
|
2569
2615
|
for (const ide of manifest.selectedIDEs) {
|
|
2570
2616
|
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2571
2617
|
if (!existsSync9(templateDir)) continue;
|
|
2572
|
-
const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
|
|
2618
|
+
const isPluginMode = ide === "claude-code" && (manifest.installMode === "plugin" || manifest.installMode === "marketplace");
|
|
2573
2619
|
const files = getAllFilesRecursive(templateDir);
|
|
2574
2620
|
for (const file of files) {
|
|
2575
2621
|
if (modifiedFiles.includes(file)) {
|
|
@@ -2586,6 +2632,9 @@ async function updateCommand(options) {
|
|
|
2586
2632
|
updatedManifest.files[file] = createManifestEntry(templateContent);
|
|
2587
2633
|
}
|
|
2588
2634
|
}
|
|
2635
|
+
if (manifest.installMode === "marketplace") {
|
|
2636
|
+
registerGitHubPlugin(targetDir, PLUGIN_NAME, GITHUB_MARKETPLACE_NAME, GITHUB_MARKETPLACE_REPO);
|
|
2637
|
+
}
|
|
2589
2638
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
2590
2639
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
2591
2640
|
for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
|
|
@@ -2608,7 +2657,7 @@ async function updateCommand(options) {
|
|
|
2608
2657
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2609
2658
|
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2610
2659
|
if (existsSync9(scriptPath)) {
|
|
2611
|
-
|
|
2660
|
+
chmodSync(scriptPath, 493);
|
|
2612
2661
|
}
|
|
2613
2662
|
}
|
|
2614
2663
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
@@ -2812,6 +2861,10 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2812
2861
|
}
|
|
2813
2862
|
}
|
|
2814
2863
|
}
|
|
2864
|
+
if (manifest.installMode === "marketplace") {
|
|
2865
|
+
registerGitHubPlugin(targetDir, PLUGIN_NAME, GITHUB_MARKETPLACE_NAME, GITHUB_MARKETPLACE_REPO);
|
|
2866
|
+
regeneratedFiles.push("GitHub marketplace registration");
|
|
2867
|
+
}
|
|
2815
2868
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
2816
2869
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
2817
2870
|
for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
|
|
@@ -2833,7 +2886,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2833
2886
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2834
2887
|
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2835
2888
|
if (existsSync9(scriptPath)) {
|
|
2836
|
-
|
|
2889
|
+
chmodSync(scriptPath, 493);
|
|
2837
2890
|
}
|
|
2838
2891
|
}
|
|
2839
2892
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
@@ -2863,29 +2916,15 @@ function hasStandaloneSkills(manifest) {
|
|
|
2863
2916
|
(f) => f.startsWith(".claude/skills/") || f.startsWith(".claude/agents/")
|
|
2864
2917
|
);
|
|
2865
2918
|
}
|
|
2866
|
-
async function
|
|
2919
|
+
async function migrateStandaloneToMarketplace(targetDir, manifest, analysis, dryRun) {
|
|
2867
2920
|
if (dryRun) {
|
|
2868
2921
|
p4.log.info("Would migrate standalone skills/agents to devtronic plugin.");
|
|
2869
2922
|
p4.outro("Dry run complete \u2014 no changes made");
|
|
2870
2923
|
return;
|
|
2871
2924
|
}
|
|
2872
2925
|
const spinner8 = p4.spinner();
|
|
2873
|
-
spinner8.start("Migrating to
|
|
2874
|
-
|
|
2875
|
-
const pluginResult = generatePlugin(
|
|
2876
|
-
targetDir,
|
|
2877
|
-
TEMPLATES_DIR,
|
|
2878
|
-
getCliVersion(),
|
|
2879
|
-
config,
|
|
2880
|
-
analysis.packageManager
|
|
2881
|
-
);
|
|
2882
|
-
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2883
|
-
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2884
|
-
if (existsSync9(scriptPath)) {
|
|
2885
|
-
chmodSync2(scriptPath, 493);
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
registerPlugin(targetDir, PLUGIN_NAME, MARKETPLACE_NAME, PLUGIN_DIR);
|
|
2926
|
+
spinner8.start("Migrating to GitHub marketplace...");
|
|
2927
|
+
registerGitHubPlugin(targetDir, PLUGIN_NAME, GITHUB_MARKETPLACE_NAME, GITHUB_MARKETPLACE_REPO);
|
|
2889
2928
|
const removed = [];
|
|
2890
2929
|
const preserved = [];
|
|
2891
2930
|
for (const [path, fileInfo] of Object.entries(manifest.files)) {
|
|
@@ -2903,9 +2942,7 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2903
2942
|
}
|
|
2904
2943
|
cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
|
|
2905
2944
|
cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
|
|
2906
|
-
|
|
2907
|
-
manifest.installMode = "plugin";
|
|
2908
|
-
manifest.pluginPath = pluginResult.pluginPath;
|
|
2945
|
+
manifest.installMode = "marketplace";
|
|
2909
2946
|
manifest.version = getCliVersion();
|
|
2910
2947
|
manifest.implantedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2911
2948
|
writeManifest(targetDir, manifest);
|
|
@@ -2923,12 +2960,59 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2923
2960
|
);
|
|
2924
2961
|
}
|
|
2925
2962
|
p4.note(
|
|
2926
|
-
`
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2963
|
+
` Marketplace: ${chalk5.cyan(GITHUB_MARKETPLACE_REPO)}
|
|
2964
|
+
Plugin: ${chalk5.cyan(PLUGIN_NAME)}
|
|
2965
|
+
Skills: /devtronic:brief, /devtronic:spec, ... (auto-namespaced)
|
|
2966
|
+
|
|
2967
|
+
${chalk5.dim("Restart Claude Code or run /reload-plugins to activate.")}`,
|
|
2968
|
+
"GitHub Marketplace Registered"
|
|
2930
2969
|
);
|
|
2931
|
-
p4.outro(chalk5.green("Migrated to
|
|
2970
|
+
p4.outro(chalk5.green("Migrated to GitHub marketplace!"));
|
|
2971
|
+
}
|
|
2972
|
+
async function migrateToMarketplace(targetDir, manifest, dryRun) {
|
|
2973
|
+
if (dryRun) {
|
|
2974
|
+
p4.log.info("Would migrate local plugin to GitHub marketplace.");
|
|
2975
|
+
p4.outro("Dry run complete \u2014 no changes made");
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const spinner8 = p4.spinner();
|
|
2979
|
+
spinner8.start("Migrating to GitHub marketplace...");
|
|
2980
|
+
registerGitHubPlugin(targetDir, PLUGIN_NAME, GITHUB_MARKETPLACE_NAME, GITHUB_MARKETPLACE_REPO);
|
|
2981
|
+
const pluginDir = join11(targetDir, PLUGIN_DIR, PLUGIN_NAME);
|
|
2982
|
+
if (existsSync9(pluginDir)) {
|
|
2983
|
+
rmSync2(pluginDir, { recursive: true, force: true });
|
|
2984
|
+
}
|
|
2985
|
+
const marketplaceDescDir = join11(targetDir, PLUGIN_DIR, ".claude-plugin");
|
|
2986
|
+
if (existsSync9(marketplaceDescDir)) {
|
|
2987
|
+
rmSync2(marketplaceDescDir, { recursive: true, force: true });
|
|
2988
|
+
}
|
|
2989
|
+
const pluginsDir = join11(targetDir, PLUGIN_DIR);
|
|
2990
|
+
if (existsSync9(pluginsDir)) {
|
|
2991
|
+
const remaining = readdirSync2(pluginsDir);
|
|
2992
|
+
if (remaining.length === 0) {
|
|
2993
|
+
rmSync2(pluginsDir, { recursive: true, force: true });
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
for (const key of Object.keys(manifest.files)) {
|
|
2997
|
+
if (key.startsWith(PLUGIN_DIR + "/")) {
|
|
2998
|
+
delete manifest.files[key];
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
manifest.installMode = "marketplace";
|
|
3002
|
+
delete manifest.pluginPath;
|
|
3003
|
+
manifest.version = getCliVersion();
|
|
3004
|
+
manifest.implantedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3005
|
+
writeManifest(targetDir, manifest);
|
|
3006
|
+
spinner8.stop("Migration complete");
|
|
3007
|
+
p4.note(
|
|
3008
|
+
` Marketplace: ${chalk5.cyan(GITHUB_MARKETPLACE_REPO)}
|
|
3009
|
+
Plugin: ${chalk5.cyan(PLUGIN_NAME)}
|
|
3010
|
+
Skills: /devtronic:brief, /devtronic:spec, ... (auto-namespaced)
|
|
3011
|
+
|
|
3012
|
+
${chalk5.dim("Restart Claude Code or run /reload-plugins to activate.")}`,
|
|
3013
|
+
"GitHub Marketplace Registered"
|
|
3014
|
+
);
|
|
3015
|
+
p4.outro(chalk5.green("Migrated to GitHub marketplace!"));
|
|
2932
3016
|
}
|
|
2933
3017
|
function buildDefaultConfig(analysis) {
|
|
2934
3018
|
const pm = analysis.packageManager || "npm";
|
|
@@ -3473,10 +3557,14 @@ Valid options:
|
|
|
3473
3557
|
}
|
|
3474
3558
|
}
|
|
3475
3559
|
if (regeneratePlugin) {
|
|
3476
|
-
if (
|
|
3560
|
+
if (manifest.installMode === "marketplace") {
|
|
3561
|
+
p8.log.info("Plugin content is managed by the GitHub marketplace.");
|
|
3562
|
+
p8.log.info("Skills, agents, and hooks update automatically when a new CLI version is released.");
|
|
3563
|
+
p8.log.info(`Use ${chalk9.cyan("--rules")} or ${chalk9.cyan("--all")} (without --plugin) to regenerate local files.`);
|
|
3564
|
+
} else if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
|
|
3477
3565
|
p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
|
|
3478
3566
|
} else {
|
|
3479
|
-
const { generatePlugin: generatePlugin2 } = await import("./plugin-
|
|
3567
|
+
const { generatePlugin: generatePlugin2 } = await import("./plugin-XSNKI5VI.js");
|
|
3480
3568
|
const pluginResult = generatePlugin2(
|
|
3481
3569
|
targetDir,
|
|
3482
3570
|
TEMPLATES_DIR2,
|
|
@@ -3511,10 +3599,14 @@ Valid options:
|
|
|
3511
3599
|
}
|
|
3512
3600
|
|
|
3513
3601
|
// src/commands/info.ts
|
|
3514
|
-
import { resolve as resolve9, join as join16 } from "path";
|
|
3602
|
+
import { resolve as resolve9, join as join16, dirname as dirname9 } from "path";
|
|
3515
3603
|
import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
|
|
3604
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
3516
3605
|
import * as p9 from "@clack/prompts";
|
|
3517
3606
|
import chalk10 from "chalk";
|
|
3607
|
+
var __info_filename = fileURLToPath5(import.meta.url);
|
|
3608
|
+
var __info_dirname = dirname9(__info_filename);
|
|
3609
|
+
var INFO_TEMPLATES_DIR = existsSync13(resolve9(__info_dirname, "../templates")) ? resolve9(__info_dirname, "../templates") : resolve9(__info_dirname, "../../templates");
|
|
3518
3610
|
async function infoCommand() {
|
|
3519
3611
|
const targetDir = resolve9(".");
|
|
3520
3612
|
p9.intro(introTitle("Info"));
|
|
@@ -3540,22 +3632,42 @@ async function infoCommand() {
|
|
|
3540
3632
|
).length;
|
|
3541
3633
|
}
|
|
3542
3634
|
}
|
|
3543
|
-
if (skillCount === 0) {
|
|
3544
|
-
const
|
|
3545
|
-
if (existsSync13(
|
|
3546
|
-
skillCount = readdirSync3(
|
|
3547
|
-
(e) => e.isDirectory()
|
|
3635
|
+
if (manifest.installMode === "marketplace" && skillCount === 0) {
|
|
3636
|
+
const templateSkills = join16(INFO_TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
3637
|
+
if (existsSync13(templateSkills)) {
|
|
3638
|
+
skillCount = readdirSync3(templateSkills, { withFileTypes: true }).filter(
|
|
3639
|
+
(e) => e.isDirectory()
|
|
3548
3640
|
).length;
|
|
3549
3641
|
}
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
if (existsSync13(claudeAgents)) {
|
|
3554
|
-
agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
|
|
3642
|
+
const templateAgents = join16(INFO_TEMPLATES_DIR, "claude-code", ".claude", "agents");
|
|
3643
|
+
if (existsSync13(templateAgents)) {
|
|
3644
|
+
agentCount = readdirSync3(templateAgents, { withFileTypes: true }).filter(
|
|
3555
3645
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
3556
3646
|
).length;
|
|
3557
3647
|
}
|
|
3558
3648
|
}
|
|
3649
|
+
const claudeSkills = join16(targetDir, ".claude", "skills");
|
|
3650
|
+
if (existsSync13(claudeSkills)) {
|
|
3651
|
+
const localSkillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
|
|
3652
|
+
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
3653
|
+
).length;
|
|
3654
|
+
if (skillCount === 0) {
|
|
3655
|
+
skillCount = localSkillCount;
|
|
3656
|
+
} else if (localSkillCount > 0) {
|
|
3657
|
+
skillCount += localSkillCount;
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
const claudeAgents = join16(targetDir, ".claude", "agents");
|
|
3661
|
+
if (existsSync13(claudeAgents)) {
|
|
3662
|
+
const localAgentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
|
|
3663
|
+
(e) => e.isFile() && e.name.endsWith(".md")
|
|
3664
|
+
).length;
|
|
3665
|
+
if (agentCount === 0) {
|
|
3666
|
+
agentCount = localAgentCount;
|
|
3667
|
+
} else if (localAgentCount > 0) {
|
|
3668
|
+
agentCount += localAgentCount;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3559
3671
|
}
|
|
3560
3672
|
const latest = await latestPromise;
|
|
3561
3673
|
if (latest) {
|
|
@@ -3594,10 +3706,14 @@ async function infoCommand() {
|
|
|
3594
3706
|
}
|
|
3595
3707
|
|
|
3596
3708
|
// src/commands/list.ts
|
|
3597
|
-
import { resolve as resolve10, join as join17 } from "path";
|
|
3709
|
+
import { resolve as resolve10, join as join17, dirname as dirname10 } from "path";
|
|
3598
3710
|
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
3711
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
3599
3712
|
import * as p10 from "@clack/prompts";
|
|
3600
3713
|
import chalk11 from "chalk";
|
|
3714
|
+
var __list_filename = fileURLToPath6(import.meta.url);
|
|
3715
|
+
var __list_dirname = dirname10(__list_filename);
|
|
3716
|
+
var LIST_TEMPLATES_DIR = existsSync14(resolve10(__list_dirname, "../templates")) ? resolve10(__list_dirname, "../templates") : resolve10(__list_dirname, "../../templates");
|
|
3601
3717
|
async function listCommand(filter, options) {
|
|
3602
3718
|
const targetDir = resolve10(options.path || ".");
|
|
3603
3719
|
p10.intro(introTitle("List"));
|
|
@@ -3627,6 +3743,20 @@ Valid options: skills, agents`);
|
|
|
3627
3743
|
}
|
|
3628
3744
|
}
|
|
3629
3745
|
}
|
|
3746
|
+
if (manifest?.installMode === "marketplace") {
|
|
3747
|
+
if (showSkills && skills.length === 0) {
|
|
3748
|
+
const templateSkills = join17(LIST_TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
3749
|
+
if (existsSync14(templateSkills)) {
|
|
3750
|
+
skills.push(...discoverSkills(templateSkills));
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
if (showAgents && agents.length === 0) {
|
|
3754
|
+
const templateAgents = join17(LIST_TEMPLATES_DIR, "claude-code", ".claude", "agents");
|
|
3755
|
+
if (existsSync14(templateAgents)) {
|
|
3756
|
+
agents.push(...discoverAgents(templateAgents));
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3630
3760
|
if (showSkills && skills.length === 0) {
|
|
3631
3761
|
const claudeSkills = join17(targetDir, ".claude", "skills");
|
|
3632
3762
|
if (existsSync14(claudeSkills)) {
|
|
@@ -3643,6 +3773,9 @@ Valid options: skills, agents`);
|
|
|
3643
3773
|
if (skills.length > 0) {
|
|
3644
3774
|
const skillLines = skills.sort((a, b) => a.name.localeCompare(b.name)).map((s) => ` ${symbols.bullet} ${chalk11.bold(s.name.padEnd(18))}${chalk11.dim(s.description)}`);
|
|
3645
3775
|
p10.note(skillLines.join("\n"), `Skills (${skills.length})`);
|
|
3776
|
+
if (manifest?.installMode === "marketplace") {
|
|
3777
|
+
p10.log.info(chalk11.dim("Plugin skills loaded from GitHub marketplace"));
|
|
3778
|
+
}
|
|
3646
3779
|
} else {
|
|
3647
3780
|
p10.log.info("No skills found.");
|
|
3648
3781
|
}
|
|
@@ -3651,6 +3784,9 @@ Valid options: skills, agents`);
|
|
|
3651
3784
|
if (agents.length > 0) {
|
|
3652
3785
|
const agentLines = agents.sort((a, b) => a.name.localeCompare(b.name)).map((a) => ` ${symbols.bullet} ${chalk11.bold(a.name.padEnd(18))}${chalk11.dim(a.description)}`);
|
|
3653
3786
|
p10.note(agentLines.join("\n"), `Agents (${agents.length})`);
|
|
3787
|
+
if (manifest?.installMode === "marketplace") {
|
|
3788
|
+
p10.log.info(chalk11.dim("Plugin skills loaded from GitHub marketplace"));
|
|
3789
|
+
}
|
|
3654
3790
|
} else {
|
|
3655
3791
|
p10.log.info("No agents found.");
|
|
3656
3792
|
}
|
|
@@ -3867,7 +4003,7 @@ async function configResetCommand(options) {
|
|
|
3867
4003
|
|
|
3868
4004
|
// src/commands/doctor.ts
|
|
3869
4005
|
import { resolve as resolve12, join as join18 } from "path";
|
|
3870
|
-
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync, chmodSync as
|
|
4006
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync, chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3871
4007
|
import { execSync } from "child_process";
|
|
3872
4008
|
import * as p12 from "@clack/prompts";
|
|
3873
4009
|
import chalk13 from "chalk";
|
|
@@ -3883,8 +4019,8 @@ async function doctorCommand(options) {
|
|
|
3883
4019
|
checks.push(checkManifestValid(manifest));
|
|
3884
4020
|
checks.push(checkManifestFiles(targetDir, manifest));
|
|
3885
4021
|
checks.push(checkScriptPermissions(targetDir, manifest));
|
|
3886
|
-
if (manifest.installMode === "plugin") {
|
|
3887
|
-
checks.push(checkPluginRegistered(targetDir));
|
|
4022
|
+
if (manifest.installMode === "plugin" || manifest.installMode === "marketplace") {
|
|
4023
|
+
checks.push(checkPluginRegistered(targetDir, manifest.installMode));
|
|
3888
4024
|
}
|
|
3889
4025
|
checks.push(checkHookScripts(targetDir, manifest));
|
|
3890
4026
|
checks.push(checkQualityScripts(targetDir));
|
|
@@ -4032,13 +4168,29 @@ function checkScriptPermissions(targetDir, manifest) {
|
|
|
4032
4168
|
fixable: true,
|
|
4033
4169
|
fix: () => {
|
|
4034
4170
|
for (const file of nonExecutable) {
|
|
4035
|
-
|
|
4171
|
+
chmodSync2(file, 493);
|
|
4036
4172
|
}
|
|
4037
4173
|
}
|
|
4038
4174
|
};
|
|
4039
4175
|
}
|
|
4040
|
-
function checkPluginRegistered(targetDir) {
|
|
4176
|
+
function checkPluginRegistered(targetDir, installMode) {
|
|
4041
4177
|
const settings = readClaudeSettings(targetDir);
|
|
4178
|
+
if (installMode === "marketplace") {
|
|
4179
|
+
const pluginKey2 = `${PLUGIN_NAME}@${GITHUB_MARKETPLACE_NAME}`;
|
|
4180
|
+
const isRegistered2 = settings.enabledPlugins?.[pluginKey2] === true;
|
|
4181
|
+
if (isRegistered2) {
|
|
4182
|
+
return { name: "plugin", status: "pass", message: "Marketplace plugin registered in .claude/settings.json" };
|
|
4183
|
+
}
|
|
4184
|
+
return {
|
|
4185
|
+
name: "plugin",
|
|
4186
|
+
status: "warn",
|
|
4187
|
+
message: "Marketplace plugin not registered in .claude/settings.json",
|
|
4188
|
+
fixable: true,
|
|
4189
|
+
fix: () => {
|
|
4190
|
+
registerGitHubPlugin(targetDir, PLUGIN_NAME, GITHUB_MARKETPLACE_NAME, GITHUB_MARKETPLACE_REPO);
|
|
4191
|
+
}
|
|
4192
|
+
};
|
|
4193
|
+
}
|
|
4042
4194
|
const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
4043
4195
|
const isRegistered = settings.enabledPlugins?.[pluginKey] === true;
|
|
4044
4196
|
if (isRegistered) {
|
|
@@ -4193,8 +4345,8 @@ function checkEslint(targetDir) {
|
|
|
4193
4345
|
}
|
|
4194
4346
|
|
|
4195
4347
|
// src/commands/uninstall.ts
|
|
4196
|
-
import { existsSync as existsSync16, rmSync as
|
|
4197
|
-
import { resolve as resolve13, join as join19, dirname as
|
|
4348
|
+
import { existsSync as existsSync16, rmSync as rmSync3, readdirSync as readdirSync6 } from "fs";
|
|
4349
|
+
import { resolve as resolve13, join as join19, dirname as dirname11 } from "path";
|
|
4198
4350
|
import * as p13 from "@clack/prompts";
|
|
4199
4351
|
import chalk14 from "chalk";
|
|
4200
4352
|
var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -4215,6 +4367,7 @@ async function uninstallCommand(options) {
|
|
|
4215
4367
|
const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
|
|
4216
4368
|
const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
|
|
4217
4369
|
const hasPlugin = manifest.installMode === "plugin" && existsSync16(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
|
|
4370
|
+
const hasMarketplace = manifest.installMode === "marketplace";
|
|
4218
4371
|
const hasThoughts = existsSync16(join19(targetDir, "thoughts"));
|
|
4219
4372
|
const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
|
|
4220
4373
|
const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
|
|
@@ -4222,6 +4375,9 @@ async function uninstallCommand(options) {
|
|
|
4222
4375
|
p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
|
|
4223
4376
|
p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
|
|
4224
4377
|
const removalLines = [];
|
|
4378
|
+
if (hasMarketplace) {
|
|
4379
|
+
removalLines.push(` ${symbols.fail} GitHub marketplace registration (${chalk14.cyan(GITHUB_MARKETPLACE_NAME)})`);
|
|
4380
|
+
}
|
|
4225
4381
|
if (hasPlugin) {
|
|
4226
4382
|
removalLines.push(` ${symbols.fail} Plugin ${chalk14.cyan(PLUGIN_NAME)} (${PLUGIN_DIR}/${PLUGIN_NAME}/)`);
|
|
4227
4383
|
}
|
|
@@ -4285,6 +4441,27 @@ async function uninstallCommand(options) {
|
|
|
4285
4441
|
const removed = [];
|
|
4286
4442
|
const kept = [];
|
|
4287
4443
|
const errors = [];
|
|
4444
|
+
if (hasMarketplace) {
|
|
4445
|
+
try {
|
|
4446
|
+
const settings = readClaudeSettings(targetDir);
|
|
4447
|
+
if (settings.extraKnownMarketplaces?.[GITHUB_MARKETPLACE_NAME]) {
|
|
4448
|
+
delete settings.extraKnownMarketplaces[GITHUB_MARKETPLACE_NAME];
|
|
4449
|
+
if (Object.keys(settings.extraKnownMarketplaces).length === 0) {
|
|
4450
|
+
delete settings.extraKnownMarketplaces;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
if (settings.enabledPlugins) {
|
|
4454
|
+
delete settings.enabledPlugins[`${PLUGIN_NAME}@${GITHUB_MARKETPLACE_NAME}`];
|
|
4455
|
+
if (Object.keys(settings.enabledPlugins).length === 0) {
|
|
4456
|
+
delete settings.enabledPlugins;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
writeClaudeSettings(targetDir, settings);
|
|
4460
|
+
removed.push("GitHub marketplace unregistered from .claude/settings.json");
|
|
4461
|
+
} catch (err) {
|
|
4462
|
+
errors.push(`Failed to unregister marketplace: ${err instanceof Error ? err.message : String(err)}`);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4288
4465
|
if (hasPlugin) {
|
|
4289
4466
|
try {
|
|
4290
4467
|
unregisterPlugin(targetDir, PLUGIN_NAME, MARKETPLACE_NAME);
|
|
@@ -4303,18 +4480,18 @@ async function uninstallCommand(options) {
|
|
|
4303
4480
|
}
|
|
4304
4481
|
if (hasPlugin) {
|
|
4305
4482
|
try {
|
|
4306
|
-
|
|
4483
|
+
rmSync3(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
|
|
4307
4484
|
removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
|
|
4308
4485
|
const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
|
|
4309
4486
|
if (existsSync16(marketplaceDescDir)) {
|
|
4310
|
-
|
|
4487
|
+
rmSync3(marketplaceDescDir, { recursive: true, force: true });
|
|
4311
4488
|
removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
|
|
4312
4489
|
}
|
|
4313
4490
|
const pluginsDir = join19(targetDir, PLUGIN_DIR);
|
|
4314
4491
|
if (existsSync16(pluginsDir)) {
|
|
4315
4492
|
const remaining = readdirSafe(pluginsDir);
|
|
4316
4493
|
if (remaining.length === 0) {
|
|
4317
|
-
|
|
4494
|
+
rmSync3(pluginsDir, { recursive: true, force: true });
|
|
4318
4495
|
removed.push(`${PLUGIN_DIR}/ (empty)`);
|
|
4319
4496
|
}
|
|
4320
4497
|
}
|
|
@@ -4329,9 +4506,9 @@ async function uninstallCommand(options) {
|
|
|
4329
4506
|
try {
|
|
4330
4507
|
const filePath = join19(targetDir, file);
|
|
4331
4508
|
if (existsSync16(filePath)) {
|
|
4332
|
-
|
|
4509
|
+
rmSync3(filePath, { force: true });
|
|
4333
4510
|
removed.push(file);
|
|
4334
|
-
cleanEmptyParents(targetDir,
|
|
4511
|
+
cleanEmptyParents(targetDir, dirname11(file));
|
|
4335
4512
|
}
|
|
4336
4513
|
} catch (err) {
|
|
4337
4514
|
errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4340,7 +4517,7 @@ async function uninstallCommand(options) {
|
|
|
4340
4517
|
if (hasClaudeMd) {
|
|
4341
4518
|
if (removeClaudeMd) {
|
|
4342
4519
|
try {
|
|
4343
|
-
|
|
4520
|
+
rmSync3(join19(targetDir, "CLAUDE.md"), { force: true });
|
|
4344
4521
|
removed.push("CLAUDE.md");
|
|
4345
4522
|
} catch (err) {
|
|
4346
4523
|
errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4352,7 +4529,7 @@ async function uninstallCommand(options) {
|
|
|
4352
4529
|
if (hasAgentsMd) {
|
|
4353
4530
|
if (removeAgentsMd) {
|
|
4354
4531
|
try {
|
|
4355
|
-
|
|
4532
|
+
rmSync3(join19(targetDir, "AGENTS.md"), { force: true });
|
|
4356
4533
|
removed.push("AGENTS.md");
|
|
4357
4534
|
} catch (err) {
|
|
4358
4535
|
errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4364,7 +4541,7 @@ async function uninstallCommand(options) {
|
|
|
4364
4541
|
if (hasThoughts) {
|
|
4365
4542
|
if (removeThoughts) {
|
|
4366
4543
|
try {
|
|
4367
|
-
|
|
4544
|
+
rmSync3(join19(targetDir, "thoughts"), { recursive: true, force: true });
|
|
4368
4545
|
removed.push("thoughts/");
|
|
4369
4546
|
} catch (err) {
|
|
4370
4547
|
errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4376,7 +4553,7 @@ async function uninstallCommand(options) {
|
|
|
4376
4553
|
try {
|
|
4377
4554
|
const manifestDir = join19(targetDir, MANIFEST_DIR);
|
|
4378
4555
|
if (existsSync16(manifestDir)) {
|
|
4379
|
-
|
|
4556
|
+
rmSync3(manifestDir, { recursive: true, force: true });
|
|
4380
4557
|
removed.push(`${MANIFEST_DIR}/`);
|
|
4381
4558
|
}
|
|
4382
4559
|
} catch (err) {
|
|
@@ -4426,8 +4603,8 @@ function cleanEmptyParents(targetDir, relDir) {
|
|
|
4426
4603
|
const entries = readdirSafe(absDir);
|
|
4427
4604
|
if (entries.length === 0) {
|
|
4428
4605
|
try {
|
|
4429
|
-
|
|
4430
|
-
cleanEmptyParents(targetDir,
|
|
4606
|
+
rmSync3(absDir, { recursive: true, force: true });
|
|
4607
|
+
cleanEmptyParents(targetDir, dirname11(relDir));
|
|
4431
4608
|
} catch {
|
|
4432
4609
|
}
|
|
4433
4610
|
}
|
|
@@ -4441,8 +4618,8 @@ function readdirSafe(dir) {
|
|
|
4441
4618
|
}
|
|
4442
4619
|
|
|
4443
4620
|
// src/commands/addon.ts
|
|
4444
|
-
import { resolve as resolve14, join as join20, dirname as
|
|
4445
|
-
import { existsSync as existsSync17, unlinkSync as unlinkSync3, rmSync as
|
|
4621
|
+
import { resolve as resolve14, join as join20, dirname as dirname12 } from "path";
|
|
4622
|
+
import { existsSync as existsSync17, unlinkSync as unlinkSync3, rmSync as rmSync4 } from "fs";
|
|
4446
4623
|
import * as p14 from "@clack/prompts";
|
|
4447
4624
|
import chalk15 from "chalk";
|
|
4448
4625
|
function isFileBasedAddon(addonName) {
|
|
@@ -4481,8 +4658,8 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
4481
4658
|
p14.outro("");
|
|
4482
4659
|
return;
|
|
4483
4660
|
}
|
|
4484
|
-
if (manifest.installMode !== "plugin"
|
|
4485
|
-
p14.log.warn("
|
|
4661
|
+
if (manifest.installMode !== "plugin" && manifest.installMode !== "marketplace") {
|
|
4662
|
+
p14.log.warn("The orchestration addon requires Claude Code in plugin or marketplace mode.");
|
|
4486
4663
|
p14.log.info("Run `npx devtronic init` with Claude Code selected.");
|
|
4487
4664
|
p14.outro("");
|
|
4488
4665
|
return;
|
|
@@ -4501,6 +4678,29 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
4501
4678
|
p14.outro("");
|
|
4502
4679
|
return;
|
|
4503
4680
|
}
|
|
4681
|
+
if (manifest.installMode === "marketplace") {
|
|
4682
|
+
const addon2 = ADDONS[addonName];
|
|
4683
|
+
p14.note(
|
|
4684
|
+
[
|
|
4685
|
+
` ${chalk15.dim("Name:")} ${addon2.label}`,
|
|
4686
|
+
` ${chalk15.dim("Description:")} ${addon2.description}`,
|
|
4687
|
+
` ${chalk15.dim("Skills:")} ${addon2.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`
|
|
4688
|
+
].join("\n"),
|
|
4689
|
+
"Enabling addon"
|
|
4690
|
+
);
|
|
4691
|
+
if (!manifest.projectConfig) {
|
|
4692
|
+
manifest.projectConfig = { architecture: "flat", layers: [], stateManagement: [], dataFetching: [], orm: [], testing: [], ui: [], validation: [], framework: "unknown", qualityCommand: "" };
|
|
4693
|
+
}
|
|
4694
|
+
manifest.projectConfig.enabledAddons = [...currentAddons, addonName];
|
|
4695
|
+
writeManifest(targetDir, manifest);
|
|
4696
|
+
p14.log.success(`${addon2.label} enabled`);
|
|
4697
|
+
p14.note(
|
|
4698
|
+
"Orchestration skills are included in the marketplace plugin.\nAlready available as /devtronic:briefing, /devtronic:recap, /devtronic:handoff.",
|
|
4699
|
+
"Info"
|
|
4700
|
+
);
|
|
4701
|
+
p14.outro("");
|
|
4702
|
+
return;
|
|
4703
|
+
}
|
|
4504
4704
|
const addon = ADDONS[addonName];
|
|
4505
4705
|
const pluginRoot = manifest.pluginPath;
|
|
4506
4706
|
const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
@@ -4533,7 +4733,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
4533
4733
|
const content = readFile(join20(sourceDir, file));
|
|
4534
4734
|
const destRelPath = join20(pluginRoot, "skills", skillDir, file);
|
|
4535
4735
|
const destAbsPath = join20(targetDir, destRelPath);
|
|
4536
|
-
ensureDir(
|
|
4736
|
+
ensureDir(dirname12(destAbsPath));
|
|
4537
4737
|
writeFile(destAbsPath, content);
|
|
4538
4738
|
manifest.files[destRelPath] = createManifestEntry(content);
|
|
4539
4739
|
addedFiles.push(destRelPath);
|
|
@@ -4560,6 +4760,14 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
4560
4760
|
p14.outro("");
|
|
4561
4761
|
return;
|
|
4562
4762
|
}
|
|
4763
|
+
if (manifest.installMode === "marketplace") {
|
|
4764
|
+
const addon2 = ADDONS[addonName];
|
|
4765
|
+
manifest.projectConfig.enabledAddons = currentAddons.filter((a) => a !== addonName);
|
|
4766
|
+
writeManifest(targetDir, manifest);
|
|
4767
|
+
p14.log.success(`${addon2.label} disabled`);
|
|
4768
|
+
p14.outro("");
|
|
4769
|
+
return;
|
|
4770
|
+
}
|
|
4563
4771
|
const addon = ADDONS[addonName];
|
|
4564
4772
|
const pluginRoot = manifest.pluginPath;
|
|
4565
4773
|
p14.note(
|
|
@@ -4616,7 +4824,7 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
4616
4824
|
}
|
|
4617
4825
|
if (existsSync17(skillAbsDir)) {
|
|
4618
4826
|
try {
|
|
4619
|
-
|
|
4827
|
+
rmSync4(skillAbsDir, { recursive: true });
|
|
4620
4828
|
} catch {
|
|
4621
4829
|
}
|
|
4622
4830
|
}
|
|
@@ -2,17 +2,21 @@ import {
|
|
|
2
2
|
BASE_AGENT_COUNT,
|
|
3
3
|
BASE_SKILL_COUNT,
|
|
4
4
|
DESIGN_SKILL_COUNT,
|
|
5
|
+
GITHUB_MARKETPLACE_NAME,
|
|
6
|
+
GITHUB_MARKETPLACE_REPO,
|
|
5
7
|
MARKETPLACE_NAME,
|
|
6
8
|
PLUGIN_DIR,
|
|
7
9
|
PLUGIN_NAME,
|
|
8
10
|
generateMarketplaceJson,
|
|
9
11
|
generatePlugin,
|
|
10
12
|
generatePluginJson
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YBT2XOOQ.js";
|
|
12
14
|
export {
|
|
13
15
|
BASE_AGENT_COUNT,
|
|
14
16
|
BASE_SKILL_COUNT,
|
|
15
17
|
DESIGN_SKILL_COUNT,
|
|
18
|
+
GITHUB_MARKETPLACE_NAME,
|
|
19
|
+
GITHUB_MARKETPLACE_REPO,
|
|
16
20
|
MARKETPLACE_NAME,
|
|
17
21
|
PLUGIN_DIR,
|
|
18
22
|
PLUGIN_NAME,
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto-lint after file changes
|
|
3
|
+
# Generated by devtronic — generic version (auto-detects package manager)
|
|
4
|
+
|
|
5
|
+
# Detect package manager
|
|
6
|
+
if [ -f "pnpm-lock.yaml" ]; then
|
|
7
|
+
PM="pnpm"
|
|
8
|
+
elif [ -f "yarn.lock" ]; then
|
|
9
|
+
PM="yarn"
|
|
10
|
+
elif [ -f "bun.lockb" ]; then
|
|
11
|
+
PM="bun"
|
|
12
|
+
else
|
|
13
|
+
PM="npm"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
RUN="$PM run"
|
|
17
|
+
if [ "$PM" != "npm" ]; then
|
|
18
|
+
RUN="$PM"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Try lint:fix first, fall back to lint --fix
|
|
22
|
+
if node -e "const p=require('./package.json');process.exit(p.scripts?.['lint:fix']?0:1)" 2>/dev/null; then
|
|
23
|
+
$RUN lint:fix -- --quiet 2>/dev/null || true
|
|
24
|
+
elif node -e "const p=require('./package.json');process.exit(p.scripts?.lint?0:1)" 2>/dev/null; then
|
|
25
|
+
$RUN lint --fix --quiet 2>/dev/null || true
|
|
26
|
+
fi
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto-checkpoint before context compaction
|
|
3
|
+
# Generated by devtronic
|
|
4
|
+
|
|
5
|
+
CHECKPOINT_DIR="thoughts/checkpoints"
|
|
6
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
7
|
+
|
|
8
|
+
mkdir -p "$CHECKPOINT_DIR"
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
echo "# Auto-checkpoint: $TIMESTAMP"
|
|
12
|
+
echo ""
|
|
13
|
+
echo "## Git Status"
|
|
14
|
+
git diff --stat 2>/dev/null || echo "Not a git repo"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "## Recent Commits"
|
|
17
|
+
git log --oneline -5 2>/dev/null || echo "No commits"
|
|
18
|
+
} > "$CHECKPOINT_DIR/${TIMESTAMP}_pre-compact.md"
|
|
19
|
+
|
|
20
|
+
echo "Checkpoint saved: $CHECKPOINT_DIR/${TIMESTAMP}_pre-compact.md"
|
|
21
|
+
|
|
22
|
+
# Update persistent state (minimal — skill-level checkpoint writes richer state)
|
|
23
|
+
STATE_FILE="thoughts/STATE.md"
|
|
24
|
+
mkdir -p "$(dirname "$STATE_FILE")"
|
|
25
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
26
|
+
{
|
|
27
|
+
echo "# Project State"
|
|
28
|
+
echo ""
|
|
29
|
+
echo "**Updated**: $(date '+%Y-%m-%d %H:%M') (auto-compact)"
|
|
30
|
+
echo "**Branch**: $BRANCH"
|
|
31
|
+
echo ""
|
|
32
|
+
echo "## Last Auto-Checkpoint"
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Context was compacted. See: \`$CHECKPOINT_DIR/${TIMESTAMP}_pre-compact.md\`"
|
|
35
|
+
echo ""
|
|
36
|
+
echo "## Recent Commits"
|
|
37
|
+
echo ""
|
|
38
|
+
git log --oneline -5 2>/dev/null || echo "No commits"
|
|
39
|
+
} > "$STATE_FILE"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "r-bart devtronic — workflow hooks",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "startup",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "prompt",
|
|
10
|
+
"prompt": "Quick project orientation: First check if thoughts/STATE.md exists — if so, read it and summarize the current project state. Then check git status, recent commits, and any in-progress work. Give a 3-line summary prioritizing STATE.md context if available.\n\nContext: $ARGUMENTS",
|
|
11
|
+
"model": "haiku",
|
|
12
|
+
"timeout": 30
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"PostToolUse": [
|
|
18
|
+
{
|
|
19
|
+
"matcher": "Write|Edit",
|
|
20
|
+
"hooks": [
|
|
21
|
+
{
|
|
22
|
+
"type": "command",
|
|
23
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/auto-lint.sh 2>/dev/null || true",
|
|
24
|
+
"timeout": 30,
|
|
25
|
+
"statusMessage": "Auto-linting..."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"Stop": [
|
|
31
|
+
{
|
|
32
|
+
"hooks": [
|
|
33
|
+
{
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/stop-guard.sh",
|
|
36
|
+
"timeout": 60,
|
|
37
|
+
"statusMessage": "Running quality checks..."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "prompt",
|
|
41
|
+
"prompt": "If thoughts/plans/ contains a recent plan with a \"Done Criteria\" section, quickly check: are there unmet criteria? If yes, list them as a brief reminder. Do NOT run a full review. If no plan or all criteria met, say nothing.",
|
|
42
|
+
"model": "haiku",
|
|
43
|
+
"timeout": 15,
|
|
44
|
+
"statusMessage": "Checking done criteria..."
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"SubagentStop": [
|
|
50
|
+
{
|
|
51
|
+
"hooks": [
|
|
52
|
+
{
|
|
53
|
+
"type": "prompt",
|
|
54
|
+
"prompt": "A subagent has finished. Based on the metadata below, assess if it completed successfully.\nConsider the agent type and whether the session ended normally.\nRespond with {\"ok\": true} if it appears complete, or {\"ok\": false, \"reason\": \"...\"} if something seems off.\n\nIMPORTANT: If stop_hook_active is true, always respond {\"ok\": true} to prevent infinite loops.\n\nContext: $ARGUMENTS",
|
|
55
|
+
"model": "haiku",
|
|
56
|
+
"timeout": 30
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"PreCompact": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "auto",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/checkpoint.sh",
|
|
68
|
+
"timeout": 30,
|
|
69
|
+
"statusMessage": "Auto-checkpoint before compaction..."
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Quality gate: runs checks before allowing Claude to stop
|
|
3
|
+
# Generated by devtronic — generic version (auto-detects package manager)
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
|
|
7
|
+
# Prevent infinite loops: if already triggered by a stop hook, allow stop
|
|
8
|
+
if echo "$INPUT" | grep -q '"stop_hook_active"'; then
|
|
9
|
+
if echo "$INPUT" | grep -q '"stop_hook_active"[[:space:]]*:[[:space:]]*true'; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Detect package manager
|
|
15
|
+
if [ -f "pnpm-lock.yaml" ]; then
|
|
16
|
+
PM="pnpm"
|
|
17
|
+
elif [ -f "yarn.lock" ]; then
|
|
18
|
+
PM="yarn"
|
|
19
|
+
elif [ -f "bun.lockb" ]; then
|
|
20
|
+
PM="bun"
|
|
21
|
+
else
|
|
22
|
+
PM="npm"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
RUN="$PM run"
|
|
26
|
+
if [ "$PM" != "npm" ]; then
|
|
27
|
+
RUN="$PM"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Build quality command from available scripts
|
|
31
|
+
QUALITY_CMD=""
|
|
32
|
+
if node -e "const p=require('./package.json');process.exit(p.scripts?.typecheck?0:1)" 2>/dev/null; then
|
|
33
|
+
QUALITY_CMD="$RUN typecheck"
|
|
34
|
+
fi
|
|
35
|
+
if node -e "const p=require('./package.json');process.exit(p.scripts?.lint?0:1)" 2>/dev/null; then
|
|
36
|
+
if [ -n "$QUALITY_CMD" ]; then
|
|
37
|
+
QUALITY_CMD="$QUALITY_CMD && $RUN lint"
|
|
38
|
+
else
|
|
39
|
+
QUALITY_CMD="$RUN lint"
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# If no quality scripts found, allow stop
|
|
44
|
+
if [ -z "$QUALITY_CMD" ]; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Run quality checks
|
|
49
|
+
if ! eval "$QUALITY_CMD" > /dev/null 2>&1; then
|
|
50
|
+
echo "Quality checks failed. Run '$QUALITY_CMD' to see details." >&2
|
|
51
|
+
exit 2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
exit 0
|