buildcrew 1.8.7 → 1.9.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/README.md +129 -1
- package/agents/architect.md +26 -0
- package/agents/browser-qa.md +29 -0
- package/agents/buildcrew.md +31 -3
- package/agents/canary-monitor.md +22 -0
- package/agents/coherence-auditor.md +347 -0
- package/agents/design-reviewer.md +36 -0
- package/agents/designer.md +29 -0
- package/agents/developer.md +34 -0
- package/agents/health-checker.md +23 -0
- package/agents/investigator.md +39 -0
- package/agents/planner.md +26 -0
- package/agents/qa-auditor.md +32 -0
- package/agents/qa-tester.md +29 -0
- package/agents/reviewer.md +35 -0
- package/agents/security-auditor.md +23 -0
- package/agents/shipper.md +23 -0
- package/agents/thinker.md +32 -0
- package/bin/hook.js +17 -0
- package/bin/setup.js +166 -7
- package/bin/watch.js +594 -0
- package/lib/hook.js +230 -0
- package/lib/install-hooks.js +165 -0
- package/package.json +7 -3
package/bin/setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readdir, copyFile, mkdir, readFile, writeFile, access } from "fs/promises";
|
|
4
|
-
import { join, dirname } from "path";
|
|
4
|
+
import { join, dirname, resolve } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -9,6 +9,7 @@ const AGENTS_SRC = join(__dirname, "..", "agents");
|
|
|
9
9
|
const TEMPLATES_SRC = join(__dirname, "..", "templates");
|
|
10
10
|
const TARGET_DIR = join(process.cwd(), ".claude", "agents");
|
|
11
11
|
const HARNESS_DIR = join(process.cwd(), ".claude", "harness");
|
|
12
|
+
// (HOOK_SCRIPT path no longer needed — hooks use `npx buildcrew-hook` bin)
|
|
12
13
|
const PKG = JSON.parse(await readFile(join(__dirname, "..", "package.json"), "utf-8"));
|
|
13
14
|
const VERSION = PKG.version;
|
|
14
15
|
|
|
@@ -556,6 +557,39 @@ async function runInstall(force) {
|
|
|
556
557
|
}
|
|
557
558
|
}
|
|
558
559
|
|
|
560
|
+
// ─── Step 2d: CC hooks (agent lifecycle banners + events.jsonl) ───
|
|
561
|
+
try {
|
|
562
|
+
const hooksInstalled = await areHooksInstalled();
|
|
563
|
+
if (hooksInstalled) {
|
|
564
|
+
log(` ${GREEN}Hooks:${RESET} installed ✓\n`);
|
|
565
|
+
} else {
|
|
566
|
+
log(` ${YELLOW}Hooks${RESET} print a colored banner to your terminal whenever an`);
|
|
567
|
+
log(` agent starts/completes or a file is written. Also feeds ${BOLD}npx buildcrew watch${RESET}.`);
|
|
568
|
+
log(` ${DIM}Writes .claude/settings.json (+ recommended permissions)${RESET}\n`);
|
|
569
|
+
const answer = await ask(` Install hooks? ${BOLD}(Y/n)${RESET} `);
|
|
570
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
571
|
+
try {
|
|
572
|
+
const { install } = await import("../lib/install-hooks.js");
|
|
573
|
+
const result = await install({
|
|
574
|
+
scope: "project",
|
|
575
|
+
cwd: process.cwd(),
|
|
576
|
+
withPermissions: true,
|
|
577
|
+
});
|
|
578
|
+
log(`\n ${GREEN}${BOLD}Hooks installed!${RESET}`);
|
|
579
|
+
log(` ${DIM} hooks: ${result.settingsPath}${RESET}`);
|
|
580
|
+
if (result.permissions?.permPath) {
|
|
581
|
+
log(` ${DIM} permissions: ${result.permissions.permPath}${RESET}`);
|
|
582
|
+
}
|
|
583
|
+
log(`\n ${BOLD}Live monitor:${RESET} ${CYAN}npx buildcrew watch${RESET} ${DIM}(in a separate pane)${RESET}\n`);
|
|
584
|
+
} catch (err) {
|
|
585
|
+
log(`\n ${RED}Hook install failed:${RESET} ${err.message}\n`);
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
log(`\n ${DIM}Skipped. Hooks not installed.${RESET}\n`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch { /* ignore, non-fatal */ }
|
|
592
|
+
|
|
559
593
|
// ─── Step 3: Project harness ───
|
|
560
594
|
if (!(await exists(join(HARNESS_DIR, "project.md")))) {
|
|
561
595
|
const initAnswer = await ask(` Generate project harness? ${DIM}(auto-detects your stack)${RESET} ${BOLD}(Y/n)${RESET} `);
|
|
@@ -588,6 +622,124 @@ async function runList() {
|
|
|
588
622
|
log("");
|
|
589
623
|
}
|
|
590
624
|
|
|
625
|
+
async function areHooksInstalled() {
|
|
626
|
+
try {
|
|
627
|
+
const settingsPath = join(process.cwd(), ".claude", "settings.json");
|
|
628
|
+
const content = await readFile(settingsPath, "utf-8");
|
|
629
|
+
return content.includes("buildcrew-hook");
|
|
630
|
+
} catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function runWatch() {
|
|
636
|
+
// Pass through to bin/watch.js — the terminal-native live monitor.
|
|
637
|
+
const watchEntry = resolve(__dirname, "watch.js");
|
|
638
|
+
const passthrough = process.argv.slice(3);
|
|
639
|
+
const { spawn } = await import("child_process");
|
|
640
|
+
const child = spawn(process.execPath, [watchEntry, ...passthrough], {
|
|
641
|
+
stdio: "inherit",
|
|
642
|
+
cwd: process.cwd(),
|
|
643
|
+
env: process.env,
|
|
644
|
+
});
|
|
645
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
646
|
+
child.on("error", (err) => {
|
|
647
|
+
console.error(`${RED}Watch failed to start:${RESET} ${err.message}`);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function runReport() {
|
|
653
|
+
// Show coherence-report.md output by the coherence-auditor agent.
|
|
654
|
+
// Usage:
|
|
655
|
+
// npx buildcrew report Show latest coherence-report
|
|
656
|
+
// npx buildcrew report --list List all reports with timestamps
|
|
657
|
+
// npx buildcrew report <feature> Show specific feature's report
|
|
658
|
+
// npx buildcrew report --raw Print raw markdown (for piping)
|
|
659
|
+
const args = process.argv.slice(3);
|
|
660
|
+
const wantList = args.includes("--list") || args.includes("-l");
|
|
661
|
+
const wantRaw = args.includes("--raw");
|
|
662
|
+
const featureArg = args.find(a => !a.startsWith("-"));
|
|
663
|
+
|
|
664
|
+
const PIPELINE_DIR = join(process.cwd(), ".claude", "pipeline");
|
|
665
|
+
if (!(await exists(PIPELINE_DIR))) {
|
|
666
|
+
log(`${YELLOW}No pipeline runs found yet.${RESET}`);
|
|
667
|
+
log(`${DIM}Run ${BOLD}@buildcrew <feature>${RESET}${DIM} in Claude Code to generate one.${RESET}\n`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Collect all coherence-report.md files and their mtimes
|
|
672
|
+
const features = await readdir(PIPELINE_DIR, { withFileTypes: true });
|
|
673
|
+
const reports = [];
|
|
674
|
+
const { stat } = await import("fs/promises");
|
|
675
|
+
for (const entry of features) {
|
|
676
|
+
if (!entry.isDirectory()) continue;
|
|
677
|
+
const reportPath = join(PIPELINE_DIR, entry.name, "coherence-report.md");
|
|
678
|
+
if (await exists(reportPath)) {
|
|
679
|
+
const s = await stat(reportPath);
|
|
680
|
+
reports.push({ feature: entry.name, path: reportPath, mtime: s.mtime });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (reports.length === 0) {
|
|
685
|
+
log(`${YELLOW}No coherence-report.md found in any pipeline run.${RESET}`);
|
|
686
|
+
log(`${DIM}coherence-auditor runs at the end of Feature mode. If you ran a feature recently and don't see a report, check your buildcrew version (need >= 1.9.0).${RESET}\n`);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
reports.sort((a, b) => b.mtime - a.mtime);
|
|
691
|
+
|
|
692
|
+
if (wantList) {
|
|
693
|
+
log(`\n ${BOLD}Coherence reports${RESET} ${DIM}(newest first)${RESET}\n`);
|
|
694
|
+
for (const r of reports) {
|
|
695
|
+
const ago = ((Date.now() - r.mtime) / 1000 / 60) | 0;
|
|
696
|
+
const when = ago < 60 ? `${ago}m ago` : ago < 1440 ? `${(ago/60)|0}h ago` : `${(ago/1440)|0}d ago`;
|
|
697
|
+
log(` ${CYAN}${r.feature.padEnd(30)}${RESET} ${DIM}${when.padStart(8)}${RESET} ${DIM}${r.path}${RESET}`);
|
|
698
|
+
}
|
|
699
|
+
log(`\n ${DIM}Show one: ${BOLD}npx buildcrew report ${CYAN}<feature>${RESET}\n`);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Pick target report
|
|
704
|
+
let target;
|
|
705
|
+
if (featureArg) {
|
|
706
|
+
target = reports.find(r => r.feature === featureArg);
|
|
707
|
+
if (!target) {
|
|
708
|
+
log(`${RED}No coherence-report for feature "${featureArg}".${RESET}`);
|
|
709
|
+
log(`${DIM}List all: ${BOLD}npx buildcrew report --list${RESET}\n`);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
target = reports[0]; // latest
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const content = await readFile(target.path, "utf8");
|
|
717
|
+
|
|
718
|
+
if (wantRaw) {
|
|
719
|
+
process.stdout.write(content);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Pretty header + content. If TTY supports it, try less for paging.
|
|
724
|
+
const header = `${BOLD}${CYAN}═══ ${target.feature} ═══${RESET} ${DIM}${target.path}${RESET}\n\n`;
|
|
725
|
+
|
|
726
|
+
if (process.stdout.isTTY && content.split("\n").length > process.stdout.rows) {
|
|
727
|
+
// Try paging through `less -R` (preserves ANSI). Fallback to direct print.
|
|
728
|
+
try {
|
|
729
|
+
const { spawn } = await import("child_process");
|
|
730
|
+
const less = spawn("less", ["-R", "-X"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
731
|
+
less.stdin.write(header + content);
|
|
732
|
+
less.stdin.end();
|
|
733
|
+
await new Promise((resolve) => less.on("exit", resolve));
|
|
734
|
+
return;
|
|
735
|
+
} catch {
|
|
736
|
+
// fall through to direct print
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
process.stdout.write(header + content + "\n");
|
|
741
|
+
}
|
|
742
|
+
|
|
591
743
|
async function runUninstall() {
|
|
592
744
|
const files = (await readdir(AGENTS_SRC)).filter(f => f.endsWith(".md"));
|
|
593
745
|
if (!(await exists(TARGET_DIR))) { log(`${YELLOW}No agents found.${RESET}`); return; }
|
|
@@ -614,11 +766,14 @@ async function main() {
|
|
|
614
766
|
${BOLD}buildcrew${RESET} v${VERSION} — 15 AI agents for Claude Code
|
|
615
767
|
|
|
616
768
|
${BOLD}Commands:${RESET}
|
|
617
|
-
npx buildcrew Install agents
|
|
769
|
+
npx buildcrew Install agents (also offers hooks + harness)
|
|
618
770
|
npx buildcrew init Auto-generate project harness (zero questions)
|
|
619
771
|
npx buildcrew add List harness templates
|
|
620
772
|
npx buildcrew add <name> Add a harness template
|
|
621
773
|
npx buildcrew harness Show harness file status
|
|
774
|
+
npx buildcrew watch Live terminal monitor (stays in your shell)
|
|
775
|
+
npx buildcrew report Show latest coherence-report (team coordination score)
|
|
776
|
+
npx buildcrew report --list List all coherence reports
|
|
622
777
|
|
|
623
778
|
${BOLD}Options:${RESET}
|
|
624
779
|
--force, -f Overwrite existing files
|
|
@@ -627,21 +782,25 @@ async function main() {
|
|
|
627
782
|
--version Show version
|
|
628
783
|
|
|
629
784
|
${BOLD}Setup:${RESET}
|
|
630
|
-
${GREEN}1.${RESET} npx buildcrew
|
|
631
|
-
${GREEN}2.${RESET} npx buildcrew init
|
|
785
|
+
${GREEN}1.${RESET} npx buildcrew ${DIM}Install agents + optional hooks${RESET}
|
|
786
|
+
${GREEN}2.${RESET} npx buildcrew init ${DIM}Auto-generate harness from codebase${RESET}
|
|
632
787
|
${GREEN}3.${RESET} Edit .claude/harness/ ${DIM}Customize (replace <!-- comments -->)${RESET}
|
|
633
|
-
${GREEN}4.${RESET}
|
|
788
|
+
${GREEN}4.${RESET} npx buildcrew watch ${DIM}(optional) live terminal monitor${RESET}
|
|
789
|
+
${GREEN}5.${RESET} @buildcrew [task] ${DIM}Start working${RESET}
|
|
634
790
|
|
|
635
791
|
${BOLD}More info:${RESET} https://github.com/z1nun/buildcrew
|
|
636
792
|
`);
|
|
637
793
|
return;
|
|
638
794
|
}
|
|
639
795
|
|
|
640
|
-
|
|
641
|
-
if (args.includes("--uninstall")) return runUninstall();
|
|
796
|
+
// Subcommand routing takes priority over global --list (so `report --list` works)
|
|
642
797
|
if (command === "init") return runInit(force);
|
|
643
798
|
if (command === "add") return runAdd(subcommand, force);
|
|
644
799
|
if (command === "harness") return runHarnessStatus();
|
|
800
|
+
if (command === "watch") return runWatch();
|
|
801
|
+
if (command === "report") return runReport();
|
|
802
|
+
if (args.includes("--list") || args.includes("-l")) return runList();
|
|
803
|
+
if (args.includes("--uninstall")) return runUninstall();
|
|
645
804
|
|
|
646
805
|
return runInstall(force);
|
|
647
806
|
}
|