devtronic 1.0.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 +61 -0
- package/dist/chunk-B6Q6YVID.js +728 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4214 -0
- package/dist/plugin-JTDPHWUF.js +18 -0
- package/package.json +70 -0
- package/templates/antigravity/.agents/rules/architecture.md +31 -0
- package/templates/antigravity/.agents/rules/quality.md +44 -0
- package/templates/claude-code/.claude/agents/architecture-checker.md +142 -0
- package/templates/claude-code/.claude/agents/code-reviewer.md +73 -0
- package/templates/claude-code/.claude/agents/commit-changes.md +74 -0
- package/templates/claude-code/.claude/agents/dependency-checker.md +112 -0
- package/templates/claude-code/.claude/agents/doc-sync.md +99 -0
- package/templates/claude-code/.claude/agents/error-investigator.md +142 -0
- package/templates/claude-code/.claude/agents/quality-runner.md +123 -0
- package/templates/claude-code/.claude/agents/test-generator.md +114 -0
- package/templates/claude-code/.claude/rules/architecture.md +257 -0
- package/templates/claude-code/.claude/rules/quality.md +103 -0
- package/templates/claude-code/.claude/skills/audit/SKILL.md +426 -0
- package/templates/claude-code/.claude/skills/audit/report-template.md +137 -0
- package/templates/claude-code/.claude/skills/backlog/SKILL.md +231 -0
- package/templates/claude-code/.claude/skills/brief/SKILL.md +425 -0
- package/templates/claude-code/.claude/skills/briefing/SKILL.md +172 -0
- package/templates/claude-code/.claude/skills/checkpoint/SKILL.md +284 -0
- package/templates/claude-code/.claude/skills/create-plan/SKILL.md +446 -0
- package/templates/claude-code/.claude/skills/create-skill/SKILL.md +245 -0
- package/templates/claude-code/.claude/skills/execute-plan/SKILL.md +398 -0
- package/templates/claude-code/.claude/skills/generate-tests/SKILL.md +358 -0
- package/templates/claude-code/.claude/skills/generate-tests/test-patterns.md +349 -0
- package/templates/claude-code/.claude/skills/handoff/SKILL.md +178 -0
- package/templates/claude-code/.claude/skills/investigate/SKILL.md +376 -0
- package/templates/claude-code/.claude/skills/learn/SKILL.md +231 -0
- package/templates/claude-code/.claude/skills/opensrc/SKILL.md +104 -0
- package/templates/claude-code/.claude/skills/post-review/SKILL.md +437 -0
- package/templates/claude-code/.claude/skills/quick/SKILL.md +188 -0
- package/templates/claude-code/.claude/skills/recap/SKILL.md +190 -0
- package/templates/claude-code/.claude/skills/research/SKILL.md +450 -0
- package/templates/claude-code/.claude/skills/scaffold/SKILL.md +281 -0
- package/templates/claude-code/.claude/skills/scaffold/bootstrap-commands.md +104 -0
- package/templates/claude-code/.claude/skills/scaffold/examples-backend.md +105 -0
- package/templates/claude-code/.claude/skills/scaffold/examples-frontend.md +197 -0
- package/templates/claude-code/.claude/skills/scaffold/examples-spa-ddd.md +667 -0
- package/templates/claude-code/.claude/skills/scaffold/structures.md +236 -0
- package/templates/claude-code/.claude/skills/setup/SKILL.md +227 -0
- package/templates/claude-code/.claude/skills/spec/SKILL.md +235 -0
- package/templates/claude-code/.claude/skills/summary/SKILL.md +279 -0
- package/templates/claude-code/.claude/skills/worktree/SKILL.md +266 -0
- package/templates/cursor/.cursor/rules/architecture.mdc +36 -0
- package/templates/cursor/.cursor/rules/quality.mdc +49 -0
- package/templates/github-copilot/.github/copilot-instructions.md +40 -0
- package/templates/opencode/.opencode/rules/architecture.md +31 -0
- package/templates/opencode/.opencode/rules/quality.md +44 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
// src/generators/plugin.ts
|
|
2
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
var ADDONS = {
|
|
6
|
+
orchestration: {
|
|
7
|
+
name: "orchestration",
|
|
8
|
+
label: "Orchestration Workflow",
|
|
9
|
+
description: "Structured pre-planning alignment, session recaps, and context rotation for long multi-session work.",
|
|
10
|
+
skills: ["briefing", "recap", "handoff"],
|
|
11
|
+
agents: []
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var PRESETS = {
|
|
15
|
+
"nextjs-clean": {
|
|
16
|
+
name: "nextjs-clean",
|
|
17
|
+
description: "Next.js with Clean Architecture",
|
|
18
|
+
config: {
|
|
19
|
+
framework: "nextjs",
|
|
20
|
+
architecture: "clean",
|
|
21
|
+
layers: ["domain", "application", "infrastructure", "presentation"],
|
|
22
|
+
stateManagement: ["Zustand"],
|
|
23
|
+
dataFetching: ["React Query"],
|
|
24
|
+
ui: ["Tailwind CSS"],
|
|
25
|
+
validation: ["Zod"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"react-clean": {
|
|
29
|
+
name: "react-clean",
|
|
30
|
+
description: "React (Vite) with Clean Architecture",
|
|
31
|
+
config: {
|
|
32
|
+
framework: "react",
|
|
33
|
+
architecture: "clean",
|
|
34
|
+
layers: ["domain", "application", "infrastructure", "presentation"],
|
|
35
|
+
stateManagement: ["Zustand"],
|
|
36
|
+
dataFetching: ["React Query"],
|
|
37
|
+
ui: ["Tailwind CSS"]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
monorepo: {
|
|
41
|
+
name: "monorepo",
|
|
42
|
+
description: "Monorepo with Clean Architecture per app",
|
|
43
|
+
config: {
|
|
44
|
+
architecture: "clean",
|
|
45
|
+
layers: ["apps", "packages", "libs"]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"feature-based": {
|
|
49
|
+
name: "feature-based",
|
|
50
|
+
description: "Feature-based architecture (co-located modules)",
|
|
51
|
+
config: {
|
|
52
|
+
architecture: "feature-based",
|
|
53
|
+
layers: ["features", "shared"]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
minimal: {
|
|
57
|
+
name: "minimal",
|
|
58
|
+
description: "Quality checks only, no architecture rules",
|
|
59
|
+
config: {
|
|
60
|
+
architecture: "none",
|
|
61
|
+
layers: []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/utils/files.ts
|
|
67
|
+
import { createHash } from "crypto";
|
|
68
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, readdirSync } from "fs";
|
|
69
|
+
import { dirname, join, relative } from "path";
|
|
70
|
+
var MANIFEST_DIR = ".ai-template";
|
|
71
|
+
var MANIFEST_FILE = "manifest.json";
|
|
72
|
+
function fileExists(path) {
|
|
73
|
+
return existsSync(path);
|
|
74
|
+
}
|
|
75
|
+
function readFile(path) {
|
|
76
|
+
return readFileSync(path, "utf-8");
|
|
77
|
+
}
|
|
78
|
+
function writeFile(path, content) {
|
|
79
|
+
const dir = dirname(path);
|
|
80
|
+
if (!existsSync(dir)) {
|
|
81
|
+
mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
writeFileSync(path, content, "utf-8");
|
|
84
|
+
}
|
|
85
|
+
function ensureDir(path) {
|
|
86
|
+
if (!existsSync(path)) {
|
|
87
|
+
mkdirSync(path, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function calculateChecksum(content) {
|
|
91
|
+
return createHash("md5").update(content).digest("hex");
|
|
92
|
+
}
|
|
93
|
+
function getManifestPath(targetDir) {
|
|
94
|
+
return join(targetDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
95
|
+
}
|
|
96
|
+
function readManifest(targetDir) {
|
|
97
|
+
const manifestPath = getManifestPath(targetDir);
|
|
98
|
+
if (!fileExists(manifestPath)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const raw = JSON.parse(readFile(manifestPath));
|
|
103
|
+
return {
|
|
104
|
+
version: raw.version ?? "unknown",
|
|
105
|
+
implantedAt: raw.implantedAt ?? "unknown",
|
|
106
|
+
selectedIDEs: raw.selectedIDEs ?? [],
|
|
107
|
+
projectConfig: raw.projectConfig,
|
|
108
|
+
files: raw.files ?? {},
|
|
109
|
+
installMode: raw.installMode,
|
|
110
|
+
pluginPath: raw.pluginPath
|
|
111
|
+
};
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function writeManifest(targetDir, manifest) {
|
|
117
|
+
const manifestPath = getManifestPath(targetDir);
|
|
118
|
+
ensureDir(dirname(manifestPath));
|
|
119
|
+
writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
120
|
+
}
|
|
121
|
+
function getAllFilesRecursive(dir, baseDir = dir) {
|
|
122
|
+
const files = [];
|
|
123
|
+
if (!existsSync(dir)) {
|
|
124
|
+
return files;
|
|
125
|
+
}
|
|
126
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
const fullPath = join(dir, entry.name);
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
files.push(...getAllFilesRecursive(fullPath, baseDir));
|
|
131
|
+
} else {
|
|
132
|
+
files.push(relative(baseDir, fullPath));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return files;
|
|
136
|
+
}
|
|
137
|
+
function createManifestEntry(content) {
|
|
138
|
+
const checksum = calculateChecksum(content);
|
|
139
|
+
return {
|
|
140
|
+
checksum,
|
|
141
|
+
modified: false,
|
|
142
|
+
originalChecksum: checksum
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function getSubdirectories(dir) {
|
|
146
|
+
if (!existsSync(dir)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/generators/hooks.ts
|
|
153
|
+
function generateHooks(config, packageManager) {
|
|
154
|
+
const lintFixCmd = buildLintFixCommand(packageManager, config);
|
|
155
|
+
const hooksConfig = {
|
|
156
|
+
description: "r-bart devtronic \u2014 workflow hooks",
|
|
157
|
+
hooks: {
|
|
158
|
+
SessionStart: [
|
|
159
|
+
{
|
|
160
|
+
matcher: "startup",
|
|
161
|
+
hooks: [
|
|
162
|
+
{
|
|
163
|
+
type: "prompt",
|
|
164
|
+
prompt: "Quick project orientation: First check if thoughts/STATE.md exists \u2014 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",
|
|
165
|
+
model: "haiku",
|
|
166
|
+
timeout: 30
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
// Synchronous lint-fix: must complete before Claude reads the file again
|
|
172
|
+
PostToolUse: [
|
|
173
|
+
{
|
|
174
|
+
matcher: "Write|Edit",
|
|
175
|
+
hooks: [
|
|
176
|
+
{
|
|
177
|
+
type: "command",
|
|
178
|
+
command: `${lintFixCmd} 2>/dev/null || true`,
|
|
179
|
+
timeout: 30,
|
|
180
|
+
statusMessage: "Auto-linting..."
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
Stop: [
|
|
186
|
+
{
|
|
187
|
+
hooks: [
|
|
188
|
+
{
|
|
189
|
+
type: "command",
|
|
190
|
+
command: "${CLAUDE_PLUGIN_ROOT}/scripts/stop-guard.sh",
|
|
191
|
+
timeout: 60,
|
|
192
|
+
statusMessage: "Running quality checks..."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: "prompt",
|
|
196
|
+
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.',
|
|
197
|
+
model: "haiku",
|
|
198
|
+
timeout: 15,
|
|
199
|
+
statusMessage: "Checking done criteria..."
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
SubagentStop: [
|
|
205
|
+
{
|
|
206
|
+
hooks: [
|
|
207
|
+
{
|
|
208
|
+
type: "prompt",
|
|
209
|
+
prompt: [
|
|
210
|
+
"A subagent has finished. Based on the metadata below, assess if it completed successfully.",
|
|
211
|
+
"Consider the agent type and whether the session ended normally.",
|
|
212
|
+
'Respond with {"ok": true} if it appears complete, or {"ok": false, "reason": "..."} if something seems off.',
|
|
213
|
+
"",
|
|
214
|
+
'IMPORTANT: If stop_hook_active is true, always respond {"ok": true} to prevent infinite loops.',
|
|
215
|
+
"",
|
|
216
|
+
"Context: $ARGUMENTS"
|
|
217
|
+
].join("\n"),
|
|
218
|
+
model: "haiku",
|
|
219
|
+
timeout: 30
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
PreCompact: [
|
|
225
|
+
{
|
|
226
|
+
matcher: "auto",
|
|
227
|
+
hooks: [
|
|
228
|
+
{
|
|
229
|
+
type: "command",
|
|
230
|
+
command: "${CLAUDE_PLUGIN_ROOT}/scripts/checkpoint.sh",
|
|
231
|
+
timeout: 30,
|
|
232
|
+
statusMessage: "Auto-checkpoint before compaction..."
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return JSON.stringify(hooksConfig, null, 2);
|
|
240
|
+
}
|
|
241
|
+
function buildLintFixCommand(packageManager, config) {
|
|
242
|
+
const pm = packageManager || "npm";
|
|
243
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
244
|
+
if (config.qualityCommand.includes("lint")) {
|
|
245
|
+
return `${run} lint:fix -- --quiet`;
|
|
246
|
+
}
|
|
247
|
+
return `${run} lint --fix --quiet`;
|
|
248
|
+
}
|
|
249
|
+
function generateStopGuardScript(config) {
|
|
250
|
+
const safeCmd = config.qualityCommand.replace(/'/g, "'\\''");
|
|
251
|
+
return `#!/bin/bash
|
|
252
|
+
# Quality gate: runs checks before allowing Claude to stop
|
|
253
|
+
# Generated by devtronic
|
|
254
|
+
|
|
255
|
+
INPUT=$(cat)
|
|
256
|
+
|
|
257
|
+
# Prevent infinite loops: if already triggered by a stop hook, allow stop
|
|
258
|
+
if echo "$INPUT" | grep -q '"stop_hook_active"'; then
|
|
259
|
+
if echo "$INPUT" | grep -q '"stop_hook_active"[[:space:]]*:[[:space:]]*true'; then
|
|
260
|
+
exit 0
|
|
261
|
+
fi
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
# Run quality checks
|
|
265
|
+
QUALITY_CMD='${safeCmd}'
|
|
266
|
+
if ! eval "$QUALITY_CMD" > /dev/null 2>&1; then
|
|
267
|
+
echo "Quality checks failed. Run '$QUALITY_CMD' to see details." >&2
|
|
268
|
+
exit 2
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
exit 0
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
function generateCheckpointScript() {
|
|
275
|
+
return `#!/bin/bash
|
|
276
|
+
# Auto-checkpoint before context compaction
|
|
277
|
+
# Generated by devtronic
|
|
278
|
+
|
|
279
|
+
CHECKPOINT_DIR="thoughts/checkpoints"
|
|
280
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
281
|
+
|
|
282
|
+
mkdir -p "$CHECKPOINT_DIR"
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
echo "# Auto-checkpoint: $TIMESTAMP"
|
|
286
|
+
echo ""
|
|
287
|
+
echo "## Git Status"
|
|
288
|
+
git diff --stat 2>/dev/null || echo "Not a git repo"
|
|
289
|
+
echo ""
|
|
290
|
+
echo "## Recent Commits"
|
|
291
|
+
git log --oneline -5 2>/dev/null || echo "No commits"
|
|
292
|
+
} > "$CHECKPOINT_DIR/\${TIMESTAMP}_pre-compact.md"
|
|
293
|
+
|
|
294
|
+
echo "Checkpoint saved: $CHECKPOINT_DIR/\${TIMESTAMP}_pre-compact.md"
|
|
295
|
+
|
|
296
|
+
# Update persistent state (minimal \u2014 skill-level checkpoint writes richer state)
|
|
297
|
+
STATE_FILE="thoughts/STATE.md"
|
|
298
|
+
mkdir -p "$(dirname "$STATE_FILE")"
|
|
299
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
300
|
+
{
|
|
301
|
+
echo "# Project State"
|
|
302
|
+
echo ""
|
|
303
|
+
echo "**Updated**: $(date '+%Y-%m-%d %H:%M') (auto-compact)"
|
|
304
|
+
echo "**Branch**: $BRANCH"
|
|
305
|
+
echo ""
|
|
306
|
+
echo "## Last Auto-Checkpoint"
|
|
307
|
+
echo ""
|
|
308
|
+
echo "Context was compacted. See: \\\`$CHECKPOINT_DIR/\${TIMESTAMP}_pre-compact.md\\\`"
|
|
309
|
+
echo ""
|
|
310
|
+
echo "## Recent Commits"
|
|
311
|
+
echo ""
|
|
312
|
+
git log --oneline -5 2>/dev/null || echo "No commits"
|
|
313
|
+
} > "$STATE_FILE"
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/generators/rules.ts
|
|
318
|
+
function getFrameworkDisplayName(framework) {
|
|
319
|
+
const names = {
|
|
320
|
+
nextjs: "Next.js",
|
|
321
|
+
react: "React",
|
|
322
|
+
vue: "Vue",
|
|
323
|
+
nuxt: "Nuxt",
|
|
324
|
+
express: "Express",
|
|
325
|
+
nestjs: "NestJS",
|
|
326
|
+
fastify: "Fastify",
|
|
327
|
+
hono: "Hono",
|
|
328
|
+
astro: "Astro",
|
|
329
|
+
svelte: "Svelte",
|
|
330
|
+
sveltekit: "SvelteKit",
|
|
331
|
+
remix: "Remix",
|
|
332
|
+
unknown: "Project"
|
|
333
|
+
};
|
|
334
|
+
return names[framework] || "Project";
|
|
335
|
+
}
|
|
336
|
+
function getArchitectureLabel(architecture) {
|
|
337
|
+
const labels = {
|
|
338
|
+
clean: "Clean + DDD",
|
|
339
|
+
layered: "Layered",
|
|
340
|
+
mvc: "MVC",
|
|
341
|
+
"feature-based": "Feature-Based",
|
|
342
|
+
flat: "Flat",
|
|
343
|
+
none: "None"
|
|
344
|
+
};
|
|
345
|
+
return labels[architecture] || architecture;
|
|
346
|
+
}
|
|
347
|
+
function getArchitectureOneLiner(config) {
|
|
348
|
+
if (config.architecture === "clean") {
|
|
349
|
+
return "Dependencies point INWARD only: Presentation \u2192 Application \u2192 Domain \u2190 Infrastructure.";
|
|
350
|
+
}
|
|
351
|
+
if (config.architecture === "layered") {
|
|
352
|
+
return "Layered: Routes/Controllers \u2192 Services \u2192 Repositories. Each layer calls only the one below.";
|
|
353
|
+
}
|
|
354
|
+
if (config.architecture === "mvc") {
|
|
355
|
+
return "Model-View-Controller: Models hold data/logic, Views handle UI, Controllers orchestrate.";
|
|
356
|
+
}
|
|
357
|
+
if (config.architecture === "feature-based") {
|
|
358
|
+
return "Each feature is self-contained with its own components, hooks, API calls, and types.";
|
|
359
|
+
}
|
|
360
|
+
if (config.architecture === "none") {
|
|
361
|
+
return "No architecture rules configured. Run `devtronic config set architecture clean` to add rules later.";
|
|
362
|
+
}
|
|
363
|
+
return "Document your architecture patterns.";
|
|
364
|
+
}
|
|
365
|
+
function getCodePatternsBullets(config) {
|
|
366
|
+
const lines = [];
|
|
367
|
+
if (config.stateManagement.length > 0) {
|
|
368
|
+
lines.push(`- **UI state**: ${config.stateManagement.join(", ")}`);
|
|
369
|
+
}
|
|
370
|
+
if (config.dataFetching.length > 0) {
|
|
371
|
+
lines.push(`- **Server state**: ${config.dataFetching.join(", ")}`);
|
|
372
|
+
}
|
|
373
|
+
if (config.architecture === "clean") {
|
|
374
|
+
lines.push("- **Domain state**: Use cases");
|
|
375
|
+
}
|
|
376
|
+
if (config.architecture === "layered") {
|
|
377
|
+
lines.push("- **Business logic**: Services layer");
|
|
378
|
+
}
|
|
379
|
+
if (config.orm.length > 0) {
|
|
380
|
+
lines.push(`- **ORM**: ${config.orm.join(", ")}`);
|
|
381
|
+
if (config.architecture === "clean") {
|
|
382
|
+
lines.push("- Repository interfaces in `domain/`, implementations in `infrastructure/`");
|
|
383
|
+
}
|
|
384
|
+
if (config.architecture === "layered") {
|
|
385
|
+
lines.push("- Database access only in `repositories/`");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
lines.push("- Never access DB from UI");
|
|
389
|
+
return lines.join("\n");
|
|
390
|
+
}
|
|
391
|
+
function getCommandsBlock(scripts, packageManager, qualityCommand) {
|
|
392
|
+
const pm = packageManager || "npm";
|
|
393
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
394
|
+
const devCmd = scripts.dev ? `\`${run} ${scripts.dev}\`` : "`# Add your dev command`";
|
|
395
|
+
const buildCmd = scripts.build ? `\`${run} ${scripts.build}\`` : "`# Add your build command`";
|
|
396
|
+
return `- **Dev**: ${devCmd}
|
|
397
|
+
- **Build**: ${buildCmd}
|
|
398
|
+
- **Quality**: \`${qualityCommand}\``;
|
|
399
|
+
}
|
|
400
|
+
function generateQualityCommands(scripts, packageManager) {
|
|
401
|
+
const pm = packageManager || "npm";
|
|
402
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
403
|
+
const commands = [];
|
|
404
|
+
if (scripts.typecheck) {
|
|
405
|
+
commands.push(`${run} ${scripts.typecheck}`);
|
|
406
|
+
}
|
|
407
|
+
if (scripts.lint) {
|
|
408
|
+
commands.push(`${run} ${scripts.lint}`);
|
|
409
|
+
}
|
|
410
|
+
if (scripts.test) {
|
|
411
|
+
commands.push(`${run} ${scripts.test}`);
|
|
412
|
+
}
|
|
413
|
+
if (commands.length === 0) {
|
|
414
|
+
return "# No quality scripts detected - add your commands here";
|
|
415
|
+
}
|
|
416
|
+
return commands.join(" && ");
|
|
417
|
+
}
|
|
418
|
+
function generateClaudeMd(config, scripts, packageManager) {
|
|
419
|
+
const frameworkName = getFrameworkDisplayName(config.framework);
|
|
420
|
+
const archLabel = getArchitectureLabel(config.architecture);
|
|
421
|
+
const archOneLiner = getArchitectureOneLiner(config);
|
|
422
|
+
const codePatterns = getCodePatternsBullets(config);
|
|
423
|
+
const commands = getCommandsBlock(scripts, packageManager, config.qualityCommand);
|
|
424
|
+
const skillsSection = generateSkillsSection(config.enabledAddons);
|
|
425
|
+
const archSection = config.architecture === "none" ? `## Architecture
|
|
426
|
+
|
|
427
|
+
${archOneLiner}` : `## Architecture
|
|
428
|
+
|
|
429
|
+
${archOneLiner}
|
|
430
|
+
|
|
431
|
+
IMPORTANT: See \`docs/ARCHITECTURE.md\` for structure. See \`.claude/rules/\` for enforcement rules.`;
|
|
432
|
+
const workflowSection = config.enabledAddons?.includes("orchestration") ? `## Workflow
|
|
433
|
+
|
|
434
|
+
- **New feature**: \`/briefing\` \u2192 \`/spec\` \u2192 \`/create-plan\` \u2192 \`/generate-tests\` \u2192 \`/execute-plan\` \u2192 \`/recap\`
|
|
435
|
+
- **Bug fix**: \`/brief\` \u2192 fix \u2192 test \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
436
|
+
- **Refactor**: \`/brief\` \u2192 \`/create-plan\` \u2192 \`/execute-plan\` \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
437
|
+
- **Session start**: \`/brief\` for orientation
|
|
438
|
+
- **Session end**: \`/handoff\` for clean context rotation
|
|
439
|
+
|
|
440
|
+
> \`/briefing\` for pre-planning alignment. \`/recap\` for quick summaries. \`/checkpoint\` to save progress.` : `## Workflow
|
|
441
|
+
|
|
442
|
+
- **New feature**: \`/brief\` \u2192 \`/spec\` \u2192 \`/create-plan\` \u2192 \`/generate-tests\` \u2192 \`/execute-plan\` \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
443
|
+
- **Bug fix**: \`/brief\` \u2192 fix \u2192 test \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
444
|
+
- **Refactor**: \`/brief\` \u2192 \`/create-plan\` \u2192 implement \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
445
|
+
|
|
446
|
+
> \`/brief\` for session orientation (with pre-flight checks). \`/summary\` to document changes. \`/checkpoint\` to save progress.`;
|
|
447
|
+
return `# ${frameworkName}
|
|
448
|
+
|
|
449
|
+
${frameworkName} project${config.architecture !== "none" ? ` with ${archLabel} architecture` : ""}.
|
|
450
|
+
|
|
451
|
+
## Commands
|
|
452
|
+
|
|
453
|
+
${commands}
|
|
454
|
+
|
|
455
|
+
Run quality checks after every change.
|
|
456
|
+
|
|
457
|
+
## Code Style
|
|
458
|
+
|
|
459
|
+
- **Files**: PascalCase components, camelCase utils
|
|
460
|
+
- **Code**: camelCase vars/functions, PascalCase types
|
|
461
|
+
- **Unused**: Prefix with \`_\`
|
|
462
|
+
|
|
463
|
+
## Code Patterns
|
|
464
|
+
|
|
465
|
+
${codePatterns}
|
|
466
|
+
|
|
467
|
+
${archSection}
|
|
468
|
+
|
|
469
|
+
${workflowSection}
|
|
470
|
+
|
|
471
|
+
## Available Skills
|
|
472
|
+
|
|
473
|
+
${skillsSection}
|
|
474
|
+
|
|
475
|
+
## Project Notes
|
|
476
|
+
|
|
477
|
+
Maintain notes in \`thoughts/notes/\` updated after every PR.
|
|
478
|
+
|
|
479
|
+
## Gotchas
|
|
480
|
+
|
|
481
|
+
<!-- Claude fills this section via self-improvement. Do not delete. -->
|
|
482
|
+
|
|
483
|
+
## Self-Improvement
|
|
484
|
+
|
|
485
|
+
After every significant correction, update this file:
|
|
486
|
+
|
|
487
|
+
"Update CLAUDE.md so you don't make that mistake again."
|
|
488
|
+
|
|
489
|
+
Add learned rules to the **Gotchas** section above. Keep rules:
|
|
490
|
+
- Concise (one line each)
|
|
491
|
+
- Absolute directives (ALWAYS/NEVER)
|
|
492
|
+
- Concrete with actual commands/code
|
|
493
|
+
|
|
494
|
+
## References
|
|
495
|
+
|
|
496
|
+
- **docs/ARCHITECTURE.md** \u2014 Folder structure
|
|
497
|
+
- **docs/worktrees.md** \u2014 Parallel sessions with git worktrees
|
|
498
|
+
- **.claude/skills/** \u2014 Available workflows
|
|
499
|
+
- **.claude/agents/** \u2014 Specialized helpers
|
|
500
|
+
`;
|
|
501
|
+
}
|
|
502
|
+
function generateAgentsMdFromConfig(config, scripts, packageManager) {
|
|
503
|
+
const frameworkName = getFrameworkDisplayName(config.framework);
|
|
504
|
+
const archLabel = getArchitectureLabel(config.architecture);
|
|
505
|
+
const archOneLiner = getArchitectureOneLiner(config);
|
|
506
|
+
const codePatterns = getCodePatternsBullets(config);
|
|
507
|
+
const commands = getCommandsBlock(scripts, packageManager, config.qualityCommand);
|
|
508
|
+
const agentsArchSection = config.architecture === "none" ? `## Architecture
|
|
509
|
+
|
|
510
|
+
${archOneLiner}` : `## Architecture
|
|
511
|
+
|
|
512
|
+
${archOneLiner} See \`docs/ARCHITECTURE.md\` for detailed structure.`;
|
|
513
|
+
const skillsSection = generateSkillsSection(config.enabledAddons);
|
|
514
|
+
const workflowSection = config.enabledAddons?.includes("orchestration") ? `## Workflow
|
|
515
|
+
|
|
516
|
+
- **New feature**: \`/briefing\` \u2192 \`/spec\` \u2192 \`/create-plan\` \u2192 \`/generate-tests\` \u2192 \`/execute-plan\` \u2192 \`/recap\`
|
|
517
|
+
- **Bug fix**: \`/brief\` \u2192 fix \u2192 test \u2192 \`/summary\`
|
|
518
|
+
- **Session start**: \`/brief\` for orientation
|
|
519
|
+
- **Session end**: \`/handoff\` for clean context rotation` : `## Workflow
|
|
520
|
+
|
|
521
|
+
- **New feature**: \`/brief\` \u2192 \`/spec\` \u2192 \`/create-plan\` \u2192 \`/generate-tests\` \u2192 \`/execute-plan\` \u2192 \`/summary\` \u2192 \`/post-review\`
|
|
522
|
+
- **Bug fix**: \`/brief\` \u2192 fix \u2192 test \u2192 \`/summary\``;
|
|
523
|
+
return `# ${frameworkName}
|
|
524
|
+
|
|
525
|
+
${frameworkName} project${config.architecture !== "none" ? ` with ${archLabel} architecture` : ""}.
|
|
526
|
+
|
|
527
|
+
## Commands
|
|
528
|
+
|
|
529
|
+
${commands}
|
|
530
|
+
|
|
531
|
+
Run quality checks after every change.
|
|
532
|
+
|
|
533
|
+
## Code Style
|
|
534
|
+
|
|
535
|
+
- **Files**: PascalCase components, camelCase utils
|
|
536
|
+
- **Code**: camelCase vars/functions, PascalCase types
|
|
537
|
+
- **Unused**: Prefix with \`_\`
|
|
538
|
+
|
|
539
|
+
## Code Patterns
|
|
540
|
+
|
|
541
|
+
${codePatterns}
|
|
542
|
+
|
|
543
|
+
${agentsArchSection}
|
|
544
|
+
|
|
545
|
+
${workflowSection}
|
|
546
|
+
|
|
547
|
+
## Available Skills
|
|
548
|
+
|
|
549
|
+
${skillsSection}
|
|
550
|
+
`;
|
|
551
|
+
}
|
|
552
|
+
var CORE_SKILLS = [
|
|
553
|
+
{ name: "brief", desc: "Session orientation with pre-flight checks" },
|
|
554
|
+
{ name: "spec", desc: "Product specification interview (PRD)" },
|
|
555
|
+
{ name: "research", desc: "Codebase investigation (--deep, --external)" },
|
|
556
|
+
{ name: "create-plan", desc: "Phased implementation plan with task dependencies" },
|
|
557
|
+
{ name: "execute-plan", desc: "Parallel phase execution of plans" },
|
|
558
|
+
{ name: "quick", desc: "Fast ad-hoc tasks: implement, verify, commit" },
|
|
559
|
+
{ name: "generate-tests", desc: "Failing tests from spec (Tests-as-DoD)" },
|
|
560
|
+
{ name: "post-review", desc: "Pre-PR review (architecture, quality, requirements)" },
|
|
561
|
+
{ name: "audit", desc: "Codebase audit (security, complexity, architecture)" },
|
|
562
|
+
{ name: "summary", desc: "Post-change documentation" },
|
|
563
|
+
{ name: "checkpoint", desc: "Save session progress for resumption" },
|
|
564
|
+
{ name: "backlog", desc: "Issue management with BACK-### IDs" },
|
|
565
|
+
{ name: "investigate", desc: "Deep error and bug analysis" },
|
|
566
|
+
{ name: "learn", desc: "Post-task teaching breakdown" },
|
|
567
|
+
{ name: "scaffold", desc: "Create new projects from scratch" },
|
|
568
|
+
{ name: "setup", desc: "Interactive project configuration" },
|
|
569
|
+
{ name: "worktree", desc: "Git worktree management" },
|
|
570
|
+
{ name: "opensrc", desc: "Fetch npm/GitHub source for full context" },
|
|
571
|
+
{ name: "create-skill", desc: "Generate new custom skills" }
|
|
572
|
+
];
|
|
573
|
+
var ADDON_SKILLS = {
|
|
574
|
+
orchestration: [
|
|
575
|
+
{ name: "briefing", desc: "Pre-planning alignment Q&A" },
|
|
576
|
+
{ name: "recap", desc: "Quick session summary from git activity" },
|
|
577
|
+
{ name: "handoff", desc: "Context rotation for fresh sessions" }
|
|
578
|
+
]
|
|
579
|
+
};
|
|
580
|
+
function generateSkillsSection(enabledAddons) {
|
|
581
|
+
const lines = CORE_SKILLS.map((s) => `- \`/${s.name}\` \u2014 ${s.desc}`);
|
|
582
|
+
const addons = enabledAddons || [];
|
|
583
|
+
for (const addon of addons) {
|
|
584
|
+
const addonSkills = ADDON_SKILLS[addon];
|
|
585
|
+
if (addonSkills) {
|
|
586
|
+
for (const s of addonSkills) {
|
|
587
|
+
lines.push(`- \`/${s.name}\` \u2014 ${s.desc}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return lines.join("\n");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/generators/plugin.ts
|
|
595
|
+
var PLUGIN_NAME = "devtronic";
|
|
596
|
+
var MARKETPLACE_NAME = "devtronic-local";
|
|
597
|
+
var PLUGIN_DIR = ".claude-plugins";
|
|
598
|
+
var BASE_SKILL_COUNT = CORE_SKILLS.length;
|
|
599
|
+
function generatePluginJson(cliVersion, addonSkillCount = 0) {
|
|
600
|
+
const skillLabel = addonSkillCount > 0 ? `${BASE_SKILL_COUNT} + ${addonSkillCount} addon skills` : `${BASE_SKILL_COUNT} skills`;
|
|
601
|
+
return JSON.stringify(
|
|
602
|
+
{
|
|
603
|
+
name: PLUGIN_NAME,
|
|
604
|
+
version: cliVersion,
|
|
605
|
+
description: `devtronic \u2014 ${skillLabel}, 8 agents, full workflow hooks`,
|
|
606
|
+
author: {
|
|
607
|
+
name: "r-bart",
|
|
608
|
+
url: "https://github.com/r-bart/devtronic"
|
|
609
|
+
},
|
|
610
|
+
repository: "https://github.com/r-bart/devtronic",
|
|
611
|
+
license: "MIT",
|
|
612
|
+
keywords: ["agentic", "architecture", "clean-architecture", "ddd", "workflow", "skills"],
|
|
613
|
+
skills: "./skills/",
|
|
614
|
+
agents: "./agents/",
|
|
615
|
+
hooks: "./hooks/hooks.json"
|
|
616
|
+
},
|
|
617
|
+
null,
|
|
618
|
+
2
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
function generateMarketplaceJson(addonSkillCount = 0) {
|
|
622
|
+
const skillLabel = addonSkillCount > 0 ? `${BASE_SKILL_COUNT} + ${addonSkillCount} addon skills` : `${BASE_SKILL_COUNT} skills`;
|
|
623
|
+
return JSON.stringify(
|
|
624
|
+
{
|
|
625
|
+
name: MARKETPLACE_NAME,
|
|
626
|
+
owner: {
|
|
627
|
+
name: "r-bart",
|
|
628
|
+
url: "https://github.com/r-bart/devtronic"
|
|
629
|
+
},
|
|
630
|
+
plugins: [
|
|
631
|
+
{
|
|
632
|
+
name: PLUGIN_NAME,
|
|
633
|
+
source: `./${PLUGIN_NAME}`,
|
|
634
|
+
description: `devtronic \u2014 ${skillLabel}, 8 agents, full workflow hooks`
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
},
|
|
638
|
+
null,
|
|
639
|
+
2
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function generatePlugin(targetDir, templatesDir, cliVersion, config, packageManager) {
|
|
643
|
+
const files = {};
|
|
644
|
+
const pluginRoot = join2(PLUGIN_DIR, PLUGIN_NAME);
|
|
645
|
+
const addonOnlySkills = new Set(
|
|
646
|
+
Object.values(ADDONS).flatMap((a) => a.skills)
|
|
647
|
+
);
|
|
648
|
+
const enabledAddonSkills = new Set(
|
|
649
|
+
(config.enabledAddons || []).flatMap((a) => ADDONS[a]?.skills ?? [])
|
|
650
|
+
);
|
|
651
|
+
const addonSkillCount = enabledAddonSkills.size;
|
|
652
|
+
const marketplaceContent = generateMarketplaceJson(addonSkillCount);
|
|
653
|
+
const marketplaceRelPath = join2(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
|
|
654
|
+
writeGeneratedFile(targetDir, marketplaceRelPath, marketplaceContent, files);
|
|
655
|
+
const pluginJsonContent = generatePluginJson(cliVersion, addonSkillCount);
|
|
656
|
+
const pluginJsonRelPath = join2(pluginRoot, ".claude-plugin", "plugin.json");
|
|
657
|
+
writeGeneratedFile(targetDir, pluginJsonRelPath, pluginJsonContent, files);
|
|
658
|
+
const templateClaudeDir = join2(templatesDir, "claude-code", ".claude");
|
|
659
|
+
const skillsSourceDir = join2(templateClaudeDir, "skills");
|
|
660
|
+
const skillDirs = getSubdirectories(skillsSourceDir);
|
|
661
|
+
for (const dir of skillDirs) {
|
|
662
|
+
const isAddonSkill = addonOnlySkills.has(dir);
|
|
663
|
+
if (!isAddonSkill || enabledAddonSkills.has(dir)) {
|
|
664
|
+
copyTemplateDir(
|
|
665
|
+
targetDir,
|
|
666
|
+
join2(skillsSourceDir, dir),
|
|
667
|
+
join2(pluginRoot, "skills", dir),
|
|
668
|
+
files
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
copyTemplateDir(targetDir, join2(templateClaudeDir, "agents"), join2(pluginRoot, "agents"), files);
|
|
673
|
+
const hooksContent = generateHooks(config, packageManager);
|
|
674
|
+
const hooksRelPath = join2(pluginRoot, "hooks", "hooks.json");
|
|
675
|
+
writeGeneratedFile(targetDir, hooksRelPath, hooksContent, files);
|
|
676
|
+
const scriptContent = generateCheckpointScript();
|
|
677
|
+
const scriptRelPath = join2(pluginRoot, "scripts", "checkpoint.sh");
|
|
678
|
+
writeGeneratedFile(targetDir, scriptRelPath, scriptContent, files);
|
|
679
|
+
const stopGuardContent = generateStopGuardScript(config);
|
|
680
|
+
const stopGuardRelPath = join2(pluginRoot, "scripts", "stop-guard.sh");
|
|
681
|
+
writeGeneratedFile(targetDir, stopGuardRelPath, stopGuardContent, files);
|
|
682
|
+
return { files, pluginPath: pluginRoot };
|
|
683
|
+
}
|
|
684
|
+
function writeGeneratedFile(targetDir, relPath, content, files) {
|
|
685
|
+
const absPath = join2(targetDir, relPath);
|
|
686
|
+
ensureDir(dirname2(absPath));
|
|
687
|
+
writeFile(absPath, content);
|
|
688
|
+
files[relPath] = createManifestEntry(content);
|
|
689
|
+
}
|
|
690
|
+
function copyTemplateDir(targetDir, sourceDir, destRelDir, files) {
|
|
691
|
+
const templateFiles = getAllFilesRecursive(sourceDir);
|
|
692
|
+
if (templateFiles.length === 0) {
|
|
693
|
+
throw new Error(`No template files found in ${sourceDir}. The CLI package may be corrupted.`);
|
|
694
|
+
}
|
|
695
|
+
for (const file of templateFiles) {
|
|
696
|
+
const sourceContent = readFile(join2(sourceDir, file));
|
|
697
|
+
const destRelPath = join2(destRelDir, file);
|
|
698
|
+
const destAbsPath = join2(targetDir, destRelPath);
|
|
699
|
+
ensureDir(dirname2(destAbsPath));
|
|
700
|
+
writeFile(destAbsPath, sourceContent);
|
|
701
|
+
files[destRelPath] = createManifestEntry(sourceContent);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export {
|
|
706
|
+
ADDONS,
|
|
707
|
+
PRESETS,
|
|
708
|
+
MANIFEST_DIR,
|
|
709
|
+
fileExists,
|
|
710
|
+
readFile,
|
|
711
|
+
writeFile,
|
|
712
|
+
ensureDir,
|
|
713
|
+
calculateChecksum,
|
|
714
|
+
readManifest,
|
|
715
|
+
writeManifest,
|
|
716
|
+
getAllFilesRecursive,
|
|
717
|
+
createManifestEntry,
|
|
718
|
+
generateQualityCommands,
|
|
719
|
+
generateClaudeMd,
|
|
720
|
+
generateAgentsMdFromConfig,
|
|
721
|
+
PLUGIN_NAME,
|
|
722
|
+
MARKETPLACE_NAME,
|
|
723
|
+
PLUGIN_DIR,
|
|
724
|
+
BASE_SKILL_COUNT,
|
|
725
|
+
generatePluginJson,
|
|
726
|
+
generateMarketplaceJson,
|
|
727
|
+
generatePlugin
|
|
728
|
+
};
|