@hopla/claude-setup 1.17.1 → 2.1.1
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/.claude-plugin/marketplace.json +2 -1
- package/.claude-plugin/plugin.json +4 -3
- package/CHANGELOG.md +123 -0
- package/LICENSE +21 -0
- package/README.md +65 -39
- package/agents/system-reviewer.md +4 -4
- package/cli.js +288 -8
- package/commands/archive.md +137 -0
- package/commands/execute.md +1 -1
- package/commands/guides/ai-optimized-codebase.md +5 -1
- package/commands/guides/data-audit.md +4 -0
- package/commands/guides/hooks-reference.md +4 -0
- package/commands/guides/mcp-integration.md +6 -2
- package/commands/guides/remote-coding.md +5 -1
- package/commands/guides/review-checklist.md +4 -0
- package/commands/guides/scaling-beyond-engineering.md +4 -0
- package/commands/guides/validation-pyramid.md +5 -1
- package/commands/guides/write-skill.md +4 -0
- package/commands/init-project.md +38 -17
- package/commands/plan-feature.md +21 -3
- package/commands/system-review.md +11 -15
- package/commands/validate.md +1 -1
- package/hooks/prompt-route.js +244 -91
- package/hooks/session-prime.js +12 -4
- package/hooks/statusline.js +3 -5
- package/hooks/tsc-check.js +40 -3
- package/package.json +16 -6
- package/skills/brainstorm/SKILL.md +18 -2
- package/skills/code-review/SKILL.md +5 -0
- package/skills/code-review/checklist.md +1 -1
- package/skills/debug/SKILL.md +1 -1
- package/skills/execution-report/SKILL.md +1 -1
- package/skills/execution-report/report-structure.md +2 -2
- package/skills/git/commit.md +2 -2
- package/skills/git/pr.md +1 -1
- package/skills/hook-audit/SKILL.md +135 -0
- package/skills/hook-audit/checklist.md +210 -0
- package/skills/hook-audit/tests/fixtures/use-bad.ts.example +53 -0
- package/skills/hook-audit/tests/fixtures/use-good.ts.example +64 -0
- package/skills/hook-audit/tests/manual-test.sh +73 -0
- package/skills/performance/SKILL.md +1 -1
- package/skills/prime/SKILL.md +2 -2
- package/skills/refactoring/SKILL.md +1 -1
- package/skills/subagent-execution/SKILL.md +2 -2
- package/skills/verify/SKILL.md +1 -1
- package/skills/worktree/SKILL.md +2 -2
package/cli.js
CHANGED
|
@@ -4,12 +4,17 @@ import fs from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import os from "os";
|
|
6
6
|
import readline from "readline";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
|
|
8
9
|
const FORCE = process.argv.includes("--force");
|
|
9
10
|
const UNINSTALL = process.argv.includes("--uninstall");
|
|
10
11
|
const MIGRATE = process.argv.includes("--migrate");
|
|
11
12
|
const VERSION = process.argv.includes("--version") || process.argv.includes("-v");
|
|
12
13
|
const DRY_RUN = process.argv.includes("--dry-run");
|
|
14
|
+
const STATUS = process.argv.includes("status");
|
|
15
|
+
const JSON_OUT = process.argv.includes("--json");
|
|
16
|
+
const SETUP_STATUSLINE = process.argv.includes("--setup-statusline");
|
|
17
|
+
const REMOVE_STATUSLINE = process.argv.includes("--remove-statusline");
|
|
13
18
|
|
|
14
19
|
if (VERSION) {
|
|
15
20
|
const pkg = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
@@ -24,6 +29,10 @@ const HOOKS_DIR = path.join(CLAUDE_DIR, "hooks");
|
|
|
24
29
|
const AGENTS_DIR = path.join(CLAUDE_DIR, "agents");
|
|
25
30
|
const PLUGINS_DIR = path.join(CLAUDE_DIR, "plugins");
|
|
26
31
|
const MARKETPLACE_CACHE = path.join(PLUGINS_DIR, "marketplaces", "hopla-marketplace");
|
|
32
|
+
const STATUSLINE_SCRIPT = path.join(MARKETPLACE_CACHE, "hooks", "statusline.js");
|
|
33
|
+
// Substring used to identify a statusLine block managed by this plugin.
|
|
34
|
+
// Survives manual renames of the marketplace as long as users keep "hopla" in the path.
|
|
35
|
+
const STATUSLINE_MARKER = "hopla-marketplace";
|
|
27
36
|
const SETTINGS_FILES = [
|
|
28
37
|
path.join(CLAUDE_DIR, "settings.json"),
|
|
29
38
|
path.join(CLAUDE_DIR, "settings.local.json"),
|
|
@@ -52,8 +61,9 @@ function safeRm(target, opts = {}) {
|
|
|
52
61
|
|
|
53
62
|
// Atomic write: stage to a tmp file and rename over the target.
|
|
54
63
|
// Protects ~/.claude/settings.json from corruption if the process crashes mid-write.
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
// Exported for testing — when imported as a library, callers can pass dryRun explicitly.
|
|
65
|
+
export function safeWrite(target, content, { dryRun = DRY_RUN } = {}) {
|
|
66
|
+
if (dryRun) return;
|
|
57
67
|
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
58
68
|
try {
|
|
59
69
|
fs.writeFileSync(tmp, content);
|
|
@@ -91,7 +101,8 @@ function logRemoved(label) {
|
|
|
91
101
|
// missing. Warns (and returns null) when the file exists but is not valid JSON
|
|
92
102
|
// — previously these failures were silently swallowed, causing cleanup and
|
|
93
103
|
// permission updates to skip with no signal to the user.
|
|
94
|
-
|
|
104
|
+
// Exported for unit testing.
|
|
105
|
+
export function parseSettingsFile(settingsPath) {
|
|
95
106
|
if (!fs.existsSync(settingsPath)) return null;
|
|
96
107
|
try {
|
|
97
108
|
return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
@@ -144,6 +155,23 @@ const LEGACY_HOOK_COMMANDS = [
|
|
|
144
155
|
"session-prime.js",
|
|
145
156
|
];
|
|
146
157
|
|
|
158
|
+
// Guide files the pre-plugin CLI (≤ v1.11.x) copied to ~/.claude/commands/guides/.
|
|
159
|
+
// The plugin now ships them via commands/guides/, namespaced as /hopla:guides:<name>,
|
|
160
|
+
// so the user-level copies show up as duplicates in autocomplete (no "(hopla)" suffix).
|
|
161
|
+
// Cleanup removes only files whose name matches a plugin-shipped guide, leaving any
|
|
162
|
+
// custom user guides in ~/.claude/commands/guides/ untouched.
|
|
163
|
+
const LEGACY_GUIDE_FILES = [
|
|
164
|
+
"ai-optimized-codebase.md",
|
|
165
|
+
"data-audit.md",
|
|
166
|
+
"hooks-reference.md",
|
|
167
|
+
"mcp-integration.md",
|
|
168
|
+
"remote-coding.md",
|
|
169
|
+
"review-checklist.md",
|
|
170
|
+
"scaling-beyond-engineering.md",
|
|
171
|
+
"validation-pyramid.md",
|
|
172
|
+
"write-skill.md",
|
|
173
|
+
];
|
|
174
|
+
|
|
147
175
|
// Agents installed directly by v1.11.0 and v1.12.0 (no hopla- prefix)
|
|
148
176
|
// Must be cleaned up so the plugin-provided versions are the only source of truth
|
|
149
177
|
const LEGACY_AGENT_FILES = [
|
|
@@ -188,6 +216,26 @@ function removeLegacyFiles() {
|
|
|
188
216
|
}
|
|
189
217
|
}
|
|
190
218
|
|
|
219
|
+
// Legacy guide duplicates in ~/.claude/commands/guides/ (pre-plugin CLI).
|
|
220
|
+
// Only remove files matching a guide the plugin currently ships; preserve
|
|
221
|
+
// any custom user-created guides in the same directory.
|
|
222
|
+
const legacyGuidesDir = path.join(COMMANDS_DIR, "guides");
|
|
223
|
+
if (fs.existsSync(legacyGuidesDir)) {
|
|
224
|
+
for (const guideFile of LEGACY_GUIDE_FILES) {
|
|
225
|
+
const guidePath = path.join(legacyGuidesDir, guideFile);
|
|
226
|
+
if (fs.existsSync(guidePath)) {
|
|
227
|
+
safeRm(guidePath);
|
|
228
|
+
removed.push(`~/.claude/commands/guides/${guideFile}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!DRY_RUN) {
|
|
232
|
+
try {
|
|
233
|
+
const remaining = fs.readdirSync(legacyGuidesDir);
|
|
234
|
+
if (remaining.length === 0) fs.rmSync(legacyGuidesDir, { recursive: true });
|
|
235
|
+
} catch { /* ignore */ }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
191
239
|
// hopla-* skills
|
|
192
240
|
if (fs.existsSync(SKILLS_DIR)) {
|
|
193
241
|
for (const entry of fs.readdirSync(SKILLS_DIR)) {
|
|
@@ -369,6 +417,11 @@ async function uninstall() {
|
|
|
369
417
|
logRemoved(item);
|
|
370
418
|
}
|
|
371
419
|
|
|
420
|
+
const statuslineRemoved = removeStatuslineFromSettings();
|
|
421
|
+
for (const item of statuslineRemoved) {
|
|
422
|
+
logRemoved(item);
|
|
423
|
+
}
|
|
424
|
+
|
|
372
425
|
log(`\n${GREEN}${BOLD}Done!${RESET} ${DRY_RUN ? "Dry-run complete — no files were changed." : "CLI-managed files removed."}\n`);
|
|
373
426
|
|
|
374
427
|
if (pluginActive) {
|
|
@@ -422,6 +475,82 @@ async function setupPermissions() {
|
|
|
422
475
|
log(` ${GREEN}✓${RESET} Permissions configured.\n`);
|
|
423
476
|
}
|
|
424
477
|
|
|
478
|
+
async function setupStatusline() {
|
|
479
|
+
log(`\n${BOLD}@hopla/claude-setup${RESET} — Setup statusline${dryTag()}\n`);
|
|
480
|
+
|
|
481
|
+
if (!fs.existsSync(STATUSLINE_SCRIPT)) {
|
|
482
|
+
log(`${RED}✕${RESET} Plugin not detected at ${STATUSLINE_SCRIPT}`);
|
|
483
|
+
log(` Install the plugin first inside Claude Code:`);
|
|
484
|
+
log(` ${CYAN}/plugin marketplace add HOPLAtools/claude-setup${RESET}`);
|
|
485
|
+
log(` ${CYAN}/plugin install hopla@hopla-marketplace${RESET}\n`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const settingsPath = path.join(CLAUDE_DIR, "settings.json");
|
|
490
|
+
let settings = parseSettingsFile(settingsPath);
|
|
491
|
+
if (!settings) {
|
|
492
|
+
if (fs.existsSync(settingsPath)) {
|
|
493
|
+
log(` ${YELLOW}↷${RESET} Skipped — settings.json is not valid JSON. Fix it and re-run.\n`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
settings = {};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const command = `node ${STATUSLINE_SCRIPT}`;
|
|
500
|
+
const newBlock = { type: "command", command };
|
|
501
|
+
|
|
502
|
+
const existing = settings.statusLine;
|
|
503
|
+
if (existing && typeof existing === "object") {
|
|
504
|
+
const isOurs = typeof existing.command === "string" && existing.command.includes(STATUSLINE_MARKER);
|
|
505
|
+
if (isOurs && existing.command === command) {
|
|
506
|
+
log(`${GREEN}✓${RESET} Statusline already configured (idempotent — no changes).\n`);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!isOurs) {
|
|
510
|
+
log(` ${YELLOW}⚠${RESET} Existing statusLine points elsewhere:`);
|
|
511
|
+
log(` ${existing.command || JSON.stringify(existing)}`);
|
|
512
|
+
const ok = await confirm(` Overwrite with Hopla statusline? (y/N) `);
|
|
513
|
+
if (!ok) {
|
|
514
|
+
log(` ${YELLOW}↷${RESET} Kept existing statusLine. No changes.\n`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
settings.statusLine = newBlock;
|
|
521
|
+
safeWrite(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
522
|
+
log(` ${GREEN}✓${RESET} ${DRY_RUN ? "Would write" : "Wrote"} statusLine to ~/.claude/settings.json:`);
|
|
523
|
+
log(` ${CYAN}${command}${RESET}\n`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function removeStatuslineFromSettings() {
|
|
527
|
+
const removed = [];
|
|
528
|
+
const settingsPath = path.join(CLAUDE_DIR, "settings.json");
|
|
529
|
+
const settings = parseSettingsFile(settingsPath);
|
|
530
|
+
if (!settings || !settings.statusLine) return removed;
|
|
531
|
+
|
|
532
|
+
const cmd = settings.statusLine.command;
|
|
533
|
+
if (typeof cmd === "string" && cmd.includes(STATUSLINE_MARKER)) {
|
|
534
|
+
delete settings.statusLine;
|
|
535
|
+
safeWrite(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
536
|
+
removed.push(`statusLine from settings.json`);
|
|
537
|
+
}
|
|
538
|
+
return removed;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function removeStatusline() {
|
|
542
|
+
log(`\n${BOLD}@hopla/claude-setup${RESET} — Remove statusline${dryTag()}\n`);
|
|
543
|
+
const removed = removeStatuslineFromSettings();
|
|
544
|
+
if (removed.length === 0) {
|
|
545
|
+
log(`${GREEN}✓${RESET} No Hopla statusline found. Nothing to remove.\n`);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
for (const item of removed) {
|
|
549
|
+
logRemoved(item);
|
|
550
|
+
}
|
|
551
|
+
log("");
|
|
552
|
+
}
|
|
553
|
+
|
|
425
554
|
async function install() {
|
|
426
555
|
log(`\n${BOLD}@hopla/claude-setup${RESET} — Global Rules Setup${dryTag()}\n`);
|
|
427
556
|
|
|
@@ -453,8 +582,159 @@ async function install() {
|
|
|
453
582
|
await setupPermissions();
|
|
454
583
|
}
|
|
455
584
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
585
|
+
// =====================================================================
|
|
586
|
+
// status — read-only inspection of a project's .agents/ workflow state
|
|
587
|
+
// =====================================================================
|
|
588
|
+
|
|
589
|
+
// Lists *.md files (and *.draft.md) in a directory. Returns [] if missing
|
|
590
|
+
// or unreadable. Sorted alphabetically for stable output.
|
|
591
|
+
function listMarkdownFiles(dir) {
|
|
592
|
+
if (!fs.existsSync(dir)) return [];
|
|
593
|
+
try {
|
|
594
|
+
return fs.readdirSync(dir)
|
|
595
|
+
.filter((f) => f.endsWith(".md") && !f.startsWith("."))
|
|
596
|
+
.sort();
|
|
597
|
+
} catch {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Best-effort git inspection — silently degrades when git is unavailable
|
|
603
|
+
// or the cwd is not inside a repo. The status command must never fail
|
|
604
|
+
// because of git.
|
|
605
|
+
function readGitState(cwd) {
|
|
606
|
+
const exec = (cmd) => {
|
|
607
|
+
try {
|
|
608
|
+
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
609
|
+
} catch {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
const insideRepo = exec("git rev-parse --is-inside-work-tree");
|
|
614
|
+
if (insideRepo !== "true") return { in_repo: false };
|
|
615
|
+
const branch = exec("git rev-parse --abbrev-ref HEAD");
|
|
616
|
+
const statusShort = exec("git status --short");
|
|
617
|
+
const uncommitted = statusShort ? statusShort.split("\n").filter(Boolean).length : 0;
|
|
618
|
+
return {
|
|
619
|
+
in_repo: true,
|
|
620
|
+
branch: branch || "(detached)",
|
|
621
|
+
uncommitted,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function readWorkflowState(cwd) {
|
|
626
|
+
const agentsDir = path.join(cwd, ".agents");
|
|
627
|
+
const present = fs.existsSync(agentsDir);
|
|
628
|
+
|
|
629
|
+
const plansDir = path.join(agentsDir, "plans");
|
|
630
|
+
const allPlanFiles = listMarkdownFiles(plansDir);
|
|
631
|
+
const draft = allPlanFiles.filter((f) => f.endsWith(".draft.md"));
|
|
632
|
+
const active = allPlanFiles.filter((f) => !f.endsWith(".draft.md"));
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
agents_dir_present: present,
|
|
636
|
+
plans: {
|
|
637
|
+
draft,
|
|
638
|
+
active,
|
|
639
|
+
done: listMarkdownFiles(path.join(plansDir, "done")),
|
|
640
|
+
backlog: listMarkdownFiles(path.join(plansDir, "backlog")),
|
|
641
|
+
},
|
|
642
|
+
specs: listMarkdownFiles(path.join(agentsDir, "specs")),
|
|
643
|
+
code_reviews: listMarkdownFiles(path.join(agentsDir, "code-reviews")),
|
|
644
|
+
execution_reports: listMarkdownFiles(path.join(agentsDir, "execution-reports")),
|
|
645
|
+
system_reviews: listMarkdownFiles(path.join(agentsDir, "system-reviews")),
|
|
646
|
+
rca: listMarkdownFiles(path.join(agentsDir, "rca")),
|
|
647
|
+
audits: listMarkdownFiles(path.join(agentsDir, "audits")),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function suggestNext(state) {
|
|
652
|
+
if (!state.agents_dir_present) {
|
|
653
|
+
return "No .agents/ found — run /hopla:init-project to scaffold the workflow.";
|
|
654
|
+
}
|
|
655
|
+
if (state.plans.draft.length > 0) {
|
|
656
|
+
return `Plan in draft (${state.plans.draft[0]}) — run /hopla:review-plan or finalize it.`;
|
|
657
|
+
}
|
|
658
|
+
if (state.plans.active.length > 0) {
|
|
659
|
+
const plan = state.plans.active[0];
|
|
660
|
+
const baseName = plan.replace(/\.md$/, "");
|
|
661
|
+
const hasReport = state.execution_reports.some((r) => r.includes(baseName));
|
|
662
|
+
const hasReview = state.code_reviews.some((r) => r.includes(baseName));
|
|
663
|
+
if (!hasReview) return `Active plan (${plan}) — execute it or run code-review skill on changes.`;
|
|
664
|
+
if (!hasReport) return `Active plan (${plan}) reviewed — run execution-report skill.`;
|
|
665
|
+
return `Active plan (${plan}) reviewed and reported — consider /hopla:archive or /hopla:system-review.`;
|
|
666
|
+
}
|
|
667
|
+
if (state.code_reviews.length > 0) {
|
|
668
|
+
return `Pending code reviews — run /hopla:code-review-fix on them.`;
|
|
669
|
+
}
|
|
670
|
+
return "Workflow clean — start with /hopla:plan-feature.";
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function status() {
|
|
674
|
+
const cwd = process.cwd();
|
|
675
|
+
const git = readGitState(cwd);
|
|
676
|
+
const workflow = readWorkflowState(cwd);
|
|
677
|
+
const next = suggestNext(workflow);
|
|
678
|
+
|
|
679
|
+
if (JSON_OUT) {
|
|
680
|
+
process.stdout.write(JSON.stringify({ cwd, git, ...workflow, next }, null, 2) + "\n");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
log(`\n${BOLD}@hopla/claude-setup${RESET} — status (${cwd})\n`);
|
|
685
|
+
|
|
686
|
+
if (git.in_repo) {
|
|
687
|
+
log(`${CYAN}Git:${RESET}`);
|
|
688
|
+
log(` Branch: ${git.branch}`);
|
|
689
|
+
log(` Uncommitted: ${git.uncommitted} ${git.uncommitted === 1 ? "file" : "files"}`);
|
|
690
|
+
log("");
|
|
691
|
+
} else {
|
|
692
|
+
log(`${YELLOW}Not inside a git repository.${RESET}\n`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (!workflow.agents_dir_present) {
|
|
696
|
+
log(`${YELLOW}No .agents/ directory found.${RESET}`);
|
|
697
|
+
log(`Run ${CYAN}/hopla:init-project${RESET} to scaffold it.\n`);
|
|
698
|
+
log(`${BOLD}Suggested next:${RESET} ${next}\n`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
log(`${CYAN}Plans:${RESET}`);
|
|
703
|
+
log(` Draft (${workflow.plans.draft.length}): ${workflow.plans.draft.join(", ") || "—"}`);
|
|
704
|
+
log(` Active (${workflow.plans.active.length}): ${workflow.plans.active.join(", ") || "—"}`);
|
|
705
|
+
log(` Done (${workflow.plans.done.length}): ${workflow.plans.done.join(", ") || "—"}`);
|
|
706
|
+
log(` Backlog (${workflow.plans.backlog.length}): ${workflow.plans.backlog.join(", ") || "—"}`);
|
|
707
|
+
log("");
|
|
708
|
+
|
|
709
|
+
log(`${CYAN}Other artifacts:${RESET}`);
|
|
710
|
+
log(` Specs: ${workflow.specs.length}`);
|
|
711
|
+
log(` Code reviews: ${workflow.code_reviews.length}`);
|
|
712
|
+
log(` Execution reports: ${workflow.execution_reports.length}`);
|
|
713
|
+
log(` System reviews: ${workflow.system_reviews.length}`);
|
|
714
|
+
log(` RCAs: ${workflow.rca.length}`);
|
|
715
|
+
log(` Audits: ${workflow.audits.length}`);
|
|
716
|
+
log("");
|
|
717
|
+
|
|
718
|
+
log(`${BOLD}Suggested next:${RESET} ${next}\n`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const run = STATUS
|
|
722
|
+
? async () => status()
|
|
723
|
+
: SETUP_STATUSLINE
|
|
724
|
+
? setupStatusline
|
|
725
|
+
: REMOVE_STATUSLINE
|
|
726
|
+
? removeStatusline
|
|
727
|
+
: (UNINSTALL ? uninstall : (MIGRATE ? migrate : install));
|
|
728
|
+
|
|
729
|
+
// Only invoke the dispatcher when this file is executed directly (e.g. via
|
|
730
|
+
// `node cli.js` or the npm bin). When the file is imported as a library
|
|
731
|
+
// (tests), skip — tests call the exported helpers themselves.
|
|
732
|
+
import { pathToFileURL } from "url";
|
|
733
|
+
const isMainModule = process.argv[1]
|
|
734
|
+
&& import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
735
|
+
if (isMainModule) {
|
|
736
|
+
run().catch((err) => {
|
|
737
|
+
console.error("Failed:", err.message);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Archive a completed plan — fold its delta-specs into canonical specs and move artifacts to archive locations
|
|
3
|
+
argument-hint: "<plan-file-path>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> 🌐 **Language:** All user-facing output must match the user's language. Code, paths, and commands stay in English.
|
|
7
|
+
|
|
8
|
+
Close the lifecycle of a completed plan: fold its proposed requirement changes into the canonical specs, move the plan and design files to their archive locations, and leave the project in a clean post-feature state.
|
|
9
|
+
|
|
10
|
+
> Use this command **after** `/hopla:execute` has finished, code-review and execution-report skills have run, and changes are committed (or staged for the merge commit). The command does not write code — it only updates docs and moves files.
|
|
11
|
+
|
|
12
|
+
## Step 0: Locate Inputs
|
|
13
|
+
|
|
14
|
+
The first argument `$1` should be the path to the completed plan (e.g. `.agents/plans/add-auth.md`). If the user did not pass a path:
|
|
15
|
+
|
|
16
|
+
1. Run `node ~/.claude/plugins/marketplaces/hopla-marketplace/cli.js status` — or `claude-setup status` if installed via npm — to list active plans.
|
|
17
|
+
2. Ask the user which one to archive.
|
|
18
|
+
|
|
19
|
+
Derive the **feature slug** from the filename (e.g. `add-auth.md` → `add-auth`).
|
|
20
|
+
|
|
21
|
+
Then look for related artifacts:
|
|
22
|
+
|
|
23
|
+
| Artifact | Expected path |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Plan | `$1` (e.g. `.agents/plans/add-auth.md`) |
|
|
26
|
+
| Design spec | `.agents/specs/<slug>.md` or `.agents/specs/YYYY-MM-DD-<slug>.md` (latest match) |
|
|
27
|
+
| Code review | `.agents/code-reviews/<slug>.md` (ephemeral — do NOT preserve) |
|
|
28
|
+
| Execution report | `.agents/execution-reports/<slug>.md` (preserve) |
|
|
29
|
+
| System review | `.agents/system-reviews/<slug>-review.md` (preserve) |
|
|
30
|
+
|
|
31
|
+
If the design spec is missing, that's OK — many features start from `/hopla:plan-feature` directly without a brainstorm step. Skip the spec-merge phase.
|
|
32
|
+
|
|
33
|
+
## Step 1: Read the Spec's Requirements Delta (if any)
|
|
34
|
+
|
|
35
|
+
If a design spec exists at `.agents/specs/<slug>.md`, read it and look for a `## Requirements Delta` section with subsections:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
## Requirements Delta
|
|
39
|
+
|
|
40
|
+
### ADDED Requirements
|
|
41
|
+
- REQ-AUTH-002: 2FA enrollment
|
|
42
|
+
- Scenario: enabling 2FA — Given the user is authenticated, When they enable 2FA, Then ...
|
|
43
|
+
|
|
44
|
+
### MODIFIED Requirements
|
|
45
|
+
- REQ-AUTH-001: User login (replaces previous version)
|
|
46
|
+
- Now includes 2FA challenge step when the account has 2FA enabled.
|
|
47
|
+
|
|
48
|
+
### REMOVED Requirements
|
|
49
|
+
- REQ-AUTH-003: SMS-only fallback (deprecated)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If no `## Requirements Delta` section exists, the spec is informational only — skip to Step 4 (archive moves).
|
|
53
|
+
|
|
54
|
+
## Step 2: Locate Canonical Specs
|
|
55
|
+
|
|
56
|
+
Canonical specs live at `.agents/specs/canonical/<domain>.md` (one file per domain — e.g. `auth.md`, `payments.md`, `ui.md`). They represent the **current behavior of the system**.
|
|
57
|
+
|
|
58
|
+
For each ADDED / MODIFIED / REMOVED requirement, determine which canonical file it belongs to. The domain is usually inferable from the requirement ID prefix (`REQ-AUTH-*` → `auth.md`) or from the project's directory structure.
|
|
59
|
+
|
|
60
|
+
If `.agents/specs/canonical/` does not exist:
|
|
61
|
+
- Ask the user: "No canonical specs directory found. Should I create `.agents/specs/canonical/` and start it with the requirements from this change?"
|
|
62
|
+
- If yes, create `.agents/specs/canonical/<domain>.md` for each domain touched and treat ALL requirements from this change as initial content (no merge needed — bootstrap mode).
|
|
63
|
+
|
|
64
|
+
## Step 3: Propose the Merge Diff (human approval required)
|
|
65
|
+
|
|
66
|
+
For each affected canonical file, build the proposed new content **in memory**:
|
|
67
|
+
|
|
68
|
+
- For each entry under `### ADDED Requirements` → append the requirement (with its scenarios) to the canonical file under `## Requirements`.
|
|
69
|
+
- For each entry under `### MODIFIED Requirements` → find the requirement by ID in the canonical file and **replace** its body with the new version. If the ID is not found, treat as ADDED and warn the user.
|
|
70
|
+
- For each entry under `### REMOVED Requirements` → find the requirement by ID and **delete** its block (heading + body until the next `### ` heading or end of section).
|
|
71
|
+
|
|
72
|
+
Show the user a summary:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
## Archive plan: <slug>
|
|
76
|
+
|
|
77
|
+
Canonical specs to update:
|
|
78
|
+
.agents/specs/canonical/auth.md
|
|
79
|
+
+ ADDED: REQ-AUTH-002 (2FA enrollment)
|
|
80
|
+
~ MODIFIED: REQ-AUTH-001 (User login — now requires 2FA step)
|
|
81
|
+
- REMOVED: REQ-AUTH-003 (SMS-only fallback)
|
|
82
|
+
|
|
83
|
+
Files to move:
|
|
84
|
+
.agents/plans/<slug>.md → .agents/plans/done/<slug>.md
|
|
85
|
+
.agents/specs/<slug>.md → .agents/specs/archived/<slug>.md
|
|
86
|
+
.agents/code-reviews/<slug>.md → DELETE (ephemeral)
|
|
87
|
+
|
|
88
|
+
Files to keep in place:
|
|
89
|
+
.agents/execution-reports/<slug>.md
|
|
90
|
+
.agents/system-reviews/<slug>-review.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then ask:
|
|
94
|
+
> "Apply these changes? (yes / show diff / cancel)"
|
|
95
|
+
|
|
96
|
+
- **show diff:** print a unified diff of each canonical file (before vs after) so the user can review the exact merge before approval.
|
|
97
|
+
- **cancel:** abort with no changes.
|
|
98
|
+
- **yes:** proceed to Step 4.
|
|
99
|
+
|
|
100
|
+
## Step 4: Apply the Changes
|
|
101
|
+
|
|
102
|
+
Only after explicit `yes`:
|
|
103
|
+
|
|
104
|
+
1. Write each updated canonical spec file via the Edit tool. Never overwrite blindly — always anchor on the requirement ID heading.
|
|
105
|
+
2. Move (or copy + delete) the plan: `.agents/plans/<slug>.md` → `.agents/plans/done/<slug>.md`. If `.agents/plans/done/` does not exist, create it.
|
|
106
|
+
3. If a design spec exists, move `.agents/specs/<slug>.md` → `.agents/specs/archived/<slug>.md`. Create `archived/` if missing.
|
|
107
|
+
4. Delete the ephemeral code review at `.agents/code-reviews/<slug>.md` (if it exists). Per project policy, code reviews are working state and not committed.
|
|
108
|
+
5. Leave `execution-reports/` and `system-reviews/` untouched — they are part of the cross-session learning loop.
|
|
109
|
+
|
|
110
|
+
## Step 5: Confirm and Suggest Next
|
|
111
|
+
|
|
112
|
+
Print a final summary:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
✅ Archived <slug>
|
|
116
|
+
|
|
117
|
+
Canonical specs updated:
|
|
118
|
+
- .agents/specs/canonical/auth.md (3 changes)
|
|
119
|
+
|
|
120
|
+
Files moved:
|
|
121
|
+
- plans/<slug>.md → plans/done/<slug>.md
|
|
122
|
+
- specs/<slug>.md → specs/archived/<slug>.md
|
|
123
|
+
|
|
124
|
+
Files removed:
|
|
125
|
+
- code-reviews/<slug>.md (ephemeral)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Suggest:
|
|
129
|
+
> "Archive complete. The canonical specs now reflect this change. Consider running the `git` skill (say 'commit') to capture the merged specs, and `/hopla:system-review` if you want to mine this implementation for process improvements."
|
|
130
|
+
|
|
131
|
+
## Notes & Edge Cases
|
|
132
|
+
|
|
133
|
+
- **No spec, no Requirements Delta:** Step 4 still runs (file moves) but no canonical specs are updated. This is the common case for tactical fixes that do not change documented requirements.
|
|
134
|
+
- **Canonical bootstrap:** if `.agents/specs/canonical/` is empty, this command may be the first to populate it. Use the change's specs as initial content (treat all ADDED entries as initial requirements, ignore MODIFIED/REMOVED — there is nothing to modify).
|
|
135
|
+
- **Conflicting modifications:** if the same REQ-* ID is modified by two parallel plans, the later archive wins. Surface this in the diff with a `⚠ conflict` marker so the user can reconcile manually before approving.
|
|
136
|
+
- **Reverting an archive:** this command is one-way. To revert, restore from git history (`git restore`).
|
|
137
|
+
- **Do not auto-commit:** per global rules, never run `git commit` or `git push` from this command. After archiving, suggest the `git` skill.
|
package/commands/execute.md
CHANGED
|
@@ -143,7 +143,7 @@ If the user requests changes that are NOT in the plan during execution:
|
|
|
143
143
|
|
|
144
144
|
After all tasks are complete, run **Levels 1–7** from `commands/guides/validation-pyramid.md` (same repo). Do not skip levels. Do not proceed if a level fails.
|
|
145
145
|
|
|
146
|
-
Use the exact commands from the plan's **Validation Checklist**. If not specified, read `CLAUDE.md` "Development Commands" to find the correct commands.
|
|
146
|
+
Use the exact commands from the plan's **Validation Checklist**. If not specified, read `AGENTS.md` (or `CLAUDE.md` as fallback) "Development Commands" to find the correct commands.
|
|
147
147
|
|
|
148
148
|
Level 5 triggers the `code-review` skill (not a slash command). Level 6 is the file-drift check specific to plan execution. Level 7 surfaces items for human verification.
|
|
149
149
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Guide for structuring codebases to be navigable and editable by AI agents (file layout, naming, comments, doc placement).
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# AI-Optimized Codebase Guide
|
|
2
6
|
|
|
3
7
|
## When to Use This Guide
|
|
@@ -51,7 +55,7 @@ logger.info("order_created", {
|
|
|
51
55
|
- Types serve as contracts that AI can read and follow
|
|
52
56
|
|
|
53
57
|
### 5. Comprehensive Validation Commands
|
|
54
|
-
Include in CLAUDE.md:
|
|
58
|
+
Include in AGENTS.md (or CLAUDE.md for legacy projects):
|
|
55
59
|
```markdown
|
|
56
60
|
## Development Commands
|
|
57
61
|
- `npm run lint` — ESLint check
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reference for auditing existing data sources (schema, value semantics, null cases, derived value propagation) before implementing data-consuming features.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Guide: Data Audit and Value Semantics
|
|
2
6
|
|
|
3
7
|
Use this guide when a feature reads from, calculates with, or references existing data or code patterns. Applies during planning (full audit) and execution (verification of documented findings).
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reference for integrating Model Context Protocol (MCP) servers with Claude Code projects — config, auth, and common patterns.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# MCP Integration Guide
|
|
2
6
|
|
|
3
7
|
## When to Use This Guide
|
|
@@ -6,7 +10,7 @@ Reference this guide when planning features that involve external tools or servi
|
|
|
6
10
|
## MCP in the PIV Loop
|
|
7
11
|
|
|
8
12
|
### During Planning (`/hopla:plan-feature`)
|
|
9
|
-
- Check what MCP servers are configured (listed in CLAUDE.md under "MCP Servers")
|
|
13
|
+
- Check what MCP servers are configured (listed in AGENTS.md or CLAUDE.md under "MCP Servers")
|
|
10
14
|
- For each external integration point, specify which MCP tool to use
|
|
11
15
|
- Example: "Step 3: Use Playwright MCP to verify the component renders correctly"
|
|
12
16
|
|
|
@@ -28,5 +32,5 @@ Reference this guide when planning features that involve external tools or servi
|
|
|
28
32
|
|
|
29
33
|
## Adding MCP to Your Project
|
|
30
34
|
1. Configure MCP servers in `.claude/settings.json` or `.claude/settings.local.json`
|
|
31
|
-
2. List them in your project's CLAUDE.md under the "MCP Servers" section
|
|
35
|
+
2. List them in your project's AGENTS.md (or CLAUDE.md) under the "MCP Servers" section
|
|
32
36
|
3. Pre-approve permissions in settings to avoid repeated prompts
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Forward-looking guide for remote agentic coding workflows — long-running agents, async coordination, sandboxed execution.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Remote Agentic Coding Guide (Future)
|
|
2
6
|
|
|
3
7
|
## When to Use This Guide
|
|
@@ -59,7 +63,7 @@ jobs:
|
|
|
59
63
|
|
|
60
64
|
## Prerequisites
|
|
61
65
|
- HOPLA commands installed in the repo (.claude/commands/)
|
|
62
|
-
- CLAUDE.md configured for the project
|
|
66
|
+
- AGENTS.md (or CLAUDE.md) configured for the project
|
|
63
67
|
- GitHub Actions enabled
|
|
64
68
|
- Claude Code API key in GitHub Secrets
|
|
65
69
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Guide for creating a project-specific code review checklist (.agents/guides/review-checklist.md) consumed by the code-review skill.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Guide: Creating a Project-Specific Review Checklist
|
|
2
6
|
|
|
3
7
|
Use this guide to create a `.agents/guides/review-checklist.md` file in your project with code review checks specific to your tech stack, domain, and known anti-patterns. The the `code-review` skill command loads this file automatically when it exists, applying your custom checks alongside the standard review categories.
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Shared reference for the full validation sequence (lint, types, tests, code review, manual smoke). Used by execute, validate, and verify.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Validation Pyramid
|
|
2
6
|
|
|
3
7
|
Shared reference for the full validation sequence. Callers (`commands/execute.md`, `commands/validate.md`, `skills/verify/SKILL.md`, plus plans' `Validation Checklist`) pick the levels that apply to their scope.
|
|
4
8
|
|
|
5
9
|
Run levels **in order**. Do not skip a level. Do not proceed if a level fails — fix it first.
|
|
6
10
|
|
|
7
|
-
Commands below are generic examples; use the exact commands from the project's `CLAUDE.md` "Development Commands" section or the plan's checklist.
|
|
11
|
+
Commands below are generic examples; use the exact commands from the project's `AGENTS.md` (or `CLAUDE.md` as fallback) "Development Commands" section or the plan's checklist.
|
|
8
12
|
|
|
9
13
|
## Level 1 — Lint & Format
|
|
10
14
|
|