haac-aikit 0.5.0 → 0.7.2

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.
Files changed (35) hide show
  1. package/README.md +3 -2
  2. package/catalog/agents/tier1/architect.md +86 -0
  3. package/catalog/agents/tier1/debugger.md +64 -0
  4. package/catalog/agents/tier1/pr-describer.md +57 -0
  5. package/catalog/agents/{researcher.md → tier1/researcher.md} +1 -1
  6. package/catalog/agents/tier2/changelog-curator.md +60 -0
  7. package/catalog/agents/tier2/dependency-upgrader.md +67 -0
  8. package/catalog/agents/tier2/evals-author.md +65 -0
  9. package/catalog/agents/tier2/flake-hunter.md +57 -0
  10. package/catalog/agents/tier2/prompt-engineer.md +58 -0
  11. package/catalog/agents/tier2/simplifier.md +57 -0
  12. package/catalog/hooks/block-dangerous-bash.sh +0 -0
  13. package/catalog/hooks/block-force-push-main.sh +0 -0
  14. package/catalog/hooks/block-secrets-in-commits.sh +0 -0
  15. package/catalog/hooks/compaction-preservation.sh +0 -0
  16. package/catalog/hooks/file-guard.sh +0 -0
  17. package/catalog/hooks/format-on-save.sh +0 -0
  18. package/catalog/hooks/session-start-prime.sh +0 -0
  19. package/catalog/husky/commit-msg +0 -0
  20. package/catalog/husky/pre-commit +0 -0
  21. package/catalog/husky/pre-push +0 -0
  22. package/catalog/skills/tier1/software-architect.md +42 -0
  23. package/dist/cli.mjs +348 -107
  24. package/dist/cli.mjs.map +1 -1
  25. package/package.json +3 -1
  26. /package/catalog/agents/{devops.md → tier1/devops.md} +0 -0
  27. /package/catalog/agents/{implementer.md → tier1/implementer.md} +0 -0
  28. /package/catalog/agents/{orchestrator.md → tier1/orchestrator.md} +0 -0
  29. /package/catalog/agents/{planner.md → tier1/planner.md} +0 -0
  30. /package/catalog/agents/{reviewer.md → tier1/reviewer.md} +0 -0
  31. /package/catalog/agents/{security-auditor.md → tier1/security-auditor.md} +0 -0
  32. /package/catalog/agents/{tester.md → tier1/tester.md} +0 -0
  33. /package/catalog/agents/{backend.md → tier2/backend.md} +0 -0
  34. /package/catalog/agents/{frontend.md → tier2/frontend.md} +0 -0
  35. /package/catalog/agents/{mobile.md → tier2/mobile.md} +0 -0
package/dist/cli.mjs CHANGED
@@ -972,6 +972,10 @@ var init_kleur = __esm({
972
972
  });
973
973
 
974
974
  // src/wizard.ts
975
+ function defaultSpecialtyAgents(scope) {
976
+ if (scope === "everything") return SPECIALTY_TIER2_AGENTS.map((a2) => a2.value);
977
+ return [];
978
+ }
975
979
  async function runWizard(projectName) {
976
980
  we(kleur_default.bgCyan().black(" haac-aikit ") + " The batteries-included AI-agentic-coding kit");
977
981
  const answers = await be(
@@ -1029,6 +1033,15 @@ async function runWizard(projectName) {
1029
1033
  initialValues: ["web"],
1030
1034
  required: false
1031
1035
  });
1036
+ },
1037
+ specialtyAgents: ({ results }) => {
1038
+ if (results.scope === "minimal") return Promise.resolve(void 0);
1039
+ return pe({
1040
+ message: "Include specialty agents? (debugger and pr-describer always installed)",
1041
+ options: SPECIALTY_TIER2_AGENTS.map((a2) => ({ value: a2.value, label: a2.label })),
1042
+ required: false,
1043
+ initialValues: defaultSpecialtyAgents(results.scope ?? "standard")
1044
+ });
1032
1045
  }
1033
1046
  },
1034
1047
  {
@@ -1044,15 +1057,24 @@ async function runWizard(projectName) {
1044
1057
  tools: answers.tools ?? ALL_TOOLS.map((t) => t.value),
1045
1058
  scope: answers.scope ?? "standard",
1046
1059
  integrations: answers.integrations ?? [],
1047
- shape: answers.shape ?? []
1060
+ shape: answers.shape ?? [],
1061
+ specialtyAgents: answers.specialtyAgents ?? []
1048
1062
  };
1049
1063
  }
1050
- var ALL_TOOLS, ALL_SHAPES, ALL_INTEGRATIONS;
1064
+ var SPECIALTY_TIER2_AGENTS, ALL_TOOLS, ALL_SHAPES, ALL_INTEGRATIONS;
1051
1065
  var init_wizard = __esm({
1052
1066
  "src/wizard.ts"() {
1053
1067
  "use strict";
1054
1068
  init_dist2();
1055
1069
  init_kleur();
1070
+ SPECIALTY_TIER2_AGENTS = [
1071
+ { value: "flake-hunter", label: "flake-hunter \u2014 diagnose intermittent test failures" },
1072
+ { value: "simplifier", label: "simplifier \u2014 DRY, dead code, complexity reduction" },
1073
+ { value: "prompt-engineer", label: "prompt-engineer \u2014 author/optimize prompts" },
1074
+ { value: "evals-author", label: "evals-author \u2014 eval datasets & benchmarks" },
1075
+ { value: "changelog-curator", label: "changelog-curator \u2014 generate CHANGELOG from commits" },
1076
+ { value: "dependency-upgrader", label: "dependency-upgrader \u2014 npm major bumps + codemods" }
1077
+ ];
1056
1078
  ALL_TOOLS = [
1057
1079
  { value: "claude", label: "Claude Code", hint: "CLAUDE.md + .claude/" },
1058
1080
  { value: "cursor", label: "Cursor", hint: ".cursor/rules/" },
@@ -1270,6 +1292,30 @@ var init_catalog = __esm({
1270
1292
  }
1271
1293
  });
1272
1294
 
1295
+ // src/catalog/shape-agents.ts
1296
+ function resolveShapeAgents(shapes) {
1297
+ const set = /* @__PURE__ */ new Set();
1298
+ for (const shape of shapes) {
1299
+ for (const agent of SHAPE_AGENTS[shape] ?? []) {
1300
+ set.add(agent);
1301
+ }
1302
+ }
1303
+ return Array.from(set);
1304
+ }
1305
+ var SHAPE_AGENTS;
1306
+ var init_shape_agents = __esm({
1307
+ "src/catalog/shape-agents.ts"() {
1308
+ "use strict";
1309
+ SHAPE_AGENTS = {
1310
+ web: ["frontend"],
1311
+ fullstack: ["frontend", "backend"],
1312
+ backend: ["backend"],
1313
+ mobile: ["mobile"],
1314
+ library: ["backend"]
1315
+ };
1316
+ }
1317
+ });
1318
+
1273
1319
  // src/render/dialects/parser.ts
1274
1320
  function parseRuleSet(content, projectName, description) {
1275
1321
  const rules = [];
@@ -1386,13 +1432,87 @@ var init_dialects = __esm({
1386
1432
  }
1387
1433
  });
1388
1434
 
1435
+ // src/fs/diff.ts
1436
+ import { createPatch } from "diff";
1437
+ function formatUnifiedDiff(local, incoming) {
1438
+ if (local === incoming) return "";
1439
+ const raw = createPatch("local", local, incoming, "", "", { context: 3 });
1440
+ const lines = [];
1441
+ let inHunk = false;
1442
+ for (const line of raw.split("\n")) {
1443
+ if (line.startsWith("@@")) {
1444
+ inHunk = true;
1445
+ continue;
1446
+ }
1447
+ if (!inHunk) continue;
1448
+ if (line.startsWith("+") && !line.startsWith("+++")) {
1449
+ lines.push(kleur_default.green(`+ ${line.slice(1)}`));
1450
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
1451
+ lines.push(kleur_default.red(`- ${line.slice(1)}`));
1452
+ } else if (line.startsWith(" ")) {
1453
+ lines.push(kleur_default.dim(` ${line.slice(1)}`));
1454
+ }
1455
+ }
1456
+ return lines.join("\n");
1457
+ }
1458
+ var init_diff = __esm({
1459
+ "src/fs/diff.ts"() {
1460
+ "use strict";
1461
+ init_kleur();
1462
+ }
1463
+ });
1464
+
1465
+ // src/fs/conflict.ts
1466
+ import { basename } from "path";
1467
+ function inferTier3Slot(filePath) {
1468
+ if ((filePath.includes("/.claude/agents/") || filePath.startsWith(".claude/agents/")) && filePath.endsWith(".md")) return "agents";
1469
+ if ((filePath.includes("/.claude/skills/") || filePath.startsWith(".claude/skills/")) && filePath.endsWith(".md")) return "skills";
1470
+ return null;
1471
+ }
1472
+ var interactivePrompt;
1473
+ var init_conflict = __esm({
1474
+ "src/fs/conflict.ts"() {
1475
+ "use strict";
1476
+ init_dist2();
1477
+ init_diff();
1478
+ interactivePrompt = {
1479
+ async ask(filePath, local, incoming) {
1480
+ while (true) {
1481
+ const choice = await de({
1482
+ message: `Modified locally: ${filePath}`,
1483
+ options: [
1484
+ { value: "replace", label: "Replace with catalog version (recommended)" },
1485
+ { value: "keep", label: "Keep local version (mark as tier3 to silence future prompts)" },
1486
+ { value: "diff", label: "Show diff first" },
1487
+ { value: "replace_all", label: "Replace all remaining conflicts" },
1488
+ { value: "skip_all", label: "Skip all remaining conflicts" }
1489
+ ],
1490
+ initialValue: "replace"
1491
+ });
1492
+ if (BD(choice)) {
1493
+ ve("Sync cancelled");
1494
+ process.exit(0);
1495
+ }
1496
+ if (choice === "diff") {
1497
+ const out = formatUnifiedDiff(local, incoming);
1498
+ ye(out, basename(filePath));
1499
+ continue;
1500
+ }
1501
+ return choice;
1502
+ }
1503
+ }
1504
+ };
1505
+ }
1506
+ });
1507
+
1389
1508
  // src/commands/sync.ts
1390
1509
  var sync_exports = {};
1391
1510
  __export(sync_exports, {
1511
+ copyAction: () => copyAction,
1392
1512
  runSync: () => runSync
1393
1513
  });
1394
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, copyFileSync, readdirSync, readFileSync as readFileSync5 } from "fs";
1395
- import { join as join2 } from "path";
1514
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, copyFileSync, readdirSync, readFileSync as readFileSync5, chmodSync } from "fs";
1515
+ import { basename as basename2, dirname as dirname3, join as join2 } from "path";
1396
1516
  async function runSync(argv) {
1397
1517
  const dryRun = argv["dry-run"];
1398
1518
  const force = argv.force;
@@ -1417,7 +1537,7 @@ async function runSync(argv) {
1417
1537
  if (config.scope !== "minimal") {
1418
1538
  results.push(safeWrite("docs/claude-md-reference.md", catalog.claudeMdReference(), { ...opts, useMarkers: false }));
1419
1539
  results.push(safeWrite(".claude/aikit-rules.json", catalog.aikitRulesJson(), { ...opts, useMarkers: false }));
1420
- results.push(...syncDir("rules/claude-rules", ".claude/rules", dryRun, [".md"]));
1540
+ results.push(...syncDir("rules/claude-rules", ".claude/rules", opts, [".md"]));
1421
1541
  }
1422
1542
  }
1423
1543
  if (config.tools.includes("copilot")) {
@@ -1442,32 +1562,98 @@ async function runSync(argv) {
1442
1562
  results.push(safeWrite(".mcp.json", catalog.mcpJson(), { ...opts, useMarkers: false }));
1443
1563
  }
1444
1564
  if (config.scope !== "minimal") {
1445
- results.push(...syncSkills("tier1", dryRun));
1446
- results.push(...syncSkills("tier2", dryRun));
1565
+ results.push(...syncSkills("tier1", opts));
1566
+ results.push(...syncSkills("tier2", opts));
1447
1567
  }
1448
1568
  if (config.integrations.hooks) {
1449
- results.push(...syncHooks(dryRun));
1569
+ results.push(...syncHooks(opts));
1450
1570
  }
1451
1571
  if (config.integrations.subagents) {
1452
- results.push(...syncAgents(config.shape, dryRun));
1572
+ results.push(...syncAgents(config, opts));
1453
1573
  }
1454
1574
  if (config.integrations.commands) {
1455
- results.push(...syncCommands(dryRun));
1575
+ results.push(...syncCommands(opts));
1456
1576
  }
1457
1577
  if (config.integrations.ci) {
1458
- results.push(...syncCI(dryRun));
1578
+ results.push(...syncCI(opts));
1459
1579
  }
1460
1580
  if (config.integrations.devcontainer) {
1461
- results.push(...syncDir("devcontainer", ".devcontainer", dryRun, [".json"]));
1581
+ results.push(...syncDir("devcontainer", ".devcontainer", opts, [".json"]));
1462
1582
  }
1463
1583
  if (config.integrations.otel) {
1464
1584
  results.push(safeWrite(".env.example", readCatalogFile("settings/env.example"), { dryRun, useMarkers: false }));
1465
1585
  }
1586
+ if (config.integrations.husky) {
1587
+ results.push(...syncDir("husky", ".husky", opts, []));
1588
+ }
1589
+ if (config.integrations.plugin) {
1590
+ const tmpl = readCatalogFile("plugin/plugin.json");
1591
+ const content = interpolate(tmpl, { projectName: config.projectName });
1592
+ results.push(safeWrite(".claude/plugin/plugin.json", content, { ...opts, useMarkers: false }));
1593
+ }
1466
1594
  ensureGitignoreEntries(dryRun);
1595
+ const conflicts = results.filter((r2) => r2.action === "conflict");
1596
+ if (conflicts.length > 0 && !force) {
1597
+ const isInteractive2 = (Boolean(process.stdin.isTTY) || argv._conflictPrompt != null) && !argv.yes;
1598
+ if (isInteractive2) {
1599
+ const prompt = argv._conflictPrompt ?? interactivePrompt;
1600
+ let bulkAction = null;
1601
+ let configMutated = false;
1602
+ let workingConfig = config;
1603
+ v2.info(`Found ${conflicts.length} file(s) modified locally. Reviewing each\u2026`);
1604
+ for (const conflict of conflicts) {
1605
+ const incomingSrc = conflict.src;
1606
+ if (!incomingSrc) continue;
1607
+ if (bulkAction === "skip_all") {
1608
+ conflict.action = "skipped";
1609
+ continue;
1610
+ }
1611
+ let resolution;
1612
+ if (bulkAction === "replace_all") {
1613
+ resolution = "replace";
1614
+ } else {
1615
+ const local = readFileSync5(conflict.path, "utf8");
1616
+ const incoming = readFileSync5(incomingSrc, "utf8");
1617
+ resolution = await prompt.ask(conflict.path, local, incoming);
1618
+ if (resolution === "replace_all") bulkAction = "replace_all";
1619
+ if (resolution === "skip_all") {
1620
+ bulkAction = "skip_all";
1621
+ conflict.action = "skipped";
1622
+ continue;
1623
+ }
1624
+ }
1625
+ if (resolution === "replace" || resolution === "replace_all") {
1626
+ if (!opts.dryRun) copyFileSync(incomingSrc, conflict.path);
1627
+ conflict.action = "updated";
1628
+ } else if (resolution === "keep") {
1629
+ const slot = inferTier3Slot(conflict.path);
1630
+ if (slot) {
1631
+ const name = basename2(conflict.path).replace(/\.md$/, "");
1632
+ const current = workingConfig[slot] ?? { tier1: "all", tier2: "all", tier3: [] };
1633
+ if (!current.tier3.includes(name)) {
1634
+ workingConfig = {
1635
+ ...workingConfig,
1636
+ [slot]: { ...current, tier3: [...current.tier3, name] }
1637
+ };
1638
+ configMutated = true;
1639
+ }
1640
+ } else {
1641
+ v2.warn(
1642
+ `Kept ${conflict.path} \u2014 no tier3 protection available; this file will be flagged again on next sync.`
1643
+ );
1644
+ }
1645
+ conflict.action = "skipped";
1646
+ }
1647
+ }
1648
+ if (configMutated && !opts.dryRun) {
1649
+ writeConfig(workingConfig);
1650
+ }
1651
+ }
1652
+ }
1467
1653
  const created = results.filter((r2) => r2.action === "created").length;
1468
1654
  const updated = results.filter((r2) => r2.action === "updated").length;
1469
1655
  const skipped = results.filter((r2) => r2.action === "skipped").length;
1470
- const conflicts = results.filter((r2) => r2.action === "conflict");
1656
+ const remainingConflicts = results.filter((r2) => r2.action === "conflict");
1471
1657
  const visible = dryRun ? results : results.filter((r2) => r2.action !== "skipped");
1472
1658
  if (dryRun) {
1473
1659
  ye(visible.map((r2) => `${r2.action.padEnd(9)} ${r2.path}`).join("\n"), "Dry run");
@@ -1481,109 +1667,121 @@ async function runSync(argv) {
1481
1667
  } else {
1482
1668
  v2.info(`Already up to date \u2014 ${skipped} files match the catalog.`);
1483
1669
  }
1484
- if (conflicts.length > 0) {
1485
- v2.warn(`${conflicts.length} conflict(s) skipped (use --force to overwrite)`);
1670
+ if (remainingConflicts.length > 0) {
1671
+ v2.warn(`${remainingConflicts.length} conflict(s) skipped (use --force to overwrite)`);
1486
1672
  }
1487
1673
  fe("Sync complete.");
1488
1674
  }
1489
- function copyAction(srcPath, destPath, dryRun) {
1675
+ function copyAction(srcPath, destPath, opts) {
1490
1676
  const existed = existsSync5(destPath);
1491
1677
  const incoming = readFileSync5(srcPath, "utf8");
1492
1678
  if (existed) {
1493
1679
  const current = readFileSync5(destPath, "utf8");
1494
1680
  if (current === incoming) {
1495
- return { path: destPath, action: "skipped" };
1681
+ return { path: destPath, action: "skipped", src: srcPath };
1682
+ }
1683
+ if (!opts.force) {
1684
+ return { path: destPath, action: "conflict", src: srcPath };
1685
+ }
1686
+ }
1687
+ if (!opts.dryRun) {
1688
+ mkdirSync2(dirname3(destPath), { recursive: true });
1689
+ copyFileSync(srcPath, destPath);
1690
+ if (destPath.endsWith(".sh") || destPath.includes("/.husky/") || destPath.includes("/.claude/hooks/")) {
1691
+ chmodSync(destPath, 493);
1496
1692
  }
1497
1693
  }
1498
- if (!dryRun) copyFileSync(srcPath, destPath);
1499
- return { path: destPath, action: existed ? "updated" : "created" };
1694
+ return { path: destPath, action: existed ? "updated" : "created", src: srcPath };
1500
1695
  }
1501
- function syncSkills(tier, dryRun) {
1696
+ function syncSkills(tier, opts) {
1502
1697
  const srcDir = join2(CATALOG_ROOT, "skills", tier);
1503
1698
  const destDir = `.claude/skills`;
1504
1699
  const results = [];
1505
1700
  if (!existsSync5(srcDir)) return results;
1506
1701
  const files = readdirSync(srcDir).filter((f2) => f2.endsWith(".md"));
1507
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1702
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1508
1703
  for (const file of files) {
1509
- results.push(copyAction(join2(srcDir, file), join2(destDir, file), dryRun));
1704
+ results.push(copyAction(join2(srcDir, file), join2(destDir, file), opts));
1510
1705
  }
1511
1706
  return results;
1512
1707
  }
1513
- function syncHooks(dryRun) {
1708
+ function syncHooks(opts) {
1514
1709
  const srcDir = join2(CATALOG_ROOT, "hooks");
1515
1710
  const destDir = `.claude/hooks`;
1516
1711
  const results = [];
1517
1712
  if (!existsSync5(srcDir)) return results;
1518
1713
  const files = readdirSync(srcDir).filter((f2) => f2.endsWith(".sh") || f2 === "hooks.json");
1519
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1714
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1520
1715
  for (const file of files) {
1521
- results.push(copyAction(join2(srcDir, file), join2(destDir, file), dryRun));
1716
+ results.push(copyAction(join2(srcDir, file), join2(destDir, file), opts));
1522
1717
  }
1523
1718
  return results;
1524
1719
  }
1525
- function syncAgents(shapes, dryRun) {
1526
- const srcDir = join2(CATALOG_ROOT, "agents");
1527
- const destDir = `.claude/agents`;
1720
+ function syncAgents(config, opts) {
1528
1721
  const results = [];
1529
- if (!existsSync5(srcDir)) return results;
1530
- const CORE_AGENTS2 = ["orchestrator", "planner", "researcher", "implementer", "reviewer", "tester", "security-auditor", "devops"];
1531
- const SHAPE_AGENTS2 = {
1532
- web: ["frontend"],
1533
- fullstack: ["frontend", "backend"],
1534
- backend: ["backend"],
1535
- mobile: ["mobile"],
1536
- library: ["backend"]
1537
- };
1538
- const agentsToInstall = new Set(CORE_AGENTS2);
1539
- for (const shape of shapes) {
1540
- for (const agent of SHAPE_AGENTS2[shape] ?? []) {
1541
- agentsToInstall.add(agent);
1542
- }
1722
+ results.push(...syncAgentTier("tier1", config.agents?.tier1 ?? "all", opts));
1723
+ results.push(...syncAgentTier("tier2", resolveTier2Set(config), opts));
1724
+ return results;
1725
+ }
1726
+ function resolveTier2Set(config) {
1727
+ if (config.agents?.tier2 === "all") return "all";
1728
+ const set = new Set(
1729
+ Array.isArray(config.agents?.tier2) ? config.agents.tier2 : []
1730
+ );
1731
+ for (const agent of resolveShapeAgents(config.shape)) {
1732
+ set.add(agent);
1543
1733
  }
1544
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1734
+ return Array.from(set);
1735
+ }
1736
+ function syncAgentTier(tier, selection, opts) {
1737
+ const srcDir = join2(CATALOG_ROOT, "agents", tier);
1738
+ const destDir = ".claude/agents";
1739
+ const results = [];
1740
+ if (!existsSync5(srcDir)) return results;
1741
+ const allAgents = readdirSync(srcDir).filter((f2) => f2.endsWith(".md")).map((f2) => f2.replace(/\.md$/, ""));
1742
+ const agentsToInstall = selection === "all" ? allAgents : allAgents.filter((a2) => selection.includes(a2));
1743
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1545
1744
  for (const agent of agentsToInstall) {
1546
1745
  const src = join2(srcDir, `${agent}.md`);
1547
1746
  const dest = join2(destDir, `${agent}.md`);
1548
- if (!existsSync5(src)) continue;
1549
- results.push(copyAction(src, dest, dryRun));
1747
+ results.push(copyAction(src, dest, opts));
1550
1748
  }
1551
1749
  return results;
1552
1750
  }
1553
- function syncCommands(dryRun) {
1751
+ function syncCommands(opts) {
1554
1752
  const srcDir = join2(CATALOG_ROOT, "commands");
1555
1753
  const destDir = `.claude/commands`;
1556
1754
  const results = [];
1557
1755
  if (!existsSync5(srcDir)) return results;
1558
1756
  const files = readdirSync(srcDir).filter((f2) => f2.endsWith(".md"));
1559
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1757
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1560
1758
  for (const file of files) {
1561
- results.push(copyAction(join2(srcDir, file), join2(destDir, file), dryRun));
1759
+ results.push(copyAction(join2(srcDir, file), join2(destDir, file), opts));
1562
1760
  }
1563
1761
  return results;
1564
1762
  }
1565
- function syncCI(dryRun) {
1763
+ function syncCI(opts) {
1566
1764
  const srcDir = join2(CATALOG_ROOT, "ci");
1567
1765
  const destDir = `.github/workflows`;
1568
1766
  const results = [];
1569
1767
  if (!existsSync5(srcDir)) return results;
1570
1768
  const files = readdirSync(srcDir).filter((f2) => f2.endsWith(".yml"));
1571
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1769
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1572
1770
  for (const file of files) {
1573
- results.push(copyAction(join2(srcDir, file), join2(destDir, file), dryRun));
1771
+ results.push(copyAction(join2(srcDir, file), join2(destDir, file), opts));
1574
1772
  }
1575
1773
  return results;
1576
1774
  }
1577
- function syncDir(catalogSubdir, destDir, dryRun, extensions = [".md", ".json", ".yml", ".yaml"]) {
1775
+ function syncDir(catalogSubdir, destDir, opts, extensions = [".md", ".json", ".yml", ".yaml"]) {
1578
1776
  const srcDir = join2(CATALOG_ROOT, catalogSubdir);
1579
1777
  const results = [];
1580
1778
  if (!existsSync5(srcDir)) return results;
1581
1779
  const files = readdirSync(srcDir).filter(
1582
- (f2) => extensions.some((ext) => f2.endsWith(ext))
1780
+ (f2) => extensions.length === 0 || extensions.some((ext) => f2.endsWith(ext))
1583
1781
  );
1584
- if (!dryRun) mkdirSync2(destDir, { recursive: true });
1782
+ if (!opts.dryRun) mkdirSync2(destDir, { recursive: true });
1585
1783
  for (const file of files) {
1586
- results.push(copyAction(join2(srcDir, file), join2(destDir, file), dryRun));
1784
+ results.push(copyAction(join2(srcDir, file), join2(destDir, file), opts));
1587
1785
  }
1588
1786
  return results;
1589
1787
  }
@@ -1598,8 +1796,11 @@ var init_sync = __esm({
1598
1796
  init_safeWrite();
1599
1797
  init_gitignore();
1600
1798
  init_catalog();
1799
+ init_shape_agents();
1601
1800
  init_dialects();
1801
+ init_template();
1602
1802
  init_markers();
1803
+ init_conflict();
1603
1804
  }
1604
1805
  });
1605
1806
 
@@ -1608,7 +1809,7 @@ var init_exports = {};
1608
1809
  __export(init_exports, {
1609
1810
  runInit: () => runInit
1610
1811
  });
1611
- import { basename } from "path";
1812
+ import { basename as basename3 } from "path";
1612
1813
  async function runInit(argv, headless) {
1613
1814
  const dryRun = argv["dry-run"];
1614
1815
  if (!argv["skip-git-check"] && !headless && isGitRepo() && isDirtyTree()) {
@@ -1629,13 +1830,13 @@ async function runInit(argv, headless) {
1629
1830
  }
1630
1831
  let config;
1631
1832
  if (headless || argv.yes) {
1632
- const projectName = basename(process.cwd());
1833
+ const projectName = basename3(process.cwd());
1633
1834
  const tools = parseTools(argv.tools);
1634
1835
  const scope = argv.preset ?? "standard";
1635
1836
  const integrations = defaultIntegrationsForScope(scope);
1636
- config = buildDefaultConfig(projectName, "", tools, scope, integrations, []);
1837
+ config = buildDefaultConfig(projectName, "", tools, scope, integrations, [], defaultSpecialtyAgents(scope));
1637
1838
  } else {
1638
- const answers = await runWizard(basename(process.cwd()));
1839
+ const answers = await runWizard(basename3(process.cwd()));
1639
1840
  const defaultIntegrations = defaultIntegrationsForScope(answers.scope);
1640
1841
  const integrations = answers.integrations.length > 0 ? answers.integrations : defaultIntegrations;
1641
1842
  config = buildDefaultConfig(
@@ -1644,7 +1845,8 @@ async function runInit(argv, headless) {
1644
1845
  answers.tools,
1645
1846
  answers.scope,
1646
1847
  integrations,
1647
- answers.shape
1848
+ answers.shape,
1849
+ answers.specialtyAgents
1648
1850
  );
1649
1851
  }
1650
1852
  writeConfig(config, argv.config ?? ".aikitrc.json", dryRun);
@@ -1662,7 +1864,9 @@ function defaultIntegrationsForScope(scope) {
1662
1864
  if (scope === "standard") return ["mcp", "hooks", "commands", "subagents", "ci", "husky"];
1663
1865
  return ["mcp", "hooks", "commands", "subagents", "ci", "husky", "devcontainer", "plugin", "otel"];
1664
1866
  }
1665
- function buildDefaultConfig(projectName, projectDescription, tools, scope, integrations, shape) {
1867
+ function buildDefaultConfig(projectName, projectDescription, tools, scope, integrations, shape, specialtyAgents) {
1868
+ const allSpecialtyValues = new Set(SPECIALTY_TIER2_AGENTS.map((a2) => a2.value));
1869
+ const agentTier2 = specialtyAgents.length === allSpecialtyValues.size && specialtyAgents.every((a2) => allSpecialtyValues.has(a2)) ? "all" : specialtyAgents;
1666
1870
  return {
1667
1871
  version: 1,
1668
1872
  projectName,
@@ -1682,6 +1886,7 @@ function buildDefaultConfig(projectName, projectDescription, tools, scope, integ
1682
1886
  otel: integrations.includes("otel")
1683
1887
  },
1684
1888
  skills: { tier1: "all", tier2: "all", tier3: [] },
1889
+ agents: { tier1: "all", tier2: agentTier2, tier3: [] },
1685
1890
  canonical: "AGENTS.md"
1686
1891
  };
1687
1892
  }
@@ -1729,7 +1934,8 @@ async function runDiff(argv) {
1729
1934
  }
1730
1935
  checkCatalogDir("skills/tier1", ".claude/skills", missing, drifted);
1731
1936
  checkCatalogDir("skills/tier2", ".claude/skills", missing, drifted);
1732
- checkCatalogAgents(config.shape, missing, drifted);
1937
+ checkCatalogDir("agents/tier1", ".claude/agents", missing, drifted);
1938
+ checkCatalogAgentsTier2(config, missing, drifted);
1733
1939
  checkCatalogDir("hooks", ".claude/hooks", missing, drifted, [".sh"]);
1734
1940
  if (config.tools.includes("claude") && config.scope !== "minimal") {
1735
1941
  checkCatalogDir("rules/claude-rules", ".claude/rules", missing, drifted);
@@ -1772,32 +1978,40 @@ async function runDiff(argv) {
1772
1978
  Run ${kleur_default.cyan("aikit sync")} to update all, or ${kleur_default.cyan("aikit update")} to review changes first.
1773
1979
  `);
1774
1980
  }
1775
- function checkCatalogAgents(shapes, missing, drifted) {
1776
- const required = new Set(CORE_AGENTS);
1777
- for (const shape of shapes) {
1778
- for (const agent of SHAPE_AGENTS[shape] ?? []) required.add(agent);
1981
+ function extractRegion(content, begin, end) {
1982
+ const bi = content.indexOf(begin);
1983
+ const ei = content.indexOf(end);
1984
+ if (bi === -1 || ei === -1) return "";
1985
+ return content.slice(bi + begin.length, ei).trim();
1986
+ }
1987
+ function checkCatalogAgentsTier2(config, missing, drifted) {
1988
+ const tier2Selection = config.agents?.tier2;
1989
+ if (tier2Selection === "all") {
1990
+ checkCatalogDir("agents/tier2", ".claude/agents", missing, drifted);
1991
+ return;
1779
1992
  }
1780
- const catalogDir = join3(CATALOG_ROOT, "agents");
1781
- if (!existsSync6(catalogDir)) return;
1782
- for (const agent of required) {
1783
- const installed = join3(".claude/agents", `${agent}.md`);
1784
- const catalogPath = join3(catalogDir, `${agent}.md`);
1993
+ const expected = new Set(
1994
+ Array.isArray(tier2Selection) ? tier2Selection : []
1995
+ );
1996
+ for (const agent of resolveShapeAgents(config.shape)) {
1997
+ expected.add(agent);
1998
+ }
1999
+ if (expected.size === 0) return;
2000
+ const tier2Dir = join3(CATALOG_ROOT, "agents", "tier2");
2001
+ if (!existsSync6(tier2Dir)) return;
2002
+ for (const name of expected) {
2003
+ const catalogPath = join3(tier2Dir, `${name}.md`);
2004
+ const installedPath = join3(".claude/agents", `${name}.md`);
1785
2005
  if (!existsSync6(catalogPath)) continue;
1786
- if (!existsSync6(installed)) {
1787
- missing.push(installed);
2006
+ if (!existsSync6(installedPath)) {
2007
+ missing.push(`agents/${name}.md`);
1788
2008
  continue;
1789
2009
  }
1790
- if (readFileSync6(catalogPath, "utf8") !== readFileSync6(installed, "utf8")) {
1791
- drifted.push(installed);
2010
+ if (readFileSync6(catalogPath, "utf8") !== readFileSync6(installedPath, "utf8")) {
2011
+ drifted.push(`agents/${name}.md`);
1792
2012
  }
1793
2013
  }
1794
2014
  }
1795
- function extractRegion(content, begin, end) {
1796
- const bi = content.indexOf(begin);
1797
- const ei = content.indexOf(end);
1798
- if (bi === -1 || ei === -1) return "";
1799
- return content.slice(bi + begin.length, ei).trim();
1800
- }
1801
2015
  function checkCatalogDir(catalogSubdir, installedDir, missing, drifted, extensions = [".md"]) {
1802
2016
  const catalogDir = join3(CATALOG_ROOT, catalogSubdir);
1803
2017
  if (!existsSync6(catalogDir)) return;
@@ -1817,30 +2031,13 @@ function checkCatalogDir(catalogSubdir, installedDir, missing, drifted, extensio
1817
2031
  }
1818
2032
  }
1819
2033
  }
1820
- var CORE_AGENTS, SHAPE_AGENTS;
1821
- var init_diff = __esm({
2034
+ var init_diff2 = __esm({
1822
2035
  "src/commands/diff.ts"() {
1823
2036
  "use strict";
1824
2037
  init_kleur();
1825
2038
  init_readConfig();
1826
2039
  init_catalog();
1827
- CORE_AGENTS = [
1828
- "orchestrator",
1829
- "planner",
1830
- "researcher",
1831
- "implementer",
1832
- "reviewer",
1833
- "tester",
1834
- "security-auditor",
1835
- "devops"
1836
- ];
1837
- SHAPE_AGENTS = {
1838
- web: ["frontend"],
1839
- fullstack: ["frontend", "backend"],
1840
- backend: ["backend"],
1841
- mobile: ["mobile"],
1842
- library: ["backend"]
1843
- };
2040
+ init_shape_agents();
1844
2041
  }
1845
2042
  });
1846
2043
 
@@ -1855,7 +2052,7 @@ async function runUpdate(argv) {
1855
2052
  v2.error(".aikitrc.json not found. Run `aikit` to initialise.");
1856
2053
  process.exit(1);
1857
2054
  }
1858
- const { runDiff: runDiff2 } = await Promise.resolve().then(() => (init_diff(), diff_exports));
2055
+ const { runDiff: runDiff2 } = await Promise.resolve().then(() => (init_diff2(), diff_exports));
1859
2056
  await runDiff2({ ...argv, "dry-run": true });
1860
2057
  if (argv.yes) {
1861
2058
  const { runSync: runSync3 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
@@ -1951,7 +2148,22 @@ function findCatalogItem(name) {
1951
2148
  }
1952
2149
  }
1953
2150
  }
1954
- for (const type of ["agent", "hook"]) {
2151
+ for (const tier of ["tier1", "tier2"]) {
2152
+ const dir = join4(CATALOG_ROOT, "agents", tier);
2153
+ if (existsSync7(dir)) {
2154
+ const candidate = join4(dir, `${name}.md`);
2155
+ if (existsSync7(candidate)) {
2156
+ return {
2157
+ type: "agent",
2158
+ name,
2159
+ srcFile: candidate,
2160
+ destFile: join4(".claude/agents", `${name}.md`),
2161
+ destDir: ".claude/agents"
2162
+ };
2163
+ }
2164
+ }
2165
+ }
2166
+ for (const type of ["hook"]) {
1955
2167
  const spec = ITEM_DIRS[type];
1956
2168
  const catalogDir = join4(CATALOG_ROOT, spec.catalog);
1957
2169
  if (!existsSync7(catalogDir)) continue;
@@ -1981,6 +2193,26 @@ function updateConfigForAddition(configPath, item) {
1981
2193
  updated = { ...config, shape: [...config.shape, shape] };
1982
2194
  message = `Added "${shape}" to .aikitrc.json shape (so sync re-installs ${item.name})`;
1983
2195
  }
2196
+ const agentTier = detectAgentTier(item.name);
2197
+ if (agentTier === "tier2" && !shape) {
2198
+ const currentAgents = config.agents ?? { tier1: "all", tier2: "all", tier3: [] };
2199
+ const tier2 = currentAgents.tier2;
2200
+ if (tier2 !== "all" && !tier2.includes(item.name)) {
2201
+ updated = {
2202
+ ...updated ?? config,
2203
+ agents: { ...currentAgents, tier2: [...tier2, item.name] }
2204
+ };
2205
+ message = `Added "${item.name}" to .aikitrc.json agents.tier2`;
2206
+ }
2207
+ }
2208
+ if (agentTier === "tier3" && !(config.agents?.tier3 ?? []).includes(item.name)) {
2209
+ const currentAgents = config.agents ?? { tier1: "all", tier2: "all", tier3: [] };
2210
+ updated = {
2211
+ ...updated ?? config,
2212
+ agents: { ...currentAgents, tier3: [...currentAgents.tier3, item.name] }
2213
+ };
2214
+ message = `Added "${item.name}" to .aikitrc.json agents.tier3`;
2215
+ }
1984
2216
  } else if (item.type === "skill") {
1985
2217
  const skillTier = detectSkillTier(item.name);
1986
2218
  if (skillTier === "tier3" && !config.skills.tier3.includes(item.name)) {
@@ -2013,9 +2245,17 @@ function detectSkillTier(name) {
2013
2245
  }
2014
2246
  return "tier3";
2015
2247
  }
2248
+ function detectAgentTier(name) {
2249
+ for (const tier of ["tier1", "tier2"]) {
2250
+ if (existsSync7(join4(CATALOG_ROOT, "agents", tier, `${name}.md`))) {
2251
+ return tier;
2252
+ }
2253
+ }
2254
+ return "tier3";
2255
+ }
2016
2256
  function listAllCatalogItems() {
2017
2257
  const items = [];
2018
- for (const subdir of ["skills/tier1", "skills/tier2", "agents", "hooks"]) {
2258
+ for (const subdir of ["skills/tier1", "skills/tier2", "agents/tier1", "agents/tier2", "hooks"]) {
2019
2259
  const dir = join4(CATALOG_ROOT, subdir);
2020
2260
  if (existsSync7(dir)) {
2021
2261
  readdirSync3(dir).filter((f2) => f2.endsWith(".md") || f2.endsWith(".sh")).forEach((f2) => items.push(f2.replace(/\.(md|sh)$/, "")));
@@ -2033,7 +2273,7 @@ var init_add = __esm({
2033
2273
  init_readConfig();
2034
2274
  ITEM_DIRS = {
2035
2275
  skill: { catalog: "skills/tier1", dest: ".claude/skills", ext: [".md"] },
2036
- agent: { catalog: "agents", dest: ".claude/agents", ext: [".md"] },
2276
+ agent: { catalog: "agents/tier1", dest: ".claude/agents", ext: [".md"] },
2037
2277
  hook: { catalog: "hooks", dest: ".claude/hooks", ext: [".sh"] }
2038
2278
  };
2039
2279
  AGENT_TO_SHAPE = {
@@ -2055,8 +2295,9 @@ async function runList(_argv) {
2055
2295
  const sections = [
2056
2296
  { label: "Skills \u2014 Tier 1", items: listCategory("skills/tier1", ".claude/skills") },
2057
2297
  { label: "Skills \u2014 Tier 2", items: listCategory("skills/tier2", ".claude/skills") },
2298
+ { label: "Agents \u2014 Tier 1", items: listCategory("agents/tier1", ".claude/agents") },
2299
+ { label: "Agents \u2014 Tier 2", items: listCategory("agents/tier2", ".claude/agents") },
2058
2300
  { label: "Slash commands", items: listCategory("commands", ".claude/commands") },
2059
- { label: "Agents", items: listCategory("agents", ".claude/agents") },
2060
2301
  { label: "Hooks", items: listCategory("hooks", ".claude/hooks", [".sh"]) },
2061
2302
  { label: "Path-scoped rules", items: listClaudeRules() }
2062
2303
  ];
@@ -3136,7 +3377,7 @@ function isInteractive() {
3136
3377
  }
3137
3378
 
3138
3379
  // src/cli.ts
3139
- var VERSION = "0.5.0";
3380
+ var VERSION = "0.7.1";
3140
3381
  var HELP = `
3141
3382
  haac-aikit \u2014 the batteries-included AI-agentic-coding kit
3142
3383
 
@@ -3217,7 +3458,7 @@ async function main() {
3217
3458
  break;
3218
3459
  }
3219
3460
  case "diff": {
3220
- const { runDiff: runDiff2 } = await Promise.resolve().then(() => (init_diff(), diff_exports));
3461
+ const { runDiff: runDiff2 } = await Promise.resolve().then(() => (init_diff2(), diff_exports));
3221
3462
  await runDiff2(argv);
3222
3463
  break;
3223
3464
  }