@cardor/agent-harness-kit 1.8.1 → 1.10.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/dist/cli.js CHANGED
@@ -1,10 +1,19 @@
1
1
  import {
2
2
  findConfigFile,
3
3
  loadConfig
4
- } from "./chunk-OEPZRC7J.js";
4
+ } from "./chunk-ADV7OPU2.js";
5
+ import {
6
+ getRowCounts,
7
+ isEmptyDatabase,
8
+ openDB,
9
+ readStorageStateFile,
10
+ resolveGlobalStorageDir,
11
+ resolveSqlitePathForScope
12
+ } from "./chunk-DNFFWQWR.js";
5
13
 
6
14
  // src/cli.ts
7
15
  import { Command } from "commander";
16
+ import pc19 from "picocolors";
8
17
 
9
18
  // src/commands/build.ts
10
19
  import { watch } from "fs";
@@ -12,8 +21,8 @@ import * as p from "@clack/prompts";
12
21
  import pc from "picocolors";
13
22
 
14
23
  // src/core/materializer/claude-code.ts
15
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
16
- import { join as join4, resolve as resolve3 } from "path";
24
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
25
+ import { join as join5, resolve as resolve3 } from "path";
17
26
 
18
27
  // src/utils/file.ts
19
28
  import { mkdirSync, writeFileSync } from "fs";
@@ -24,29 +33,88 @@ var write = (cwd2, relPath, content, mode) => {
24
33
  writeFileSync(abs, content, { encoding: "utf8", mode });
25
34
  };
26
35
 
36
+ // src/core/materializer/detect-package-manager.ts
37
+ import { existsSync, readFileSync } from "fs";
38
+ import { join as join2 } from "path";
39
+ function detectPackageManager(cwd2) {
40
+ const fromField = detectFromPackageManagerField(cwd2);
41
+ if (fromField) return fromField;
42
+ if (existsSync(join2(cwd2, "pnpm-lock.yaml"))) return "pnpm";
43
+ if (existsSync(join2(cwd2, "bun.lockb")) || existsSync(join2(cwd2, "bun.lock"))) return "bun";
44
+ if (existsSync(join2(cwd2, "yarn.lock"))) {
45
+ return existsSync(join2(cwd2, ".yarnrc.yml")) ? "yarn-berry" : "yarn-classic";
46
+ }
47
+ if (existsSync(join2(cwd2, "package-lock.json"))) return "npm";
48
+ return "npm";
49
+ }
50
+ function detectFromPackageManagerField(cwd2) {
51
+ const pkgPath2 = join2(cwd2, "package.json");
52
+ if (!existsSync(pkgPath2)) return null;
53
+ try {
54
+ const pkg2 = JSON.parse(readFileSync(pkgPath2, "utf8"));
55
+ const field = pkg2?.packageManager;
56
+ if (typeof field !== "string" || !field.trim()) return null;
57
+ const match = field.match(/^([a-z]+)@(\d+)/i);
58
+ if (!match) return null;
59
+ const [, rawName, majorStr] = match;
60
+ const name = rawName.toLowerCase();
61
+ const major = Number(majorStr);
62
+ switch (name) {
63
+ case "npm":
64
+ return "npm";
65
+ case "pnpm":
66
+ return "pnpm";
67
+ case "bun":
68
+ return "bun";
69
+ case "yarn":
70
+ return major >= 2 ? "yarn-berry" : "yarn-classic";
71
+ default:
72
+ return null;
73
+ }
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function getMcpCommandParts(pm, port) {
79
+ const portStr = String(port);
80
+ switch (pm) {
81
+ case "pnpm":
82
+ return ["pnpm", "exec", "ahk", "serve", "--port", portStr];
83
+ case "yarn-classic":
84
+ case "yarn-berry":
85
+ return ["yarn", "run", "ahk", "serve", "--port", portStr];
86
+ case "bun":
87
+ return ["bunx", "--no-install", "ahk", "serve", "--port", portStr];
88
+ case "npm":
89
+ default:
90
+ return ["npx", "--no", "ahk", "serve", "--port", portStr];
91
+ }
92
+ }
93
+
27
94
  // src/core/materializer/mcp-merge.ts
28
- import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
95
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
29
96
  import { dirname } from "path";
30
- function mergeClaudeMcpJson(filePath, port) {
97
+ function mergeClaudeMcpJson(filePath, port, pm = "npm") {
31
98
  const folderPath = dirname(filePath);
32
- if (!existsSync(folderPath)) {
99
+ if (!existsSync2(folderPath)) {
33
100
  mkdirSync2(folderPath, { recursive: true });
34
101
  }
35
102
  let existing = {};
36
- if (existsSync(filePath)) {
103
+ if (existsSync2(filePath)) {
37
104
  try {
38
- existing = JSON.parse(readFileSync(filePath, "utf8"));
105
+ existing = JSON.parse(readFileSync2(filePath, "utf8"));
39
106
  } catch {
40
107
  }
41
108
  }
109
+ const [command, ...args] = getMcpCommandParts(pm, port);
42
110
  const merged = {
43
111
  ...existing,
44
112
  mcpServers: {
45
113
  ...existing.mcpServers ?? {},
46
114
  "agent-harness-kit": {
47
115
  type: "stdio",
48
- command: "npx",
49
- args: ["ahk", "serve", "--port", String(port)]
116
+ command,
117
+ args
50
118
  }
51
119
  }
52
120
  };
@@ -56,9 +124,9 @@ function mergeClaudeMcpJson(filePath, port) {
56
124
  function mergeClaudeSettingsJson(filePath) {
57
125
  mkdirSync2(dirname(filePath), { recursive: true });
58
126
  let existing = {};
59
- if (existsSync(filePath)) {
127
+ if (existsSync2(filePath)) {
60
128
  try {
61
- existing = JSON.parse(readFileSync(filePath, "utf8"));
129
+ existing = JSON.parse(readFileSync2(filePath, "utf8"));
62
130
  } catch {
63
131
  }
64
132
  }
@@ -163,9 +231,9 @@ var MCP_CLAUDE_PERMISSIONS = [
163
231
  function mergeClaudeSettingsLocalJson(filePath) {
164
232
  mkdirSync2(dirname(filePath), { recursive: true });
165
233
  let existing = {};
166
- if (existsSync(filePath)) {
234
+ if (existsSync2(filePath)) {
167
235
  try {
168
- existing = JSON.parse(readFileSync(filePath, "utf8"));
236
+ existing = JSON.parse(readFileSync2(filePath, "utf8"));
169
237
  } catch {
170
238
  }
171
239
  }
@@ -184,15 +252,15 @@ function mergeClaudeSettingsLocalJson(filePath) {
184
252
  };
185
253
  writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
186
254
  }
187
- function mergeOpencodeJson(filePath, port) {
255
+ function mergeOpencodeJson(filePath, port, pm = "npm") {
188
256
  const folderPath = dirname(filePath);
189
- if (!existsSync(folderPath)) {
257
+ if (!existsSync2(folderPath)) {
190
258
  mkdirSync2(folderPath, { recursive: true });
191
259
  }
192
260
  let existing = {};
193
- if (existsSync(filePath)) {
261
+ if (existsSync2(filePath)) {
194
262
  try {
195
- existing = JSON.parse(readFileSync(filePath, "utf8"));
263
+ existing = JSON.parse(readFileSync2(filePath, "utf8"));
196
264
  } catch {
197
265
  }
198
266
  }
@@ -207,7 +275,9 @@ function mergeOpencodeJson(filePath, port) {
207
275
  "agent-harness-kit": {
208
276
  enabled: true,
209
277
  type: "local",
210
- command: ["npx", "ahk", "serve", "--port", String(port)]
278
+ // OpenCode's mcp.<name>.command field is a single array (unlike
279
+ // Claude/Codex, which split command/args) — pass the full token list.
280
+ command: getMcpCommandParts(pm, port)
211
281
  }
212
282
  }
213
283
  };
@@ -237,15 +307,16 @@ function mergeTomlSection(content, sectionName, sectionBody) {
237
307
  ];
238
308
  return newLines.join("\n");
239
309
  }
240
- function mergeCodexConfigToml(filePath, port) {
310
+ function mergeCodexConfigToml(filePath, port, pm = "npm") {
241
311
  mkdirSync2(dirname(filePath), { recursive: true });
242
312
  let content = "";
243
- if (existsSync(filePath)) {
244
- content = readFileSync(filePath, "utf8");
313
+ if (existsSync2(filePath)) {
314
+ content = readFileSync2(filePath, "utf8");
245
315
  }
316
+ const [command, ...args] = getMcpCommandParts(pm, port);
246
317
  const sectionBody = [
247
- 'command = "npx"',
248
- `args = ["ahk", "serve", "--port", "${port}"]`,
318
+ `command = ${JSON.stringify(command)}`,
319
+ `args = ${JSON.stringify(args)}`,
249
320
  'default_tools_approval_mode = "auto"'
250
321
  ].join("\n");
251
322
  content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
@@ -253,18 +324,18 @@ function mergeCodexConfigToml(filePath, port) {
253
324
  }
254
325
 
255
326
  // src/core/materializer/scaffold-utils.ts
256
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
257
- import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
327
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
328
+ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
258
329
  import { fileURLToPath as fileURLToPath2 } from "url";
259
330
 
260
331
  // src/core/materializer/templates.ts
261
- import { readFileSync as readFileSync2 } from "fs";
262
- import { dirname as dirname2, join as join2 } from "path";
332
+ import { readFileSync as readFileSync3 } from "fs";
333
+ import { dirname as dirname2, join as join3 } from "path";
263
334
  import { fileURLToPath } from "url";
264
335
  var __dirname = dirname2(fileURLToPath(import.meta.url));
265
- var TEMPLATES_DIR = join2(__dirname, "agent-templates");
336
+ var TEMPLATES_DIR = join3(__dirname, "agent-templates");
266
337
  function loadAgentTemplate(name, vars = {}) {
267
- const raw = readFileSync2(join2(TEMPLATES_DIR, `${name}.md`), "utf8");
338
+ const raw = readFileSync3(join3(TEMPLATES_DIR, `${name}.md`), "utf8");
268
339
  return raw.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
269
340
  }
270
341
  var HEALTH_SH = `#!/usr/bin/env bash
@@ -456,7 +527,11 @@ If orchestrating: Agent definition files in .claude/agents/
456
527
  \`\`\`
457
528
  `;
458
529
  }
530
+ function modelField(model) {
531
+ return model ? `, model: ${JSON.stringify(model)}` : "";
532
+ }
459
533
  function configTs(params) {
534
+ const models = params.models ?? {};
460
535
  return `import { defineHarness } from '@cardor/agent-harness-kit'
461
536
 
462
537
  export default defineHarness({
@@ -469,11 +544,12 @@ export default defineHarness({
469
544
  provider: '${params.provider}',
470
545
 
471
546
  agents: {
472
- lead: { instructionsPath: null },
473
- explorer: { instructionsPath: null, allowedPaths: ['${params.docsPath}', './src'] },
474
- builder: { instructionsPath: null, writablePaths: ['./src', './tests'] },
475
- reviewer: { instructionsPath: null },
476
- custom: [],
547
+ lead: { instructionsPath: null${modelField(models.lead)} },
548
+ explorer: { instructionsPath: null, allowedPaths: ['${params.docsPath}', './src']${modelField(models.explorer)} },
549
+ builder: { instructionsPath: null, writablePaths: ['./src', './tests']${modelField(models.builder)} },
550
+ reviewer: { instructionsPath: null${modelField(models.reviewer)} },
551
+ ${models.consultant ? `consultant: { instructionsPath: null${modelField(models.consultant)} },
552
+ ` : ""}custom: [],
477
553
  },
478
554
 
479
555
  // SQLite (default). Switch to postgres/mysql by changing database.type.
@@ -492,6 +568,10 @@ export default defineHarness({
492
568
  nextSteps: false,
493
569
  },
494
570
  markdownFallback: { enabled: true, path: '.harness/current.md' },
571
+ // 'local' \u2014 DB lives in .harness/ (project-relative). 'global' \u2014 DB lives
572
+ // under ~/.harness/dbs/<projectId>/, outside the project tree.
573
+ scope: '${params.scope}',
574
+ projectId: '${params.projectId}',
495
575
  },
496
576
 
497
577
  health: {
@@ -508,6 +588,7 @@ export default defineHarness({
508
588
  }
509
589
  var configMjs = configTs;
510
590
  function configCjs(params) {
591
+ const models = params.models ?? {};
511
592
  return `const { defineHarness } = require('@cardor/agent-harness-kit')
512
593
 
513
594
  module.exports = defineHarness({
@@ -520,11 +601,12 @@ module.exports = defineHarness({
520
601
  provider: '${params.provider}',
521
602
 
522
603
  agents: {
523
- lead: { instructionsPath: null },
524
- explorer: { instructionsPath: null, allowedPaths: ['${params.docsPath}', './src'] },
525
- builder: { instructionsPath: null, writablePaths: ['./src', './tests'] },
526
- reviewer: { instructionsPath: null },
527
- custom: [],
604
+ lead: { instructionsPath: null${modelField(models.lead)} },
605
+ explorer: { instructionsPath: null, allowedPaths: ['${params.docsPath}', './src']${modelField(models.explorer)} },
606
+ builder: { instructionsPath: null, writablePaths: ['./src', './tests']${modelField(models.builder)} },
607
+ reviewer: { instructionsPath: null${modelField(models.reviewer)} },
608
+ ${models.consultant ? `consultant: { instructionsPath: null${modelField(models.consultant)} },
609
+ ` : ""}custom: [],
528
610
  },
529
611
 
530
612
  // SQLite (default). Switch to postgres/mysql by changing database.type.
@@ -543,6 +625,10 @@ module.exports = defineHarness({
543
625
  nextSteps: false,
544
626
  },
545
627
  markdownFallback: { enabled: true, path: '.harness/current.md' },
628
+ // 'local' \u2014 DB lives in .harness/ (project-relative). 'global' \u2014 DB lives
629
+ // under ~/.harness/dbs/<projectId>/, outside the project tree.
630
+ scope: '${params.scope}',
631
+ projectId: '${params.projectId}',
546
632
  },
547
633
 
548
634
  health: {
@@ -590,11 +676,14 @@ function stripFrontmatter(md) {
590
676
  }
591
677
  return { description, body };
592
678
  }
593
- function toCodexToml(name, description, body, sandboxMode) {
679
+ function toCodexToml(name, description, body, sandboxMode, model) {
594
680
  const safe = (s) => s.replace(/"""/g, '""\\u0022');
681
+ const trimmedModel = model?.trim() ?? "";
682
+ const modelLine = trimmedModel.length >= 3 ? `model = "${trimmedModel}"
683
+ ` : "";
595
684
  return `name = "${name}"
596
685
  sandbox_mode = "${sandboxMode}"
597
-
686
+ ${modelLine}
598
687
  description = """
599
688
  ${safe(description)}
600
689
  """
@@ -606,29 +695,29 @@ ${safe(body.trimEnd())}
606
695
  }
607
696
  function agentLeadToml(vars) {
608
697
  const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
609
- return toCodexToml("lead", description, body, "read-only");
698
+ return toCodexToml("lead", description, body, "read-only", vars.model);
610
699
  }
611
700
  function agentLeadAsDefaultToml(vars) {
612
701
  const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
613
- return toCodexToml("default", description, body, "read-only");
702
+ return toCodexToml("default", description, body, "read-only", vars.model);
614
703
  }
615
704
  function agentExplorerToml(vars) {
616
705
  const { description, body } = stripFrontmatter(loadAgentTemplate("explorer", vars));
617
- return toCodexToml("explorer", description, body, "read-only");
706
+ return toCodexToml("explorer", description, body, "read-only", vars.model);
618
707
  }
619
708
  function agentBuilderToml(vars) {
620
709
  const { description, body } = stripFrontmatter(loadAgentTemplate("builder", vars));
621
- return toCodexToml("builder", description, body, "workspace-write");
710
+ return toCodexToml("builder", description, body, "workspace-write", vars.model);
622
711
  }
623
712
  function agentReviewerToml(vars) {
624
713
  const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
625
- return toCodexToml("reviewer", description, body, "read-only");
714
+ return toCodexToml("reviewer", description, body, "read-only", vars.model);
626
715
  }
627
716
  function agentConsultantToml(vars) {
628
717
  const { description, body } = stripFrontmatter(loadAgentTemplate("consultant", vars));
629
- return toCodexToml("consultant", description, body, "read-only");
718
+ return toCodexToml("consultant", description, body, "read-only", vars.model);
630
719
  }
631
- function translateFrontmatterForClaudeCode(md, agentName) {
720
+ function translateFrontmatterForClaudeCode(md, agentName, model) {
632
721
  const permissionsMap = {
633
722
  lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
634
723
  explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
@@ -638,13 +727,24 @@ function translateFrontmatterForClaudeCode(md, agentName) {
638
727
  };
639
728
  const permissions = permissionsMap[agentName] ?? MCP_CLAUDE_PERMISSIONS;
640
729
  const mcpLines = permissions.map((t) => ` - ${t}`).join("\n");
641
- return md.replace(/(tools:\n(?: - (?!mcp__)[^\n]+\n)+)/, (match) => {
730
+ let result = md.replace(/(tools:\n(?: - (?!mcp__)[^\n]+\n)+)/, (match) => {
642
731
  const trimmed = match.trimEnd();
643
732
  return `${trimmed}
644
733
  - Task
645
734
  ${mcpLines}
646
735
  `;
647
736
  });
737
+ if (model) {
738
+ result = injectModelFrontmatterLine(result, model);
739
+ }
740
+ return result;
741
+ }
742
+ function injectModelFrontmatterLine(md, model) {
743
+ if (/^model:\s*.*$/m.test(md)) {
744
+ return md.replace(/^model:\s*.*$/m, `model: ${model}`);
745
+ }
746
+ return md.replace(/^(name:.*)$/m, `$1
747
+ model: ${model}`);
648
748
  }
649
749
  function translateFrontmatterForOpenCode(md) {
650
750
  return md.replace(/(tools:\n(?: - [^\n]+\n)+)/, (match) => {
@@ -663,14 +763,14 @@ var GITIGNORE_ENTRIES = `
663
763
  // src/core/materializer/scaffold-utils.ts
664
764
  var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
665
765
  function writeAgentFile(cwd2, relPath, content) {
666
- const abs = join3(cwd2, relPath);
667
- if (existsSync2(abs)) return;
766
+ const abs = join4(cwd2, relPath);
767
+ if (existsSync3(abs)) return;
668
768
  mkdirSync3(resolve2(abs, ".."), { recursive: true });
669
769
  writeFileSync3(abs, content, "utf8");
670
770
  }
671
771
  function appendGitignore(cwd2) {
672
- const giPath = join3(cwd2, ".gitignore");
673
- const existing = existsSync2(giPath) ? readFileSync3(giPath, "utf8") : "";
772
+ const giPath = join4(cwd2, ".gitignore");
773
+ const existing = existsSync3(giPath) ? readFileSync4(giPath, "utf8") : "";
674
774
  const toAdd = GITIGNORE_ENTRIES.split("\n").filter((line) => line && !existing.includes(line)).join("\n");
675
775
  if (toAdd.trim()) {
676
776
  writeFileSync3(giPath, existing + (existing.endsWith("\n") ? "" : "\n") + toAdd + "\n", "utf8");
@@ -680,15 +780,23 @@ function slugify(title) {
680
780
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
681
781
  }
682
782
  function writeSkills(cwd2, skillsDir) {
683
- const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage"];
783
+ const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review"];
684
784
  for (const skillName of skillNames) {
685
- const src = join3(__dirname2, "skills", skillName, "SKILL.md");
686
- const destDir = join3(cwd2, skillsDir, skillName);
687
- const dest = join3(destDir, "SKILL.md");
785
+ const src = join4(__dirname2, "skills", skillName, "SKILL.md");
786
+ const destDir = join4(cwd2, skillsDir, skillName);
787
+ const dest = join4(destDir, "SKILL.md");
688
788
  mkdirSync3(destDir, { recursive: true });
689
- writeFileSync3(dest, readFileSync3(src, "utf8"), "utf8");
789
+ writeFileSync3(dest, readFileSync4(src, "utf8"), "utf8");
690
790
  }
691
791
  }
792
+ function writeSkill(skillsRoot, skillName) {
793
+ const src = join4(__dirname2, "skills", skillName, "SKILL.md");
794
+ const destDir = join4(skillsRoot, skillName);
795
+ const dest = join4(destDir, "SKILL.md");
796
+ if (existsSync3(dest)) return;
797
+ mkdirSync3(destDir, { recursive: true });
798
+ writeFileSync3(dest, readFileSync4(src, "utf8"), "utf8");
799
+ }
692
800
 
693
801
  // src/core/materializer/claude-code.ts
694
802
  var ClaudeCodeMaterializer = class {
@@ -696,12 +804,12 @@ var ClaudeCodeMaterializer = class {
696
804
  const { cwd: cwd2 } = opts;
697
805
  write(cwd2, "AGENTS.md", agentsMd(config));
698
806
  write(cwd2, "CLAUDE.md", claudeMd(config));
699
- if (!existsSync3(join4(cwd2, "health.sh"))) {
807
+ if (!existsSync4(join5(cwd2, "health.sh"))) {
700
808
  write(cwd2, "health.sh", HEALTH_SH, 493);
701
809
  }
702
810
  const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
703
811
  write(cwd2, "feature_list.json", featureListJson(tasks));
704
- if (!existsSync3(join4(cwd2, config.storage.markdownFallback.path))) {
812
+ if (!existsSync4(join5(cwd2, config.storage.markdownFallback.path))) {
705
813
  write(
706
814
  cwd2,
707
815
  config.storage.markdownFallback.path,
@@ -717,20 +825,25 @@ No tasks in progress.
717
825
  const projectName = config.project.name;
718
826
  const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
719
827
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
720
- writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
721
- writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
722
- writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
723
- writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
724
- writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
725
- mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
726
- mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
727
- mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
828
+ const leadModel = config.agents.lead.model;
829
+ const explorerModel = config.agents.explorer.model;
830
+ const consultantModel = config.agents.consultant?.model;
831
+ const builderModel = config.agents.builder.model;
832
+ const reviewerModel = config.agents.reviewer.model;
833
+ writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead", leadModel));
834
+ writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer", explorerModel));
835
+ writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant", consultantModel));
836
+ writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder", builderModel));
837
+ writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer", reviewerModel));
838
+ mergeClaudeMcpJson(join5(cwd2, ".mcp.json"), config.tools.mcp.port, detectPackageManager(cwd2));
839
+ mergeClaudeSettingsJson(join5(cwd2, ".claude/settings.json"));
840
+ mergeClaudeSettingsLocalJson(join5(cwd2, ".claude/settings.local.json"));
728
841
  appendGitignore(cwd2);
729
842
  writeSkills(cwd2, ".claude/skills");
730
843
  }
731
844
  async build(config, cwd2) {
732
845
  const write2 = (relPath, content) => {
733
- const abs = join4(cwd2, relPath);
846
+ const abs = join5(cwd2, relPath);
734
847
  mkdirSync4(resolve3(abs, ".."), { recursive: true });
735
848
  writeFileSync4(abs, content, "utf8");
736
849
  };
@@ -739,14 +852,19 @@ No tasks in progress.
739
852
  const projectName = config.project.name;
740
853
  const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
741
854
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
742
- write2(".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
743
- write2(".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
744
- write2(".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
745
- write2(".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
746
- write2(".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
747
- mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
748
- mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
749
- mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
855
+ const leadModel = config.agents.lead.model;
856
+ const explorerModel = config.agents.explorer.model;
857
+ const consultantModel = config.agents.consultant?.model;
858
+ const builderModel = config.agents.builder.model;
859
+ const reviewerModel = config.agents.reviewer.model;
860
+ write2(".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead", leadModel));
861
+ write2(".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer", explorerModel));
862
+ write2(".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant", consultantModel));
863
+ write2(".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder", builderModel));
864
+ write2(".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer", reviewerModel));
865
+ mergeClaudeMcpJson(join5(cwd2, ".mcp.json"), config.tools.mcp.port, detectPackageManager(cwd2));
866
+ mergeClaudeSettingsJson(join5(cwd2, ".claude/settings.json"));
867
+ mergeClaudeSettingsLocalJson(join5(cwd2, ".claude/settings.local.json"));
750
868
  writeSkills(cwd2, ".claude/skills");
751
869
  }
752
870
  async migrate(config, _to, _cwd) {
@@ -761,12 +879,12 @@ No tasks in progress.
761
879
  reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
762
880
  };
763
881
  for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
764
- const filePath = join4(cwd2, ".claude", "agents", `${agent}.md`);
765
- if (!existsSync3(filePath)) {
882
+ const filePath = join5(cwd2, ".claude", "agents", `${agent}.md`);
883
+ if (!existsSync4(filePath)) {
766
884
  console.log(` ${agent}.md not found \u2014 skipping`);
767
885
  continue;
768
886
  }
769
- const content = readFileSync4(filePath, "utf-8");
887
+ const content = readFileSync5(filePath, "utf-8");
770
888
  const updated = content.replace(
771
889
  /(tools:\n)((?: - [^\n]+\n)*)/m,
772
890
  (_match, header, toolsSection) => {
@@ -787,23 +905,23 @@ No tasks in progress.
787
905
  };
788
906
 
789
907
  // src/core/materializer/codex-cli.ts
790
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
791
- import { join as join5, resolve as resolve4 } from "path";
908
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
909
+ import { join as join6, resolve as resolve4 } from "path";
792
910
  var CodexCliMaterializer = class {
793
911
  async scaffold(config, opts) {
794
912
  const { cwd: cwd2 } = opts;
795
913
  const write2 = (relPath, content, mode) => {
796
- const abs = join5(cwd2, relPath);
914
+ const abs = join6(cwd2, relPath);
797
915
  mkdirSync5(resolve4(abs, ".."), { recursive: true });
798
916
  writeFileSync5(abs, content, { encoding: "utf8", mode });
799
917
  };
800
918
  write2("AGENTS.md", agentsMd(config));
801
- if (!existsSync4(join5(cwd2, "health.sh"))) {
919
+ if (!existsSync5(join6(cwd2, "health.sh"))) {
802
920
  write2("health.sh", HEALTH_SH, 493);
803
921
  }
804
922
  const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
805
- write2(join5(config.storage.dir, "feature_list.json"), featureListJson(tasks));
806
- if (!existsSync4(join5(cwd2, config.storage.markdownFallback.path))) {
923
+ write2(join6(config.storage.dir, "feature_list.json"), featureListJson(tasks));
924
+ if (!existsSync5(join6(cwd2, config.storage.markdownFallback.path))) {
807
925
  write2(
808
926
  config.storage.markdownFallback.path,
809
927
  `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
@@ -818,19 +936,24 @@ No tasks in progress.
818
936
  const projectName = config.project.name;
819
937
  const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
820
938
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
821
- writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
822
- writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
823
- writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName }));
824
- writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
825
- writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
826
- writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
827
- mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
939
+ const leadModel = config.agents.lead.model;
940
+ const explorerModel = config.agents.explorer.model;
941
+ const consultantModel = config.agents.consultant?.model;
942
+ const builderModel = config.agents.builder.model;
943
+ const reviewerModel = config.agents.reviewer.model;
944
+ writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName, model: leadModel }));
945
+ writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths, model: explorerModel }));
946
+ writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName, model: consultantModel }));
947
+ writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths, model: builderModel }));
948
+ writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName, model: reviewerModel }));
949
+ writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName, model: leadModel }));
950
+ mergeCodexConfigToml(join6(cwd2, ".codex/config.toml"), config.tools.mcp.port, detectPackageManager(cwd2));
828
951
  appendGitignore(cwd2);
829
952
  writeSkills(cwd2, ".agents/skills");
830
953
  }
831
954
  async build(config, cwd2) {
832
955
  const write2 = (relPath, content) => {
833
- const abs = join5(cwd2, relPath);
956
+ const abs = join6(cwd2, relPath);
834
957
  mkdirSync5(resolve4(abs, ".."), { recursive: true });
835
958
  writeFileSync5(abs, content, "utf8");
836
959
  };
@@ -838,13 +961,18 @@ No tasks in progress.
838
961
  const projectName = config.project.name;
839
962
  const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
840
963
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
841
- writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
842
- writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
843
- writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName }));
844
- writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
845
- writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
846
- writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
847
- mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
964
+ const leadModel = config.agents.lead.model;
965
+ const explorerModel = config.agents.explorer.model;
966
+ const consultantModel = config.agents.consultant?.model;
967
+ const builderModel = config.agents.builder.model;
968
+ const reviewerModel = config.agents.reviewer.model;
969
+ writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName, model: leadModel }));
970
+ writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths, model: explorerModel }));
971
+ writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName, model: consultantModel }));
972
+ writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths, model: builderModel }));
973
+ writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName, model: reviewerModel }));
974
+ writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName, model: leadModel }));
975
+ mergeCodexConfigToml(join6(cwd2, ".codex/config.toml"), config.tools.mcp.port, detectPackageManager(cwd2));
848
976
  writeSkills(cwd2, ".agents/skills");
849
977
  }
850
978
  async migrate(config, _to, _cwd) {
@@ -856,23 +984,23 @@ No tasks in progress.
856
984
  };
857
985
 
858
986
  // src/core/materializer/opencode.ts
859
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
860
- import { join as join6, resolve as resolve5 } from "path";
987
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
988
+ import { join as join7, resolve as resolve5 } from "path";
861
989
  var OpenCodeMaterializer = class {
862
990
  async scaffold(config, opts) {
863
991
  const { cwd: cwd2 } = opts;
864
992
  const write2 = (relPath, content, mode) => {
865
- const abs = join6(cwd2, relPath);
993
+ const abs = join7(cwd2, relPath);
866
994
  mkdirSync6(resolve5(abs, ".."), { recursive: true });
867
995
  writeFileSync6(abs, content, { encoding: "utf8", mode });
868
996
  };
869
997
  write2("AGENTS.md", agentsMd(config));
870
- if (!existsSync5(join6(cwd2, "health.sh"))) {
998
+ if (!existsSync6(join7(cwd2, "health.sh"))) {
871
999
  write2("health.sh", HEALTH_SH, 493);
872
1000
  }
873
1001
  const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
874
- write2(join6(config.storage.dir, "feature_list.json"), featureListJson(tasks));
875
- if (!existsSync5(join6(cwd2, config.storage.markdownFallback.path))) {
1002
+ write2(join7(config.storage.dir, "feature_list.json"), featureListJson(tasks));
1003
+ if (!existsSync6(join7(cwd2, config.storage.markdownFallback.path))) {
876
1004
  write2(
877
1005
  config.storage.markdownFallback.path,
878
1006
  `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
@@ -892,13 +1020,13 @@ No tasks in progress.
892
1020
  writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
893
1021
  writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
894
1022
  writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
895
- mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
1023
+ mergeOpencodeJson(join7(cwd2, "opencode.json"), config.tools.mcp.port, detectPackageManager(cwd2));
896
1024
  appendGitignore(cwd2);
897
1025
  writeSkills(cwd2, ".opencode/skills");
898
1026
  }
899
1027
  async build(config, cwd2) {
900
1028
  const write2 = (relPath, content) => {
901
- const abs = join6(cwd2, relPath);
1029
+ const abs = join7(cwd2, relPath);
902
1030
  mkdirSync6(resolve5(abs, ".."), { recursive: true });
903
1031
  writeFileSync6(abs, content, "utf8");
904
1032
  };
@@ -911,7 +1039,7 @@ No tasks in progress.
911
1039
  writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
912
1040
  writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
913
1041
  writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
914
- mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
1042
+ mergeOpencodeJson(join7(cwd2, "opencode.json"), config.tools.mcp.port, detectPackageManager(cwd2));
915
1043
  writeSkills(cwd2, ".opencode/skills");
916
1044
  }
917
1045
  async migrate(config, _to, _cwd) {
@@ -977,14 +1105,14 @@ async function buildOnce(cwd2) {
977
1105
  }
978
1106
 
979
1107
  // src/commands/dashboard.ts
980
- import { dirname as dirname5, join as join9, resolve as resolve7 } from "path";
1108
+ import { dirname as dirname4, join as join9, resolve as resolve6 } from "path";
981
1109
  import { fileURLToPath as fileURLToPath3 } from "url";
982
1110
  import pc2 from "picocolors";
983
1111
 
984
1112
  // src/core/dashboard-server.ts
985
1113
  import { watch as watch2 } from "fs";
986
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
987
- import { extname, join as join7 } from "path";
1114
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1115
+ import { extname, join as join8 } from "path";
988
1116
  import { serve } from "@hono/node-server";
989
1117
  import { Hono } from "hono";
990
1118
  import { WebSocketServer } from "ws";
@@ -1026,7 +1154,7 @@ var MIME = {
1026
1154
  ".ttf": "font/ttf"
1027
1155
  };
1028
1156
  function fileResponse(filePath) {
1029
- const content = readFileSync5(filePath);
1157
+ const content = readFileSync6(filePath);
1030
1158
  const mime = MIME[extname(filePath)] ?? "application/octet-stream";
1031
1159
  return new Response(content, {
1032
1160
  headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
@@ -1132,15 +1260,15 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
1132
1260
  app.get("/*", (c) => {
1133
1261
  const urlPath = c.req.path;
1134
1262
  if (urlPath !== "/") {
1135
- const candidate = join7(staticPath, urlPath);
1136
- if (existsSync6(candidate)) {
1263
+ const candidate = join8(staticPath, urlPath);
1264
+ if (existsSync7(candidate)) {
1137
1265
  try {
1138
1266
  return fileResponse(candidate);
1139
1267
  } catch {
1140
1268
  }
1141
1269
  }
1142
1270
  }
1143
- return fileResponse(join7(staticPath, "index.html"));
1271
+ return fileResponse(join8(staticPath, "index.html"));
1144
1272
  });
1145
1273
  const resolvedPort = await findFreePort(port);
1146
1274
  if (resolvedPort !== port) {
@@ -1171,7 +1299,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
1171
1299
  let watcher = null;
1172
1300
  if (dbPath) {
1173
1301
  const walPath = `${dbPath}-wal`;
1174
- const watchTarget = existsSync6(walPath) ? walPath : dbPath;
1302
+ const watchTarget = existsSync7(walPath) ? walPath : dbPath;
1175
1303
  watcher = watch2(watchTarget, broadcast);
1176
1304
  }
1177
1305
  return {
@@ -1185,635 +1313,12 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
1185
1313
  };
1186
1314
  }
1187
1315
 
1188
- // src/core/db.ts
1189
- import { randomUUID } from "crypto";
1190
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
1191
- import { dirname as dirname4, join as join8, resolve as resolve6 } from "path";
1192
-
1193
- // src/core/repositories/ActionRepository.ts
1194
- var ActionRepository = class {
1195
- constructor(driver) {
1196
- this.driver = driver;
1197
- }
1198
- driver;
1199
- async create(id, taskId, agent, now) {
1200
- await this.driver.exec(
1201
- `INSERT INTO actions (id, task_id, agent, status, created_at) VALUES (?, ?, ?, 'in_progress', ?)`,
1202
- [id, taskId, agent, now]
1203
- );
1204
- }
1205
- async complete(actionId, summary, now) {
1206
- await this.driver.exec(
1207
- `UPDATE actions SET status = 'completed', completed_at = ?, summary = ? WHERE id = ?`,
1208
- [now, summary, actionId]
1209
- );
1210
- }
1211
- async closeOrphaned(taskId, now) {
1212
- return this.driver.exec(
1213
- `UPDATE actions SET status = 'completed', completed_at = ?, summary = 'Auto-closed: task marked done' WHERE task_id = ? AND status = 'in_progress'`,
1214
- [now, taskId]
1215
- );
1216
- }
1217
- async getById(actionId) {
1218
- return this.driver.queryOne(`SELECT * FROM actions WHERE id = ?`, [actionId]);
1219
- }
1220
- async getForTask(taskId) {
1221
- return this.driver.query(
1222
- `SELECT * FROM actions WHERE task_id = ? ORDER BY created_at`,
1223
- [taskId]
1224
- );
1225
- }
1226
- async getAll() {
1227
- return this.driver.query(`SELECT * FROM actions ORDER BY created_at`);
1228
- }
1229
- async getWithDetails(taskId) {
1230
- const actions = await this.getForTask(taskId);
1231
- return Promise.all(
1232
- actions.map(async (action) => ({
1233
- ...action,
1234
- sections: await this.getSections(action.id),
1235
- files: await this.getFiles(action.id),
1236
- tools: await this.getTools(action.id)
1237
- }))
1238
- );
1239
- }
1240
- // ─── Sections ─────────────────────────────────────────────────────────────
1241
- async addSection(actionId, sectionType, content, now) {
1242
- await this.driver.exec(
1243
- `INSERT INTO action_sections (action_id, section_type, content, created_at) VALUES (?, ?, ?, ?)`,
1244
- [actionId, sectionType, content, now]
1245
- );
1246
- }
1247
- async getSections(actionId) {
1248
- return this.driver.query(
1249
- `SELECT * FROM action_sections WHERE action_id = ? ORDER BY created_at`,
1250
- [actionId]
1251
- );
1252
- }
1253
- async getAllSections() {
1254
- return this.driver.query(`SELECT * FROM action_sections ORDER BY created_at`);
1255
- }
1256
- // ─── Files ────────────────────────────────────────────────────────────────
1257
- async addFile(actionId, filePath, operation, notes) {
1258
- await this.driver.exec(
1259
- `INSERT INTO action_files (action_id, file_path, operation, notes) VALUES (?, ?, ?, ?)`,
1260
- [actionId, filePath, operation, notes]
1261
- );
1262
- }
1263
- async getFiles(actionId) {
1264
- return this.driver.query(
1265
- `SELECT * FROM action_files WHERE action_id = ?`,
1266
- [actionId]
1267
- );
1268
- }
1269
- async getFilesForTask(taskId) {
1270
- return this.driver.query(
1271
- `SELECT af.*, a.agent FROM action_files af JOIN actions a ON af.action_id = a.id WHERE a.task_id = ? ORDER BY a.agent, af.operation`,
1272
- [taskId]
1273
- );
1274
- }
1275
- // ─── Tools ────────────────────────────────────────────────────────────────
1276
- async addTool(actionId, toolName, argsJson, resultSummary, now) {
1277
- await this.driver.exec(
1278
- `INSERT INTO action_tools (action_id, tool_name, args_json, result_summary, called_at) VALUES (?, ?, ?, ?, ?)`,
1279
- [actionId, toolName, argsJson, resultSummary, now]
1280
- );
1281
- }
1282
- async getTools(actionId) {
1283
- return this.driver.query(
1284
- `SELECT * FROM action_tools WHERE action_id = ? ORDER BY called_at`,
1285
- [actionId]
1286
- );
1287
- }
1288
- async getTopTools(limit) {
1289
- return this.driver.query(
1290
- `SELECT tool_name, COUNT(*) as uses FROM action_tools GROUP BY tool_name ORDER BY uses DESC LIMIT ?`,
1291
- [limit]
1292
- );
1293
- }
1294
- };
1295
-
1296
- // src/core/repositories/StatsRepository.ts
1297
- var AGENT_ORDER = ["lead", "explorer", "builder", "reviewer"];
1298
- var StatsRepository = class {
1299
- constructor(driver) {
1300
- this.driver = driver;
1301
- }
1302
- driver;
1303
- async getCounts() {
1304
- const [{ total: totalActions }] = await this.driver.query(
1305
- `SELECT COUNT(*) as total FROM actions`
1306
- );
1307
- const [{ total: totalFiles }] = await this.driver.query(
1308
- `SELECT COUNT(*) as total FROM action_files`
1309
- );
1310
- const [{ total: uniqueTools }] = await this.driver.query(
1311
- `SELECT COUNT(DISTINCT tool_name) as total FROM action_tools`
1312
- );
1313
- const [{ total: activeAgents }] = await this.driver.query(
1314
- `SELECT COUNT(DISTINCT agent) as total FROM actions WHERE status = 'in_progress'`
1315
- );
1316
- return { totalActions, totalFiles, uniqueTools, activeAgents };
1317
- }
1318
- async getRecentTools(limit) {
1319
- return this.driver.query(
1320
- `SELECT at.*, t.id as task_id, t.title as task_title, t.slug as task_slug, a.agent
1321
- FROM action_tools at
1322
- JOIN actions a ON at.action_id = a.id
1323
- JOIN tasks t ON a.task_id = t.id
1324
- ORDER BY at.called_at DESC
1325
- LIMIT ?`,
1326
- [limit]
1327
- );
1328
- }
1329
- async getTopFiles(limit) {
1330
- return this.driver.query(
1331
- `SELECT
1332
- file_path,
1333
- COUNT(*) as total,
1334
- SUM(CASE WHEN operation='read' THEN 1 ELSE 0 END) as read,
1335
- SUM(CASE WHEN operation='created' THEN 1 ELSE 0 END) as created,
1336
- SUM(CASE WHEN operation='modified' THEN 1 ELSE 0 END) as modified,
1337
- SUM(CASE WHEN operation='deleted' THEN 1 ELSE 0 END) as deleted
1338
- FROM action_files
1339
- GROUP BY file_path
1340
- ORDER BY total DESC
1341
- LIMIT ?`,
1342
- [limit]
1343
- );
1344
- }
1345
- async getRecentFiles(limit) {
1346
- return this.driver.query(
1347
- `SELECT af.*, t.id as task_id, t.title as task_title, t.slug as task_slug,
1348
- a.agent, a.created_at as called_at
1349
- FROM action_files af
1350
- JOIN actions a ON af.action_id = a.id
1351
- JOIN tasks t ON a.task_id = t.id
1352
- ORDER BY a.created_at DESC
1353
- LIMIT ?`,
1354
- [limit]
1355
- );
1356
- }
1357
- async getAgentStats() {
1358
- const rows = await this.driver.query(
1359
- `SELECT
1360
- a.agent,
1361
- COUNT(*) as actions_total,
1362
- SUM(CASE WHEN a.status='completed' THEN 1 ELSE 0 END) as actions_done,
1363
- SUM(CASE WHEN a.status='blocked' THEN 1 ELSE 0 END) as actions_blocked,
1364
- COUNT(DISTINCT a.task_id) as tasks_worked,
1365
- COUNT(DISTINCT af.file_path) as files_touched
1366
- FROM actions a
1367
- LEFT JOIN action_files af ON af.action_id = a.id
1368
- GROUP BY a.agent
1369
- ORDER BY actions_total DESC`
1370
- );
1371
- return rows.sort((a, b) => {
1372
- const ai = AGENT_ORDER.indexOf(a.agent);
1373
- const bi = AGENT_ORDER.indexOf(b.agent);
1374
- if (ai === -1 && bi === -1) return 0;
1375
- if (ai === -1) return 1;
1376
- if (bi === -1) return -1;
1377
- return ai - bi;
1378
- });
1379
- }
1380
- async getTimeline(limit) {
1381
- return this.driver.query(
1382
- `SELECT a.*, t.title as task_title, t.slug as task_slug, t.status as task_status
1383
- FROM actions a
1384
- JOIN tasks t ON a.task_id = t.id
1385
- ORDER BY a.created_at DESC
1386
- LIMIT ?`,
1387
- [limit]
1388
- );
1389
- }
1390
- };
1391
-
1392
- // src/core/repositories/TaskRepository.ts
1393
- var TaskRepository = class {
1394
- constructor(driver) {
1395
- this.driver = driver;
1396
- }
1397
- driver;
1398
- async add(params) {
1399
- const now = (/* @__PURE__ */ new Date()).toISOString();
1400
- return this.driver.insert(
1401
- `INSERT INTO tasks (slug, title, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
1402
- [params.slug, params.title, params.description ?? null, params.status ?? "pending", now, now]
1403
- );
1404
- }
1405
- async addAcceptance(taskId, criteria) {
1406
- for (const criterion of criteria) {
1407
- await this.driver.exec(
1408
- `INSERT INTO task_acceptance (task_id, criterion) VALUES (?, ?)`,
1409
- [taskId, criterion]
1410
- );
1411
- }
1412
- }
1413
- async getAll(status, includeArchived = false) {
1414
- let sql = `SELECT * FROM tasks`;
1415
- const params = [];
1416
- const conditions = [];
1417
- if (!includeArchived) {
1418
- conditions.push(`archived_at IS NULL`);
1419
- }
1420
- if (status) {
1421
- conditions.push(`status = ?`);
1422
- params.push(status);
1423
- }
1424
- if (conditions.length > 0) {
1425
- sql += ` WHERE ${conditions.join(" AND ")}`;
1426
- }
1427
- sql += ` ORDER BY CASE status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, updated_at DESC`;
1428
- return this.driver.query(sql, params);
1429
- }
1430
- async getAllWithAcceptanceCounts(includeArchived = false) {
1431
- let sql = `
1432
- SELECT t.*,
1433
- COUNT(ta.id) as acceptance_total,
1434
- COALESCE(SUM(ta.met), 0) as acceptance_met
1435
- FROM tasks t
1436
- LEFT JOIN task_acceptance ta ON ta.task_id = t.id
1437
- `;
1438
- if (!includeArchived) {
1439
- sql += ` WHERE t.archived_at IS NULL`;
1440
- }
1441
- sql += ` GROUP BY t.id ORDER BY CASE t.status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, t.updated_at DESC`;
1442
- return this.driver.query(sql);
1443
- }
1444
- async getById(id) {
1445
- return this.driver.queryOne(`SELECT * FROM tasks WHERE id = ?`, [id]);
1446
- }
1447
- async getBySlug(slug) {
1448
- return this.driver.queryOne(`SELECT * FROM tasks WHERE slug = ?`, [slug]);
1449
- }
1450
- async getAcceptance(taskId) {
1451
- return this.driver.query(
1452
- `SELECT * FROM task_acceptance WHERE task_id = ?`,
1453
- [taskId]
1454
- );
1455
- }
1456
- async setStatus(id, status, extra) {
1457
- const now = (/* @__PURE__ */ new Date()).toISOString();
1458
- if (extra?.started_at) {
1459
- await this.driver.exec(
1460
- `UPDATE tasks SET status = ?, started_at = ?, updated_at = ? WHERE id = ?`,
1461
- [status, extra.started_at, now, id]
1462
- );
1463
- } else if (extra?.completed_at) {
1464
- await this.driver.exec(
1465
- `UPDATE tasks SET status = ?, completed_at = ?, updated_at = ? WHERE id = ?`,
1466
- [status, extra.completed_at, now, id]
1467
- );
1468
- } else {
1469
- await this.driver.exec(`UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?`, [status, now, id]);
1470
- }
1471
- }
1472
- async update(id, params) {
1473
- const sets = [];
1474
- const vals = [];
1475
- const now = (/* @__PURE__ */ new Date()).toISOString();
1476
- if (params.title !== void 0) {
1477
- sets.push("title = ?");
1478
- vals.push(params.title);
1479
- }
1480
- if (params.description !== void 0) {
1481
- sets.push("description = ?");
1482
- vals.push(params.description);
1483
- }
1484
- if (params.slug !== void 0) {
1485
- sets.push("slug = ?");
1486
- vals.push(params.slug);
1487
- }
1488
- if (sets.length === 0) return;
1489
- sets.push("updated_at = ?");
1490
- vals.push(now);
1491
- vals.push(id);
1492
- await this.driver.exec(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, vals);
1493
- }
1494
- async replaceAcceptance(taskId, criteria) {
1495
- await this.driver.exec(`DELETE FROM task_acceptance WHERE task_id = ?`, [taskId]);
1496
- for (const criterion of criteria) {
1497
- await this.driver.exec(
1498
- `INSERT INTO task_acceptance (task_id, criterion) VALUES (?, ?)`,
1499
- [taskId, criterion]
1500
- );
1501
- }
1502
- }
1503
- async archive(id) {
1504
- const now = (/* @__PURE__ */ new Date()).toISOString();
1505
- await this.driver.exec(`UPDATE tasks SET archived_at = ?, updated_at = ? WHERE id = ?`, [now, now, id]);
1506
- }
1507
- async unarchive(id) {
1508
- const now = (/* @__PURE__ */ new Date()).toISOString();
1509
- await this.driver.exec(`UPDATE tasks SET archived_at = NULL, updated_at = ? WHERE id = ?`, [now, id]);
1510
- }
1511
- async getArchived() {
1512
- return this.driver.query(
1513
- `SELECT * FROM tasks WHERE archived_at IS NOT NULL ORDER BY archived_at DESC`
1514
- );
1515
- }
1516
- async claim(id, agent, now) {
1517
- return this.driver.exec(
1518
- `UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'`,
1519
- [agent, now, now, id]
1520
- );
1521
- }
1522
- async markAcceptanceMet(criterionId) {
1523
- await this.driver.exec(`UPDATE task_acceptance SET met = 1 WHERE id = ?`, [criterionId]);
1524
- }
1525
- async getStatusSummary() {
1526
- return this.driver.query(
1527
- `SELECT status, COUNT(*) as total FROM tasks WHERE archived_at IS NULL GROUP BY status`
1528
- );
1529
- }
1530
- };
1531
-
1532
- // src/core/db.ts
1533
- var HarnessDB = class {
1534
- tasks;
1535
- actions;
1536
- stats;
1537
- driver;
1538
- config;
1539
- constructor(driver, config) {
1540
- this.driver = driver;
1541
- this.config = config;
1542
- this.tasks = new TaskRepository(driver);
1543
- this.actions = new ActionRepository(driver);
1544
- this.stats = new StatsRepository(driver);
1545
- }
1546
- // ─── Tasks (public facade — delegates to TaskRepository) ──────────────────
1547
- async addTask(params) {
1548
- const taskId = await this.tasks.add({
1549
- slug: params.slug,
1550
- title: params.title,
1551
- description: params.description
1552
- });
1553
- if (params.acceptance?.length) {
1554
- await this.tasks.addAcceptance(taskId, params.acceptance);
1555
- }
1556
- await this.regenerateCurrentMd();
1557
- return await this.tasks.getById(taskId);
1558
- }
1559
- async getTasks(status, includeArchived = false) {
1560
- return this.tasks.getAll(status, includeArchived);
1561
- }
1562
- async getTaskById(id) {
1563
- return this.tasks.getById(id);
1564
- }
1565
- async getTaskBySlug(slug) {
1566
- return this.tasks.getBySlug(slug);
1567
- }
1568
- async getTaskAcceptance(taskId) {
1569
- return this.tasks.getAcceptance(taskId);
1570
- }
1571
- async updateTaskStatus(idOrSlug, status) {
1572
- const now = (/* @__PURE__ */ new Date()).toISOString();
1573
- const task2 = typeof idOrSlug === "number" ? await this.tasks.getById(idOrSlug) : await this.tasks.getBySlug(idOrSlug);
1574
- if (!task2) throw new Error(`Task not found: ${idOrSlug}`);
1575
- if (status === "in_progress" && !task2.started_at) {
1576
- await this.tasks.setStatus(task2.id, status, { started_at: now });
1577
- } else if (status === "done") {
1578
- await this.tasks.setStatus(task2.id, status, { completed_at: now });
1579
- } else {
1580
- await this.tasks.setStatus(task2.id, status);
1581
- }
1582
- await this.regenerateCurrentMd();
1583
- return await this.tasks.getById(task2.id);
1584
- }
1585
- async claimTask(id, agent) {
1586
- const now = (/* @__PURE__ */ new Date()).toISOString();
1587
- return this.driver.transaction(async (tx) => {
1588
- const txTasks = new TaskRepository(tx);
1589
- const changed = await txTasks.claim(id, agent, now);
1590
- if (!changed) return null;
1591
- const task2 = await txTasks.getById(id);
1592
- if (!task2 || task2.status !== "in_progress" || task2.assigned_to !== agent) return null;
1593
- await this.regenerateCurrentMd();
1594
- return task2;
1595
- });
1596
- }
1597
- async markAcceptanceMet(criterionId) {
1598
- return this.tasks.markAcceptanceMet(criterionId);
1599
- }
1600
- async updateTask(id, params) {
1601
- await this.tasks.update(id, params);
1602
- await this.regenerateCurrentMd();
1603
- return await this.tasks.getById(id);
1604
- }
1605
- async updateTaskAcceptance(taskId, criteria) {
1606
- await this.tasks.replaceAcceptance(taskId, criteria);
1607
- await this.regenerateCurrentMd();
1608
- }
1609
- async archiveTask(id) {
1610
- await this.tasks.archive(id);
1611
- await this.regenerateCurrentMd();
1612
- return await this.tasks.getById(id);
1613
- }
1614
- async unarchiveTask(id) {
1615
- await this.tasks.unarchive(id);
1616
- await this.regenerateCurrentMd();
1617
- return await this.tasks.getById(id);
1618
- }
1619
- async getArchivedTasks() {
1620
- return this.tasks.getArchived();
1621
- }
1622
- async getStatusSummary() {
1623
- return this.tasks.getStatusSummary();
1624
- }
1625
- // ─── Actions (public facade — delegates to ActionRepository) ──────────────
1626
- async startAction(taskId, agent) {
1627
- const id = randomUUID();
1628
- const now = (/* @__PURE__ */ new Date()).toISOString();
1629
- await this.actions.create(id, taskId, agent, now);
1630
- await this.regenerateCurrentMd();
1631
- return await this.actions.getById(id);
1632
- }
1633
- async writeSection(actionId, sectionType, content) {
1634
- const now = (/* @__PURE__ */ new Date()).toISOString();
1635
- await this.actions.addSection(actionId, sectionType, content, now);
1636
- await this.regenerateCurrentMd();
1637
- }
1638
- async completeAction(actionId, summary) {
1639
- const now = (/* @__PURE__ */ new Date()).toISOString();
1640
- await this.actions.complete(actionId, summary, now);
1641
- await this.regenerateCurrentMd();
1642
- return await this.actions.getById(actionId);
1643
- }
1644
- async closeOrphanedActions(taskId) {
1645
- const now = (/* @__PURE__ */ new Date()).toISOString();
1646
- return this.actions.closeOrphaned(taskId, now);
1647
- }
1648
- async getAction(actionId) {
1649
- return this.actions.getById(actionId);
1650
- }
1651
- async getActionsForTask(taskId) {
1652
- return this.actions.getForTask(taskId);
1653
- }
1654
- async getActionSections(actionId) {
1655
- return this.actions.getSections(actionId);
1656
- }
1657
- async recordFile(actionId, filePath, operation, notes) {
1658
- return this.actions.addFile(actionId, filePath, operation, notes ?? null);
1659
- }
1660
- async recordTool(actionId, toolName, argsJson, resultSummary) {
1661
- const now = (/* @__PURE__ */ new Date()).toISOString();
1662
- return this.actions.addTool(actionId, toolName, argsJson ?? null, resultSummary ?? null, now);
1663
- }
1664
- async getFilesForTask(taskId) {
1665
- return this.actions.getFilesForTask(taskId);
1666
- }
1667
- async getTopTools(limit = 10) {
1668
- return this.actions.getTopTools(limit);
1669
- }
1670
- // ─── current.md fallback ──────────────────────────────────────────────────
1671
- async regenerateCurrentMd() {
1672
- if (!this.config.storage.markdownFallback.enabled) return;
1673
- const mdPath = resolve6(this.config.storage.markdownFallback.path);
1674
- mkdirSync7(dirname4(mdPath), { recursive: true });
1675
- const inProgress = await this.tasks.getAll("in_progress");
1676
- const now = (/* @__PURE__ */ new Date()).toISOString();
1677
- let md = `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
1678
- `;
1679
- md += `<!-- Last updated: ${now} -->
1680
-
1681
- `;
1682
- md += `# Current Session
1683
-
1684
- `;
1685
- if (inProgress.length === 0) {
1686
- md += `## No tasks in progress
1687
-
1688
- `;
1689
- const pending = await this.tasks.getAll("pending");
1690
- if (pending.length > 0) {
1691
- md += `### Next pending tasks
1692
- `;
1693
- for (const t of pending.slice(0, 5)) {
1694
- md += `- **#${t.id}** ${t.title} (\`${t.slug}\`)
1695
- `;
1696
- }
1697
- }
1698
- } else {
1699
- for (const task2 of inProgress) {
1700
- md += `## Active Task
1701
- `;
1702
- md += `- **ID:** ${task2.id}
1703
- `;
1704
- md += `- **Slug:** ${task2.slug}
1705
- `;
1706
- md += `- **Status:** ${task2.status}
1707
- `;
1708
- md += `- **Started:** ${task2.started_at ?? "unknown"}
1709
-
1710
- `;
1711
- const taskActions = await this.actions.getForTask(task2.id);
1712
- if (taskActions.length > 0) {
1713
- md += `## Actions this session
1714
- `;
1715
- md += `| Agent | Status | Summary | Started |
1716
- `;
1717
- md += `|----------|-------------|----------------------------------|-------------|
1718
- `;
1719
- for (const a of taskActions) {
1720
- const started = a.created_at.slice(11, 16);
1721
- const summary = (a.summary ?? "").slice(0, 34).padEnd(34);
1722
- md += `| ${a.agent.padEnd(8)} | ${a.status.padEnd(11)} | ${summary} | ${started} |
1723
- `;
1724
- }
1725
- md += `
1726
- `;
1727
- }
1728
- const acceptance = await this.tasks.getAcceptance(task2.id);
1729
- if (acceptance.length > 0) {
1730
- md += `## Acceptance Criteria
1731
- `;
1732
- for (const a of acceptance) {
1733
- md += `- [${a.met ? "x" : " "}] ${a.criterion}
1734
- `;
1735
- }
1736
- md += `
1737
- `;
1738
- }
1739
- }
1740
- }
1741
- writeFileSync7(mdPath, md, "utf8");
1742
- }
1743
- // ─── Raw query escape hatch ───────────────────────────────────────────────
1744
- async queryRaw(sql, ...params) {
1745
- return this.driver.query(sql, params);
1746
- }
1747
- // ─── Export helpers ───────────────────────────────────────────────────────
1748
- async exportJson() {
1749
- return {
1750
- tasks: await this.tasks.getAll(void 0, true),
1751
- actions: await this.actions.getAll(),
1752
- sections: await this.actions.getAllSections()
1753
- };
1754
- }
1755
- async reconnect() {
1756
- await this.driver.reconnect();
1757
- }
1758
- async close() {
1759
- await this.driver.close();
1760
- }
1761
- // ─── feature_list.json sync ───────────────────────────────────────────────
1762
- async syncFromFeatureList(seeds) {
1763
- let added = 0;
1764
- let skipped = 0;
1765
- for (const t of seeds) {
1766
- if (await this.tasks.getBySlug(t.slug)) {
1767
- skipped++;
1768
- continue;
1769
- }
1770
- await this.addTask(t);
1771
- added++;
1772
- }
1773
- return { added, skipped };
1774
- }
1775
- async writeFeatureList(cwd2) {
1776
- const allTasks = await this.tasks.getAll(void 0, true);
1777
- const list = await Promise.all(
1778
- allTasks.map(async (t) => ({
1779
- slug: t.slug,
1780
- title: t.title,
1781
- description: t.description ?? void 0,
1782
- acceptance: (await this.tasks.getAcceptance(t.id)).map((a) => a.criterion),
1783
- status: t.status
1784
- }))
1785
- );
1786
- const path = join8(resolve6(cwd2), this.config.storage.dir, "feature_list.json");
1787
- mkdirSync7(dirname4(path), { recursive: true });
1788
- writeFileSync7(path, JSON.stringify(list, null, 2) + "\n", "utf8");
1789
- }
1790
- };
1791
- async function openDB(config, cwd2) {
1792
- const dbConfig = config.database;
1793
- let driver;
1794
- if (dbConfig.type === "postgres") {
1795
- const { PostgresDriver } = await import("./postgres-IOQE32DM.js");
1796
- driver = new PostgresDriver(dbConfig);
1797
- } else if (dbConfig.type === "mysql") {
1798
- const { MySQLDriver } = await import("./mysql-THKQOXIS.js");
1799
- driver = new MySQLDriver(dbConfig);
1800
- } else {
1801
- const { SQLiteDriver } = await import("./sqlite-KWYK4IJW.js");
1802
- if (dbConfig.type !== "sqlite") {
1803
- throw new Error("Invalid database type");
1804
- }
1805
- driver = new SQLiteDriver(resolve6(cwd2, dbConfig.path));
1806
- }
1807
- await driver.ensureSchema();
1808
- return new HarnessDB(driver, config);
1809
- }
1810
-
1811
1316
  // src/commands/dashboard.ts
1812
- var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
1317
+ var __dirname3 = dirname4(fileURLToPath3(import.meta.url));
1813
1318
  async function runDashboard(cwd2, opts) {
1814
1319
  const config = await loadConfig(cwd2);
1815
1320
  const db = await openDB(config, cwd2);
1816
- const dbPath = config.database.type === "sqlite" ? resolve7(cwd2, config.database.path) : null;
1321
+ const dbPath = config.database.type === "sqlite" ? resolve6(cwd2, config.database.path) : null;
1817
1322
  const staticPath = join9(__dirname3, "dashboard-dist");
1818
1323
  const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
1819
1324
  console.log(pc2.green(`\u2713`) + ` Dashboard running at ${pc2.bold(pc2.cyan(url))}`);
@@ -1834,26 +1339,37 @@ async function runDashboard(cwd2, opts) {
1834
1339
  import pc3 from "picocolors";
1835
1340
 
1836
1341
  // src/core/doctor.ts
1837
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1838
- import { dirname as dirname7, join as join11 } from "path";
1342
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1343
+ import { homedir } from "os";
1344
+ import { dirname as dirname6, join as join11 } from "path";
1839
1345
  import { fileURLToPath as fileURLToPath5 } from "url";
1840
1346
 
1841
1347
  // src/core/package-data.ts
1348
+ import { existsSync as existsSync8 } from "fs";
1842
1349
  import { createRequire } from "module";
1843
- import { dirname as dirname6, join as join10 } from "path";
1350
+ import { dirname as dirname5, join as join10 } from "path";
1844
1351
  import { fileURLToPath as fileURLToPath4 } from "url";
1845
1352
  var require2 = createRequire(import.meta.url);
1846
- var pkgPath = join10(dirname6(fileURLToPath4(import.meta.url)), "..", "package.json");
1353
+ var here = dirname5(fileURLToPath4(import.meta.url));
1354
+ var candidates = [join10(here, "..", "..", "package.json"), join10(here, "..", "package.json")];
1355
+ var pkgPath = candidates.find((p8) => existsSync8(p8)) ?? candidates[0];
1847
1356
  var pkg = require2(pkgPath);
1848
1357
 
1849
1358
  // src/core/doctor.ts
1850
1359
  var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
1851
1360
  var TIMEOUT_MS = 2e3;
1361
+ var LIB_VERSION_CACHE_TTL_MS = 5 * 60 * 1e3;
1852
1362
  var AGENT_NAMES = ["lead", "explorer", "consultant", "builder", "reviewer"];
1853
- var SKILL_NAMES = ["ahk-ask", "ahk-consultant", "ahk-triage"];
1854
- var __dirname4 = dirname7(fileURLToPath5(import.meta.url));
1363
+ var SKILL_NAMES = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review"];
1364
+ var __dirname4 = dirname6(fileURLToPath5(import.meta.url));
1365
+ var libVersionCache = null;
1855
1366
  async function checkLibVersion() {
1856
1367
  const current = pkg.version;
1368
+ if (libVersionCache && Date.now() - libVersionCache.fetchedAt < LIB_VERSION_CACHE_TTL_MS) {
1369
+ if (libVersionCache.status.current === current) {
1370
+ return libVersionCache.status;
1371
+ }
1372
+ }
1857
1373
  try {
1858
1374
  const controller = new AbortController();
1859
1375
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
@@ -1862,9 +1378,13 @@ async function checkLibVersion() {
1862
1378
  const data = await res.json();
1863
1379
  const latest = data.version;
1864
1380
  const outdated = isNewer(latest, current);
1865
- return { current, latest, outdated };
1381
+ const status = { current, latest, outdated };
1382
+ libVersionCache = { status, fetchedAt: Date.now() };
1383
+ return status;
1866
1384
  } catch {
1867
- return { current, latest: null, outdated: false };
1385
+ const status = { current, latest: null, outdated: false };
1386
+ libVersionCache = { status, fetchedAt: Date.now() };
1387
+ return status;
1868
1388
  }
1869
1389
  }
1870
1390
  function isNewer(latest, current) {
@@ -1888,7 +1408,7 @@ function getProviderAgentInfo(provider) {
1888
1408
  }
1889
1409
  }
1890
1410
  function generateExpectedAgentContent(agentName, provider, vars) {
1891
- const { projectName, allowedPaths, writablePaths } = vars;
1411
+ const { projectName, allowedPaths, writablePaths, model } = vars;
1892
1412
  if (provider === "claude-code") {
1893
1413
  const templateFns = {
1894
1414
  lead: () => agentLead({ projectName }),
@@ -1897,7 +1417,7 @@ function generateExpectedAgentContent(agentName, provider, vars) {
1897
1417
  builder: () => agentBuilder({ projectName, writablePaths }),
1898
1418
  reviewer: () => agentReviewer({ projectName })
1899
1419
  };
1900
- return translateFrontmatterForClaudeCode(templateFns[agentName](), agentName);
1420
+ return translateFrontmatterForClaudeCode(templateFns[agentName](), agentName, model);
1901
1421
  }
1902
1422
  if (provider === "opencode") {
1903
1423
  const templateFns = {
@@ -1910,27 +1430,27 @@ function generateExpectedAgentContent(agentName, provider, vars) {
1910
1430
  return translateFrontmatterForOpenCode(templateFns[agentName]());
1911
1431
  }
1912
1432
  const tomlFns = {
1913
- lead: () => agentLeadToml({ projectName }),
1914
- explorer: () => agentExplorerToml({ projectName, allowedPaths }),
1915
- consultant: () => agentConsultantToml({ projectName }),
1916
- builder: () => agentBuilderToml({ projectName, writablePaths }),
1917
- reviewer: () => agentReviewerToml({ projectName })
1433
+ lead: () => agentLeadToml({ projectName, model }),
1434
+ explorer: () => agentExplorerToml({ projectName, allowedPaths, model }),
1435
+ consultant: () => agentConsultantToml({ projectName, model }),
1436
+ builder: () => agentBuilderToml({ projectName, writablePaths, model }),
1437
+ reviewer: () => agentReviewerToml({ projectName, model })
1918
1438
  };
1919
1439
  return tomlFns[agentName]();
1920
1440
  }
1921
- function checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths) {
1922
- const { agentsDir, ext } = getProviderAgentInfo(provider);
1441
+ function checkAgentFilesAtRoot(agentsRoot, ext, provider, projectName, allowedPaths, writablePaths, models) {
1923
1442
  return AGENT_NAMES.map((name) => {
1924
- const filePath = join11(cwd2, agentsDir, `${name}${ext}`);
1925
- if (!existsSync7(filePath)) {
1443
+ const filePath = join11(agentsRoot, `${name}${ext}`);
1444
+ if (!existsSync9(filePath)) {
1926
1445
  return { name, status: "missing" };
1927
1446
  }
1928
1447
  try {
1929
- const live = readFileSync6(filePath, "utf8");
1448
+ const live = readFileSync7(filePath, "utf8");
1930
1449
  const expected = generateExpectedAgentContent(name, provider, {
1931
1450
  projectName,
1932
1451
  allowedPaths,
1933
- writablePaths
1452
+ writablePaths,
1453
+ model: models[name]
1934
1454
  });
1935
1455
  return { name, status: live === expected ? "ok" : "outdated" };
1936
1456
  } catch {
@@ -1938,6 +1458,18 @@ function checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePath
1938
1458
  }
1939
1459
  });
1940
1460
  }
1461
+ function checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths, models) {
1462
+ const { agentsDir, ext } = getProviderAgentInfo(provider);
1463
+ return checkAgentFilesAtRoot(
1464
+ join11(cwd2, agentsDir),
1465
+ ext,
1466
+ provider,
1467
+ projectName,
1468
+ allowedPaths,
1469
+ writablePaths,
1470
+ models
1471
+ );
1472
+ }
1941
1473
  function getProviderSkillsDir(provider) {
1942
1474
  switch (provider) {
1943
1475
  case "claude-code":
@@ -1950,24 +1482,76 @@ function getProviderSkillsDir(provider) {
1950
1482
  return ".claude/skills";
1951
1483
  }
1952
1484
  }
1953
- function checkSkills(cwd2, provider) {
1954
- const skillsDir = getProviderSkillsDir(provider);
1485
+ function checkSkillsAtRoot(skillsRoot) {
1955
1486
  const skillSourceBase = join11(__dirname4, "skills");
1956
1487
  return SKILL_NAMES.map((name) => {
1957
- const livePath = join11(cwd2, skillsDir, name, "SKILL.md");
1488
+ const livePath = join11(skillsRoot, name, "SKILL.md");
1958
1489
  const sourcePath = join11(skillSourceBase, name, "SKILL.md");
1959
- if (!existsSync7(livePath)) {
1490
+ if (!existsSync9(livePath)) {
1960
1491
  return { name, status: "missing" };
1961
1492
  }
1962
1493
  try {
1963
- const live = readFileSync6(livePath, "utf8");
1964
- const source = readFileSync6(sourcePath, "utf8");
1494
+ const live = readFileSync7(livePath, "utf8");
1495
+ const source = readFileSync7(sourcePath, "utf8");
1965
1496
  return { name, status: live === source ? "ok" : "outdated" };
1966
1497
  } catch {
1967
1498
  return { name, status: "outdated" };
1968
1499
  }
1969
1500
  });
1970
1501
  }
1502
+ function checkSkills(cwd2, provider) {
1503
+ const skillsDir = getProviderSkillsDir(provider);
1504
+ return checkSkillsAtRoot(join11(cwd2, skillsDir));
1505
+ }
1506
+ function getGlobalProviderAgentDir(provider, homeDir) {
1507
+ switch (provider) {
1508
+ case "claude-code":
1509
+ return { agentsDir: join11(homeDir, ".claude", "agents"), ext: ".md" };
1510
+ case "opencode":
1511
+ return { agentsDir: join11(homeDir, ".config", "opencode", "agents"), ext: ".md" };
1512
+ case "codex-cli":
1513
+ return { agentsDir: join11(homeDir, ".codex", "agents"), ext: ".toml" };
1514
+ default:
1515
+ return { agentsDir: join11(homeDir, ".claude", "agents"), ext: ".md" };
1516
+ }
1517
+ }
1518
+ function getGlobalProviderSkillsDir(provider, homeDir) {
1519
+ switch (provider) {
1520
+ case "claude-code":
1521
+ return join11(homeDir, ".claude", "skills");
1522
+ case "opencode":
1523
+ return join11(homeDir, ".config", "opencode", "skills");
1524
+ case "codex-cli":
1525
+ return join11(homeDir, ".agents", "skills");
1526
+ default:
1527
+ return join11(homeDir, ".claude", "skills");
1528
+ }
1529
+ }
1530
+ async function getGlobalDoctorStatus(provider, config, homeDir = homedir()) {
1531
+ const projectName = config.project.name;
1532
+ const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1533
+ const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1534
+ const models = {
1535
+ lead: config.agents.lead.model,
1536
+ explorer: config.agents.explorer.model,
1537
+ consultant: config.agents.consultant?.model,
1538
+ builder: config.agents.builder.model,
1539
+ reviewer: config.agents.reviewer.model
1540
+ };
1541
+ const { agentsDir, ext } = getGlobalProviderAgentDir(provider, homeDir);
1542
+ const agents = checkAgentFilesAtRoot(
1543
+ agentsDir,
1544
+ ext,
1545
+ provider,
1546
+ projectName,
1547
+ allowedPaths,
1548
+ writablePaths,
1549
+ models
1550
+ );
1551
+ const skillsDir = getGlobalProviderSkillsDir(provider, homeDir);
1552
+ const skills = checkSkillsAtRoot(skillsDir);
1553
+ return { agents, skills };
1554
+ }
1971
1555
  async function getDoctorStatus(cwd2) {
1972
1556
  const lib = await checkLibVersion();
1973
1557
  let config;
@@ -1984,7 +1568,14 @@ async function getDoctorStatus(cwd2) {
1984
1568
  const projectName = config.project.name;
1985
1569
  const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1986
1570
  const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1987
- const agents = checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths);
1571
+ const models = {
1572
+ lead: config.agents.lead.model,
1573
+ explorer: config.agents.explorer.model,
1574
+ consultant: config.agents.consultant?.model,
1575
+ builder: config.agents.builder.model,
1576
+ reviewer: config.agents.reviewer.model
1577
+ };
1578
+ const agents = checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths, models);
1988
1579
  const skills = checkSkills(cwd2, provider);
1989
1580
  return { lib, agents, skills };
1990
1581
  }
@@ -2074,7 +1665,7 @@ async function runDoctor(cwd2) {
2074
1665
  }
2075
1666
 
2076
1667
  // src/commands/export.ts
2077
- import { writeFileSync as writeFileSync8 } from "fs";
1668
+ import { writeFileSync as writeFileSync7 } from "fs";
2078
1669
  import pc4 from "picocolors";
2079
1670
  async function runExport(cwd2, opts) {
2080
1671
  if (!opts.sql && !opts.json) {
@@ -2088,7 +1679,7 @@ async function runExport(cwd2, opts) {
2088
1679
  const data = await db.exportJson();
2089
1680
  const out = JSON.stringify(data, null, 2) + "\n";
2090
1681
  if (opts.output) {
2091
- writeFileSync8(opts.output, out, "utf8");
1682
+ writeFileSync7(opts.output, out, "utf8");
2092
1683
  console.log(pc4.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
2093
1684
  } else {
2094
1685
  process.stdout.write(out);
@@ -2105,8 +1696,8 @@ async function runExport(cwd2, opts) {
2105
1696
 
2106
1697
  // src/commands/health.ts
2107
1698
  import { spawnSync } from "child_process";
2108
- import { existsSync as existsSync8 } from "fs";
2109
- import { join as join12, resolve as resolve8 } from "path";
1699
+ import { existsSync as existsSync10 } from "fs";
1700
+ import { join as join12, resolve as resolve7 } from "path";
2110
1701
  import pc5 from "picocolors";
2111
1702
  function checkLine(label, ok3, message, indent = 0) {
2112
1703
  const prefix = label ? pc5.cyan(`[${label}] `) : " ".repeat(indent);
@@ -2124,8 +1715,8 @@ async function runHealth(cwd2) {
2124
1715
  let allOk = true;
2125
1716
  let dbOk;
2126
1717
  if (config.database.type === "sqlite") {
2127
- const dbPath = resolve8(cwd2, config.database.path);
2128
- dbOk = existsSync8(dbPath);
1718
+ const dbPath = resolve7(cwd2, config.database.path);
1719
+ dbOk = existsSync10(dbPath);
2129
1720
  checkLine("checking DB", dbOk, `${config.database.path} reachable`);
2130
1721
  } else {
2131
1722
  dbOk = true;
@@ -2139,7 +1730,7 @@ async function runHealth(cwd2) {
2139
1730
  for (let i = 0; i < agentNames.length; i++) {
2140
1731
  const name = agentNames[i];
2141
1732
  const agentPath = join12(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
2142
- const ok3 = existsSync8(agentPath);
1733
+ const ok3 = existsSync10(agentPath);
2143
1734
  checkLine(
2144
1735
  i === 0 ? "checking agents" : null,
2145
1736
  ok3,
@@ -2150,8 +1741,8 @@ async function runHealth(cwd2) {
2150
1741
  }
2151
1742
  if (config.tools.mcp.enabled) {
2152
1743
  const mcpFile = providerFiles.mcpFile;
2153
- const mcpPath = resolve8(cwd2, mcpFile);
2154
- const mcpOk = existsSync8(mcpPath);
1744
+ const mcpPath = resolve7(cwd2, mcpFile);
1745
+ const mcpOk = existsSync10(mcpPath);
2155
1746
  checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
2156
1747
  if (!mcpOk) allOk = false;
2157
1748
  }
@@ -2160,8 +1751,8 @@ async function runHealth(cwd2) {
2160
1751
  console.error(pc5.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
2161
1752
  process.exit(1);
2162
1753
  }
2163
- const scriptPath = resolve8(cwd2, config.health.scriptPath);
2164
- if (!existsSync8(scriptPath)) {
1754
+ const scriptPath = resolve7(cwd2, config.health.scriptPath);
1755
+ if (!existsSync10(scriptPath)) {
2165
1756
  console.error(pc5.red(`\u2717 health.sh not found: ${scriptPath}`));
2166
1757
  console.error(" Run ahk init first.");
2167
1758
  process.exit(1);
@@ -2197,11 +1788,56 @@ function getProviderHealthFiles(provider) {
2197
1788
  }
2198
1789
 
2199
1790
  // src/commands/init.ts
2200
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
1791
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
2201
1792
  import { join as join14 } from "path";
2202
1793
  import * as p3 from "@clack/prompts";
2203
1794
  import pc7 from "picocolors";
2204
1795
 
1796
+ // src/core/materializer/global-sync.ts
1797
+ import { homedir as homedir2 } from "os";
1798
+ async function syncGlobalAgentsAndSkills(config, provider, homeDir = homedir2()) {
1799
+ const status = await getGlobalDoctorStatus(provider, config, homeDir);
1800
+ const missingAgents = status.agents.filter((a) => a.status === "missing");
1801
+ const missingSkills = status.skills.filter((s) => s.status === "missing");
1802
+ if (missingAgents.length === 0 && missingSkills.length === 0) {
1803
+ return { alreadySynced: true, createdAgents: [], createdSkills: [] };
1804
+ }
1805
+ const projectName = config.project.name;
1806
+ const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1807
+ const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1808
+ const models = {
1809
+ lead: config.agents.lead.model,
1810
+ explorer: config.agents.explorer.model,
1811
+ consultant: config.agents.consultant?.model,
1812
+ builder: config.agents.builder.model,
1813
+ reviewer: config.agents.reviewer.model
1814
+ };
1815
+ const createdAgents = [];
1816
+ if (missingAgents.length > 0) {
1817
+ const { agentsDir, ext } = getGlobalProviderAgentDir(provider, homeDir);
1818
+ for (const agent of missingAgents) {
1819
+ const name = agent.name;
1820
+ const content = generateExpectedAgentContent(name, provider, {
1821
+ projectName,
1822
+ allowedPaths,
1823
+ writablePaths,
1824
+ model: models[name]
1825
+ });
1826
+ writeAgentFile(agentsDir, `${name}${ext}`, content);
1827
+ createdAgents.push(name);
1828
+ }
1829
+ }
1830
+ const createdSkills = [];
1831
+ if (missingSkills.length > 0) {
1832
+ const skillsDir = getGlobalProviderSkillsDir(provider, homeDir);
1833
+ for (const skill of missingSkills) {
1834
+ writeSkill(skillsDir, skill.name);
1835
+ createdSkills.push(skill.name);
1836
+ }
1837
+ }
1838
+ return { alreadySynced: false, createdAgents, createdSkills };
1839
+ }
1840
+
2205
1841
  // src/schema/init.ts
2206
1842
  import * as v from "valibot";
2207
1843
  var initNameSchema = v.pipe(
@@ -2253,14 +1889,15 @@ var cliFormWithRetry = async (formFn, schema) => {
2253
1889
  };
2254
1890
 
2255
1891
  // src/commands/init-helpers.ts
2256
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1892
+ import { randomUUID } from "crypto";
1893
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2257
1894
  import { join as join13 } from "path";
2258
1895
  import pc6 from "picocolors";
2259
1896
  function readProjectNameFromPackageJson(cwd2) {
2260
1897
  try {
2261
1898
  const pkgPath2 = join13(cwd2, "package.json");
2262
- if (!existsSync9(pkgPath2)) return null;
2263
- const content = readFileSync7(pkgPath2, "utf8");
1899
+ if (!existsSync11(pkgPath2)) return null;
1900
+ const content = readFileSync8(pkgPath2, "utf8");
2264
1901
  const pkg2 = JSON.parse(content);
2265
1902
  const name = pkg2?.name;
2266
1903
  if (typeof name === "string" && name.trim()) return name.trim();
@@ -2271,16 +1908,17 @@ function readProjectNameFromPackageJson(cwd2) {
2271
1908
  }
2272
1909
  function detectConfigExtension(cwd2) {
2273
1910
  try {
2274
- if (existsSync9(join13(cwd2, "tsconfig.json"))) return "ts";
1911
+ if (existsSync11(join13(cwd2, "tsconfig.json"))) return "ts";
2275
1912
  const pkgPath2 = join13(cwd2, "package.json");
2276
- if (!existsSync9(pkgPath2)) return "mjs";
2277
- const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
1913
+ if (!existsSync11(pkgPath2)) return "mjs";
1914
+ const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
2278
1915
  if (pkg2?.type === "module") return "mjs";
2279
1916
  } catch {
2280
1917
  }
2281
1918
  return "mjs";
2282
1919
  }
2283
1920
  function applyConfigDefaults(params) {
1921
+ const models = params.models ?? {};
2284
1922
  return {
2285
1923
  provider: params.provider,
2286
1924
  project: {
@@ -2290,10 +1928,19 @@ function applyConfigDefaults(params) {
2290
1928
  agentsMd: "./AGENTS.md"
2291
1929
  },
2292
1930
  agents: {
2293
- lead: { instructionsPath: null },
2294
- explorer: { instructionsPath: null, allowedPaths: [params.docsPath, "./src"] },
2295
- builder: { instructionsPath: null, writablePaths: ["./src", "./tests"] },
2296
- reviewer: { instructionsPath: null },
1931
+ lead: { instructionsPath: null, ...models.lead && { model: models.lead } },
1932
+ explorer: {
1933
+ instructionsPath: null,
1934
+ allowedPaths: [params.docsPath, "./src"],
1935
+ ...models.explorer && { model: models.explorer }
1936
+ },
1937
+ builder: {
1938
+ instructionsPath: null,
1939
+ writablePaths: ["./src", "./tests"],
1940
+ ...models.builder && { model: models.builder }
1941
+ },
1942
+ reviewer: { instructionsPath: null, ...models.reviewer && { model: models.reviewer } },
1943
+ ...models.consultant && { consultant: { instructionsPath: null, model: models.consultant } },
2297
1944
  custom: []
2298
1945
  },
2299
1946
  database: { type: "sqlite", path: ".harness/harness.db" },
@@ -2307,7 +1954,9 @@ function applyConfigDefaults(params) {
2307
1954
  blockers: true,
2308
1955
  nextSteps: false
2309
1956
  },
2310
- markdownFallback: { enabled: true, path: ".harness/current.md" }
1957
+ markdownFallback: { enabled: true, path: ".harness/current.md" },
1958
+ scope: params.scope ?? "local",
1959
+ projectId: params.projectId ?? randomUUID()
2311
1960
  },
2312
1961
  health: {
2313
1962
  scriptPath: "./health.sh",
@@ -2420,6 +2069,61 @@ async function runInit(cwd2, flags) {
2420
2069
  }
2421
2070
  provider = val;
2422
2071
  }
2072
+ const AGENT_LABELS = [
2073
+ { key: "lead", label: "Lead" },
2074
+ { key: "explorer", label: "Explorer" },
2075
+ { key: "consultant", label: "Consultant" },
2076
+ { key: "builder", label: "Builder" },
2077
+ { key: "reviewer", label: "Reviewer" }
2078
+ ];
2079
+ const modelOverrides = {};
2080
+ if (provider === "claude-code" || provider === "codex-cli") {
2081
+ const wantsModelCustomization = await p3.confirm({
2082
+ message: "\xBFPersonalizar el modelo por agente?",
2083
+ initialValue: false
2084
+ });
2085
+ if (p3.isCancel(wantsModelCustomization)) {
2086
+ p3.cancel("Cancelled.");
2087
+ process.exit(0);
2088
+ }
2089
+ if (wantsModelCustomization) {
2090
+ if (provider === "claude-code") {
2091
+ for (const agent of AGENT_LABELS) {
2092
+ const val = await p3.select({
2093
+ message: `Modelo para ${agent.label}`,
2094
+ options: [
2095
+ { value: "inherit", label: "inherit (default)" },
2096
+ { value: "haiku", label: "haiku" },
2097
+ { value: "sonnet", label: "sonnet" },
2098
+ { value: "opus", label: "opus" },
2099
+ { value: "fable", label: "fable" }
2100
+ ],
2101
+ initialValue: "inherit"
2102
+ });
2103
+ if (p3.isCancel(val)) {
2104
+ p3.cancel("Cancelled.");
2105
+ process.exit(0);
2106
+ }
2107
+ modelOverrides[agent.key] = val;
2108
+ }
2109
+ } else {
2110
+ for (const agent of AGENT_LABELS) {
2111
+ const val = await p3.text({
2112
+ message: `Modelo para ${agent.label} (Codex no valida este valor)`,
2113
+ placeholder: "ej. gpt-5 (vac\xEDo o <3 caracteres = sin override)"
2114
+ });
2115
+ if (p3.isCancel(val)) {
2116
+ p3.cancel("Cancelled.");
2117
+ process.exit(0);
2118
+ }
2119
+ const trimmed = val.trim();
2120
+ if (trimmed.length >= 3) {
2121
+ modelOverrides[agent.key] = trimmed;
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2423
2127
  let docsPath;
2424
2128
  if (flags.docs) {
2425
2129
  docsPath = flags.docs;
@@ -2436,6 +2140,24 @@ async function runInit(cwd2, flags) {
2436
2140
  return val;
2437
2141
  }, initDocsSchema);
2438
2142
  }
2143
+ let storageScope;
2144
+ if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
2145
+ storageScope = flags.storageScope;
2146
+ } else {
2147
+ const val = await p3.select({
2148
+ message: "Storage scope",
2149
+ options: [
2150
+ { value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
2151
+ { value: "global", label: "Global \u2014 DB lives under ~/.harness/dbs/<projectId>/, outside the project" }
2152
+ ],
2153
+ initialValue: "local"
2154
+ });
2155
+ if (p3.isCancel(val)) {
2156
+ p3.cancel("Cancelled.");
2157
+ process.exit(0);
2158
+ }
2159
+ storageScope = val;
2160
+ }
2439
2161
  let tasksAdapter;
2440
2162
  if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
2441
2163
  tasksAdapter = flags.tasks;
@@ -2490,10 +2212,19 @@ async function runInit(cwd2, flags) {
2490
2212
  firstTask = { title: taskTitle, description: taskDesc, acceptance };
2491
2213
  }
2492
2214
  let configExt = "ts";
2215
+ let globalSyncResult = null;
2493
2216
  const spinner6 = p3.spinner();
2494
2217
  spinner6.start("Scaffolding...");
2495
2218
  try {
2496
- const config = applyConfigDefaults({ name, description, provider, docsPath, tasksAdapter });
2219
+ const config = applyConfigDefaults({
2220
+ name,
2221
+ description,
2222
+ provider,
2223
+ docsPath,
2224
+ tasksAdapter,
2225
+ models: modelOverrides,
2226
+ scope: storageScope
2227
+ });
2497
2228
  const materializer = getMaterializer(provider);
2498
2229
  const installDir = cwd2;
2499
2230
  configExt = detectConfigExtension(cwd2);
@@ -2505,12 +2236,19 @@ async function runInit(cwd2, flags) {
2505
2236
  provider,
2506
2237
  docsPath,
2507
2238
  tasksAdapter,
2508
- port: config.tools.mcp.port
2239
+ port: config.tools.mcp.port,
2240
+ models: modelOverrides,
2241
+ scope: config.storage.scope,
2242
+ projectId: config.storage.projectId
2509
2243
  });
2510
- writeFileSync9(join14(installDir, configFileName), configContent, "utf8");
2511
- mkdirSync8(join14(installDir, config.storage.dir), { recursive: true });
2244
+ writeFileSync8(join14(installDir, configFileName), configContent, "utf8");
2245
+ mkdirSync7(join14(installDir, config.storage.dir), { recursive: true });
2512
2246
  const db = await openDB(config, installDir);
2247
+ await db.writeStorageState(installDir);
2513
2248
  await materializer.scaffold(config, { cwd: installDir, firstTask });
2249
+ if (config.storage.scope === "global") {
2250
+ globalSyncResult = await syncGlobalAgentsAndSkills(config, provider);
2251
+ }
2514
2252
  if (firstTask) {
2515
2253
  const slug = slugify(firstTask.title);
2516
2254
  await db.addTask({
@@ -2534,14 +2272,28 @@ async function runInit(cwd2, flags) {
2534
2272
  console.log(pc7.green(`\u2713 agent-harness-kit.config.${configExt}`));
2535
2273
  console.log(pc7.green("\u2713 AGENTS.md"));
2536
2274
  console.log(pc7.green("\u2713 health.sh"));
2537
- console.log(pc7.green("\u2713 .harness/harness.db"));
2538
- console.log(pc7.green("\u2713 .harness/current.md"));
2275
+ console.log(pc7.green(storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/harness.db" : "\u2713 .harness/harness.db"));
2276
+ console.log(pc7.green(storageScope === "global" ? "\u2713 ~/.harness/dbs/<projectId>/current.md" : "\u2713 .harness/current.md"));
2277
+ console.log(pc7.green("\u2713 .harness/storage-state.json"));
2539
2278
  console.log(pc7.green(`\u2713 ${agentsDir}lead.md`));
2540
2279
  console.log(pc7.green(`\u2713 ${agentsDir}explorer.md`));
2541
2280
  console.log(pc7.green(`\u2713 ${agentsDir}builder.md`));
2542
2281
  console.log(pc7.green(`\u2713 ${agentsDir}reviewer.md`));
2543
2282
  console.log(pc7.green(`\u2713 ${mcpFile}`));
2544
2283
  console.log(pc7.green("\u2713 .gitignore entries added"));
2284
+ if (globalSyncResult) {
2285
+ console.log("");
2286
+ if (globalSyncResult.alreadySynced) {
2287
+ console.log(pc7.dim("\u2713 Global agents/skills already synced \u2014 skipped"));
2288
+ } else {
2289
+ for (const name2 of globalSyncResult.createdAgents) {
2290
+ console.log(pc7.green(`\u2713 ~global agent added: ${name2}`));
2291
+ }
2292
+ for (const name2 of globalSyncResult.createdSkills) {
2293
+ console.log(pc7.green(`\u2713 ~global skill added: ${name2}`));
2294
+ }
2295
+ }
2296
+ }
2545
2297
  console.log("");
2546
2298
  console.log(pc7.cyan("\u2192") + ` Edit ${pc7.cyan("health.sh")} with your project checks`);
2547
2299
  console.log(pc7.cyan("\u2192") + ` ${pc7.cyan("ahk task add")} to queue work for agents`);
@@ -2598,17 +2350,251 @@ async function runMigrate(cwd2, opts) {
2598
2350
  }
2599
2351
  }
2600
2352
 
2353
+ // src/commands/migrate-storage.ts
2354
+ import { copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
2355
+ import { homedir as homedir3 } from "os";
2356
+ import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
2357
+ import pc9 from "picocolors";
2358
+ function log5(msg) {
2359
+ console.log(msg);
2360
+ }
2361
+ function fail(msg) {
2362
+ throw new Error(msg);
2363
+ }
2364
+ function currentMdPathForScope(scope, config, cwd2, homeDir) {
2365
+ return scope === "global" ? join15(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve8(cwd2, config.storage.markdownFallback.path);
2366
+ }
2367
+ async function backupDestination(cwd2, storageDir, data) {
2368
+ const backupsDir = resolve8(cwd2, storageDir, "backups");
2369
+ const path = join15(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
2370
+ try {
2371
+ mkdirSync8(backupsDir, { recursive: true });
2372
+ writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
2373
+ } catch (err) {
2374
+ throw new Error(
2375
+ `Could not write destination backup to ${path} (${err instanceof Error ? err.message : String(err)}). Aborting migration WITHOUT touching the destination \u2014 nothing was overwritten.`
2376
+ );
2377
+ }
2378
+ return path;
2379
+ }
2380
+ function copySqliteFile(srcPath, destPath) {
2381
+ mkdirSync8(dirname7(destPath), { recursive: true });
2382
+ copyFileSync(srcPath, destPath);
2383
+ for (const suffix of ["-wal", "-shm"]) {
2384
+ if (existsSync12(`${srcPath}${suffix}`)) {
2385
+ copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
2386
+ }
2387
+ }
2388
+ if (!existsSync12(destPath)) {
2389
+ throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
2390
+ }
2391
+ }
2392
+ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
2393
+ const config = await loadConfig(cwd2);
2394
+ const storageDir = config.storage.dir;
2395
+ const state = readStorageStateFile(cwd2, storageDir);
2396
+ let realScope;
2397
+ let realDbType;
2398
+ if (!state) {
2399
+ const defaultSqlitePath = config.database.type === "sqlite" ? config.database.path : ".harness/harness.db";
2400
+ const localPath = resolveSqlitePathForScope("local", defaultSqlitePath, cwd2, config, homeDir);
2401
+ const globalPath = resolveSqlitePathForScope("global", defaultSqlitePath, cwd2, config, homeDir);
2402
+ const localCount = await probeTaskCount(localPath);
2403
+ const globalCount = await probeTaskCount(globalPath);
2404
+ if (localCount > 0 && globalCount > 0) {
2405
+ fail(
2406
+ `storage-state.json is missing and BOTH candidate locations have data \u2014 local (${localPath}): ${localCount} task(s); global (${globalPath}): ${globalCount} task(s). Refusing to guess which one is authoritative. Resolve manually (inspect both databases) or delete the one that should be discarded, then re-run this command.`
2407
+ );
2408
+ }
2409
+ if (localCount === 0 && globalCount === 0) {
2410
+ const db = await openDB(config, cwd2, homeDir);
2411
+ try {
2412
+ await db.writeStorageState(cwd2);
2413
+ } finally {
2414
+ await db.close();
2415
+ }
2416
+ log5(pc9.dim("storage-state.json was missing; no data found at either candidate location. Nothing to migrate \u2014 state recorded."));
2417
+ return;
2418
+ }
2419
+ realScope = localCount > 0 ? "local" : "global";
2420
+ realDbType = "sqlite";
2421
+ log5(
2422
+ pc9.yellow(
2423
+ `storage-state.json was missing. Detected real data at ${realScope} sqlite location (${realScope === "local" ? localPath : globalPath}) \u2014 using it as the migration source.`
2424
+ )
2425
+ );
2426
+ } else {
2427
+ realScope = state.scope;
2428
+ realDbType = state.dbType;
2429
+ }
2430
+ const desiredScope = config.storage.scope;
2431
+ const desiredDbType = config.database.type;
2432
+ if (realScope === desiredScope && realDbType === desiredDbType) {
2433
+ log5(pc9.green(`\u2713 Storage already matches config (scope=${desiredScope}, database=${desiredDbType}) \u2014 nothing to migrate.`));
2434
+ return;
2435
+ }
2436
+ if (realDbType !== "sqlite") {
2437
+ fail(
2438
+ `Cannot auto-locate the previous ${realDbType} database \u2014 storage-state.json does not retain connection credentials for security. Manually run "ahk export --json" while still connected to the old database (with the old config), then adjust agent-harness-kit.config.ts and re-import. This direction is out of scope for "ahk migrate storage".`
2439
+ );
2440
+ }
2441
+ if (desiredDbType === "sqlite" && realDbType === "sqlite") {
2442
+ return migrateScopeOnly(cwd2, config, homeDir, realScope, desiredScope, opts);
2443
+ }
2444
+ return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
2445
+ }
2446
+ async function probeTaskCount(dbPath) {
2447
+ if (!existsSync12(dbPath)) return 0;
2448
+ const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
2449
+ const driver = new SQLiteDriver(dbPath);
2450
+ try {
2451
+ await driver.ensureSchema();
2452
+ const counts = await getRowCounts(driver);
2453
+ return counts.tasks;
2454
+ } finally {
2455
+ await driver.close();
2456
+ }
2457
+ }
2458
+ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts) {
2459
+ const sqlitePath = config.database.type === "sqlite" ? config.database.path : ".harness/harness.db";
2460
+ const srcDb = resolveSqlitePathForScope(fromScope, sqlitePath, cwd2, config, homeDir);
2461
+ const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
2462
+ const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
2463
+ const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
2464
+ if (!existsSync12(srcDb)) {
2465
+ fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
2466
+ }
2467
+ const destExists = existsSync12(destDb);
2468
+ if (destExists) {
2469
+ const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
2470
+ const destDriver = new SQLiteDriver(destDb);
2471
+ let destEmpty;
2472
+ try {
2473
+ await destDriver.ensureSchema();
2474
+ destEmpty = await isEmptyDatabase(destDriver);
2475
+ } finally {
2476
+ await destDriver.close();
2477
+ }
2478
+ if (!destEmpty && !opts.force) {
2479
+ fail(
2480
+ `Destination (${toScope}, ${destDb}) already has data. Re-run with --force to overwrite it (a backup of the destination will be written first).`
2481
+ );
2482
+ }
2483
+ if (!destEmpty && opts.force) {
2484
+ const { SQLiteDriver: Driver } = await import("./sqlite-TR4D324R.js");
2485
+ const backupDriver = new Driver(destDb);
2486
+ let data;
2487
+ try {
2488
+ await backupDriver.ensureSchema();
2489
+ const { HarnessDB } = await import("./db-QQ7BR5K7.js");
2490
+ const tmpDb = new HarnessDB(backupDriver, { ...config, storage: { ...config.storage, scope: toScope } }, homeDir);
2491
+ data = await tmpDb.exportJson();
2492
+ } finally {
2493
+ await backupDriver.close();
2494
+ }
2495
+ const backupPath = await backupDestination(cwd2, config.storage.dir, data);
2496
+ log5(pc9.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
2497
+ }
2498
+ }
2499
+ if (opts.dryRun) {
2500
+ log5(pc9.dim(`[dry-run] Would copy ${srcDb} \u2192 ${destDb} (scope ${fromScope} \u2192 ${toScope}), and move current.md.`));
2501
+ return;
2502
+ }
2503
+ copySqliteFile(srcDb, destDb);
2504
+ log5(pc9.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
2505
+ if (existsSync12(srcMd)) {
2506
+ mkdirSync8(dirname7(destMd), { recursive: true });
2507
+ copyFileSync(srcMd, destMd);
2508
+ log5(pc9.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
2509
+ }
2510
+ rmSync(srcDb, { force: true });
2511
+ rmSync(`${srcDb}-wal`, { force: true });
2512
+ rmSync(`${srcDb}-shm`, { force: true });
2513
+ if (existsSync12(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
2514
+ const db = await openDB(config, cwd2, homeDir);
2515
+ try {
2516
+ await db.writeStorageState(cwd2);
2517
+ } finally {
2518
+ await db.close();
2519
+ }
2520
+ log5(pc9.green(`\u2713 Storage migrated: scope ${fromScope} \u2192 ${toScope}`));
2521
+ }
2522
+ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
2523
+ const sqlitePath = config.database.type === "sqlite" ? config.database.path : ".harness/harness.db";
2524
+ const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
2525
+ if (!existsSync12(srcPath)) {
2526
+ fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
2527
+ }
2528
+ const { SQLiteDriver } = await import("./sqlite-TR4D324R.js");
2529
+ const srcDriver = new SQLiteDriver(srcPath);
2530
+ let sourceData;
2531
+ let sourceCounts;
2532
+ try {
2533
+ await srcDriver.ensureSchema();
2534
+ sourceCounts = await getRowCounts(srcDriver);
2535
+ const { HarnessDB } = await import("./db-QQ7BR5K7.js");
2536
+ const srcDb = new HarnessDB(srcDriver, { ...config, storage: { ...config.storage, scope: sourceScope }, database: { type: "sqlite", path: sqlitePath } }, homeDir);
2537
+ sourceData = await srcDb.exportJson();
2538
+ } finally {
2539
+ await srcDriver.close();
2540
+ }
2541
+ let destDb;
2542
+ try {
2543
+ destDb = await openDB(config, cwd2, homeDir);
2544
+ } catch (err) {
2545
+ fail(`Could not connect to destination (${config.database.type}): ${err instanceof Error ? err.message : String(err)}. Verify database configuration.`);
2546
+ }
2547
+ try {
2548
+ const destCounts = await destDb.getRowCounts();
2549
+ const destEmpty = Object.values(destCounts).every((n) => n === 0);
2550
+ const sourceEmpty = Object.values(sourceCounts).every((n) => n === 0);
2551
+ if (!destEmpty) {
2552
+ if (!opts.force) {
2553
+ const bothHaveData = !sourceEmpty;
2554
+ fail(
2555
+ bothHaveData ? `Both source (${sourceCounts.tasks} task(s)) and destination (${config.database.type}, ${destCounts.tasks} task(s)) have data that DIVERGE \u2014 this is not a first-time migration. Refusing to auto-merge. Review both manually, or re-run with --force to overwrite the destination (a JSON backup will be written first).` : `Destination (${config.database.type}) already has data (${destCounts.tasks} task(s)). Re-run with --force to overwrite it (a backup of the destination will be written first).`
2556
+ );
2557
+ }
2558
+ }
2559
+ if (opts.dryRun) {
2560
+ log5(
2561
+ pc9.dim(
2562
+ `[dry-run] Would migrate ${sourceCounts.tasks} task(s) from sqlite (${sourceScope}, ${srcPath}) to ${config.database.type}${destEmpty ? "" : " (destination has data \u2014 would back up first, then overwrite)"}.`
2563
+ )
2564
+ );
2565
+ return;
2566
+ }
2567
+ let backupPath = null;
2568
+ if (!destEmpty && opts.force) {
2569
+ const currentDestData = await destDb.exportJson();
2570
+ backupPath = await backupDestination(cwd2, config.storage.dir, currentDestData);
2571
+ log5(pc9.yellow(` Backed up existing destination data \u2192 ${backupPath}`));
2572
+ }
2573
+ await destDb.importFullExport(sourceData, config.database.type, { truncateFirst: !destEmpty });
2574
+ await destDb.writeStorageState(cwd2);
2575
+ log5(
2576
+ pc9.green(
2577
+ `\u2713 Migrated ${sourceData.tasks.length} task(s), ${sourceData.actions.length} action(s) from sqlite (${sourceScope}) \u2192 ${config.database.type}.`
2578
+ )
2579
+ );
2580
+ if (backupPath) log5(pc9.dim(` Destination backup: ${backupPath}`));
2581
+ log5(pc9.yellow(` Note: the original sqlite file at ${srcPath} was NOT deleted \u2014 remove it manually once you've verified the migration.`));
2582
+ } finally {
2583
+ await destDb.close();
2584
+ }
2585
+ }
2586
+
2601
2587
  // src/commands/reset.ts
2602
- import { existsSync as existsSync10, readdirSync, rmSync } from "fs";
2603
- import { join as join15, resolve as resolve9 } from "path";
2588
+ import { existsSync as existsSync13, readdirSync, rmSync as rmSync2 } from "fs";
2589
+ import { join as join16, resolve as resolve9 } from "path";
2604
2590
  import * as p5 from "@clack/prompts";
2605
- import pc9 from "picocolors";
2591
+ import pc10 from "picocolors";
2606
2592
  var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
2607
2593
  async function resetAgentMds(cwd2, provider) {
2608
2594
  const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
2609
2595
  const agentDirPath = resolve9(cwd2, agentDir);
2610
- if (!existsSync10(agentDirPath)) {
2611
- console.log(pc9.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2596
+ if (!existsSync13(agentDirPath)) {
2597
+ console.log(pc10.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2612
2598
  return;
2613
2599
  }
2614
2600
  const existingFiles = [];
@@ -2620,11 +2606,11 @@ async function resetAgentMds(cwd2, provider) {
2620
2606
  }
2621
2607
  }
2622
2608
  } catch {
2623
- console.log(pc9.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2609
+ console.log(pc10.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2624
2610
  return;
2625
2611
  }
2626
2612
  if (existingFiles.length === 0) {
2627
- console.log(pc9.yellow(` No agent MD files found in ${agentDir}/`));
2613
+ console.log(pc10.yellow(` No agent MD files found in ${agentDir}/`));
2628
2614
  return;
2629
2615
  }
2630
2616
  for (const file of existingFiles) {
@@ -2633,19 +2619,19 @@ async function resetAgentMds(cwd2, provider) {
2633
2619
  initialValue: true
2634
2620
  });
2635
2621
  if (p5.isCancel(confirm3)) {
2636
- console.log(pc9.red(" Cancelled by user."));
2622
+ console.log(pc10.red(" Cancelled by user."));
2637
2623
  return;
2638
2624
  }
2639
2625
  if (confirm3) {
2640
2626
  try {
2641
- const filePath = join15(agentDirPath, file);
2642
- rmSync(filePath, { force: true });
2643
- console.log(pc9.green(` Removed ${file}`));
2627
+ const filePath = join16(agentDirPath, file);
2628
+ rmSync2(filePath, { force: true });
2629
+ console.log(pc10.green(` Removed ${file}`));
2644
2630
  } catch {
2645
- console.error(pc9.red(` Failed to remove ${file}`));
2631
+ console.error(pc10.red(` Failed to remove ${file}`));
2646
2632
  }
2647
2633
  } else {
2648
- console.log(pc9.cyan(` Skipped ${file}`));
2634
+ console.log(pc10.cyan(` Skipped ${file}`));
2649
2635
  }
2650
2636
  }
2651
2637
  }
@@ -2654,7 +2640,7 @@ async function runReset(cwd2, opts) {
2654
2640
  try {
2655
2641
  config = await loadConfig(cwd2);
2656
2642
  } catch {
2657
- console.error(pc9.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2643
+ console.error(pc10.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2658
2644
  process.exit(1);
2659
2645
  }
2660
2646
  const storageDir = config.storage.dir || ".harness";
@@ -2663,12 +2649,12 @@ async function runReset(cwd2, opts) {
2663
2649
  let resetDb = false;
2664
2650
  let resetFeatureList = false;
2665
2651
  let resetAgentMdsFlag = false;
2666
- if (dbPath && existsSync10(dbPath)) {
2652
+ if (dbPath && existsSync13(dbPath)) {
2667
2653
  if (opts.force) {
2668
2654
  resetDb = true;
2669
2655
  } else {
2670
2656
  if (config.database.type !== "sqlite") {
2671
- console.log(pc9.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2657
+ console.log(pc10.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2672
2658
  resetDb = false;
2673
2659
  } else {
2674
2660
  const confirm3 = await p5.confirm({
@@ -2676,16 +2662,16 @@ async function runReset(cwd2, opts) {
2676
2662
  initialValue: true
2677
2663
  });
2678
2664
  if (p5.isCancel(confirm3)) {
2679
- console.log(pc9.red(" Cancelled by user."));
2665
+ console.log(pc10.red(" Cancelled by user."));
2680
2666
  return;
2681
2667
  }
2682
2668
  resetDb = confirm3;
2683
2669
  }
2684
2670
  }
2685
2671
  } else if (!dbPath) {
2686
- console.log(pc9.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2672
+ console.log(pc10.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2687
2673
  }
2688
- if (existsSync10(featureListPath)) {
2674
+ if (existsSync13(featureListPath)) {
2689
2675
  if (opts.force) {
2690
2676
  resetFeatureList = true;
2691
2677
  } else {
@@ -2694,7 +2680,7 @@ async function runReset(cwd2, opts) {
2694
2680
  initialValue: true
2695
2681
  });
2696
2682
  if (p5.isCancel(confirm3)) {
2697
- console.log(pc9.red(" Cancelled by user."));
2683
+ console.log(pc10.red(" Cancelled by user."));
2698
2684
  return;
2699
2685
  }
2700
2686
  resetFeatureList = confirm3;
@@ -2705,20 +2691,20 @@ async function runReset(cwd2, opts) {
2705
2691
  }
2706
2692
  if (resetDb && dbPath) {
2707
2693
  try {
2708
- rmSync(dbPath, { force: true });
2709
- rmSync(`${dbPath}-wal`, { force: true });
2710
- rmSync(`${dbPath}-shm`, { force: true });
2711
- console.log(pc9.green(` \u2713 Removed ${dbPath}`));
2694
+ rmSync2(dbPath, { force: true });
2695
+ rmSync2(`${dbPath}-wal`, { force: true });
2696
+ rmSync2(`${dbPath}-shm`, { force: true });
2697
+ console.log(pc10.green(` \u2713 Removed ${dbPath}`));
2712
2698
  } catch {
2713
- console.error(pc9.red(` \u2717 Failed to remove ${dbPath}`));
2699
+ console.error(pc10.red(` \u2717 Failed to remove ${dbPath}`));
2714
2700
  }
2715
2701
  }
2716
2702
  if (resetFeatureList) {
2717
2703
  try {
2718
- rmSync(featureListPath, { force: true });
2719
- console.log(pc9.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2704
+ rmSync2(featureListPath, { force: true });
2705
+ console.log(pc10.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2720
2706
  } catch {
2721
- console.error(pc9.red(` \u2717 Failed to remove ${featureListPath}`));
2707
+ console.error(pc10.red(` \u2717 Failed to remove ${featureListPath}`));
2722
2708
  }
2723
2709
  }
2724
2710
  if (resetAgentMdsFlag) {
@@ -2726,16 +2712,16 @@ async function runReset(cwd2, opts) {
2726
2712
  await resetAgentMds(cwd2, opts.provider || "claude-code");
2727
2713
  }
2728
2714
  if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
2729
- console.log(pc9.yellow(" Nothing to reset (all items missing or skipped)."));
2715
+ console.log(pc10.yellow(" Nothing to reset (all items missing or skipped)."));
2730
2716
  return;
2731
2717
  }
2732
2718
  console.log("");
2733
- console.log(pc9.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2719
+ console.log(pc10.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2734
2720
  }
2735
2721
 
2736
2722
  // src/core/mcp-server.ts
2737
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync10 } from "fs";
2738
- import { join as join17, resolve as resolve10 } from "path";
2723
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
2724
+ import { join as join18, resolve as resolve10 } from "path";
2739
2725
  import { Server } from "@modelcontextprotocol/sdk/server";
2740
2726
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2741
2727
  import {
@@ -2744,8 +2730,8 @@ import {
2744
2730
  } from "@modelcontextprotocol/sdk/types.js";
2745
2731
 
2746
2732
  // src/core/permissions-check.ts
2747
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2748
- import { join as join16 } from "path";
2733
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
2734
+ import { join as join17 } from "path";
2749
2735
  var CANONICAL = {
2750
2736
  lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
2751
2737
  explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
@@ -2768,14 +2754,14 @@ function checkPermissionsSync(cwd2, config) {
2768
2754
  const agents = {};
2769
2755
  let in_sync = true;
2770
2756
  for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
2771
- const filePath = join16(cwd2, ".claude", "agents", `${agent}.md`);
2772
- if (!existsSync11(filePath)) {
2757
+ const filePath = join17(cwd2, ".claude", "agents", `${agent}.md`);
2758
+ if (!existsSync14(filePath)) {
2773
2759
  const missing2 = CANONICAL[agent];
2774
2760
  agents[agent] = { ok: false, missing: missing2, extra: [] };
2775
2761
  in_sync = false;
2776
2762
  continue;
2777
2763
  }
2778
- const content = readFileSync8(filePath, "utf-8");
2764
+ const content = readFileSync9(filePath, "utf-8");
2779
2765
  const installed = parseToolsFromFrontmatter(content);
2780
2766
  const canonical = CANONICAL[agent];
2781
2767
  const missing = canonical.filter((t) => !installed.includes(t));
@@ -3195,19 +3181,19 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3195
3181
  return ok2(JSON.stringify(result, null, 2));
3196
3182
  }
3197
3183
  case "deps.snapshot": {
3198
- const pkgPath2 = join17(cwd2, "package.json");
3199
- if (!existsSync12(pkgPath2)) {
3184
+ const pkgPath2 = join18(cwd2, "package.json");
3185
+ if (!existsSync15(pkgPath2)) {
3200
3186
  return ok2("package.json not found in project root", true);
3201
3187
  }
3202
- const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
3188
+ const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
3203
3189
  const snapshot = {
3204
3190
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
3205
3191
  dependencies: pkg2.dependencies ?? {},
3206
3192
  devDependencies: pkg2.devDependencies ?? {}
3207
3193
  };
3208
- const harnessDir = join17(cwd2, ".harness");
3194
+ const harnessDir = join18(cwd2, ".harness");
3209
3195
  mkdirSync9(harnessDir, { recursive: true });
3210
- writeFileSync10(join17(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
3196
+ writeFileSync10(join18(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
3211
3197
  return ok2(
3212
3198
  JSON.stringify({
3213
3199
  message: "Snapshot saved to .harness/deps-lock.json",
@@ -3216,12 +3202,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3216
3202
  );
3217
3203
  }
3218
3204
  case "deps.check": {
3219
- const pkgPath2 = join17(cwd2, "package.json");
3220
- const lockPath = join17(cwd2, ".harness", "deps-lock.json");
3221
- if (!existsSync12(pkgPath2)) {
3205
+ const pkgPath2 = join18(cwd2, "package.json");
3206
+ const lockPath = join18(cwd2, ".harness", "deps-lock.json");
3207
+ if (!existsSync15(pkgPath2)) {
3222
3208
  return ok2("package.json not found in project root", true);
3223
3209
  }
3224
- if (!existsSync12(lockPath)) {
3210
+ if (!existsSync15(lockPath)) {
3225
3211
  return ok2(
3226
3212
  JSON.stringify({
3227
3213
  status: "no-snapshot",
@@ -3229,8 +3215,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3229
3215
  })
3230
3216
  );
3231
3217
  }
3232
- const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
3233
- const lock = JSON.parse(readFileSync9(lockPath, "utf8"));
3218
+ const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
3219
+ const lock = JSON.parse(readFileSync10(lockPath, "utf8"));
3234
3220
  const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
3235
3221
  const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
3236
3222
  const added = [];
@@ -3297,7 +3283,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
3297
3283
  for (const file of files) {
3298
3284
  if (results.length >= maxResults) break;
3299
3285
  try {
3300
- const content = readFileSync9(file, "utf8");
3286
+ const content = readFileSync10(file, "utf8");
3301
3287
  const lines = content.split("\n");
3302
3288
  for (let i = 0; i < lines.length; i++) {
3303
3289
  const lower = lines[i].toLowerCase();
@@ -3322,7 +3308,7 @@ function collectMarkdownFiles(dir) {
3322
3308
  const files = [];
3323
3309
  try {
3324
3310
  for (const entry of readdirSync2(dir)) {
3325
- const full = join17(dir, entry);
3311
+ const full = join18(dir, entry);
3326
3312
  const stat = statSync(full);
3327
3313
  if (stat.isDirectory()) {
3328
3314
  files.push(...collectMarkdownFiles(full));
@@ -3373,12 +3359,12 @@ async function runServe(cwd2, opts) {
3373
3359
 
3374
3360
  // src/commands/status.ts
3375
3361
  import Table from "cli-table3";
3376
- import pc10 from "picocolors";
3362
+ import pc11 from "picocolors";
3377
3363
  var STATUS_COLOR = {
3378
- pending: (s) => pc10.dim(s),
3379
- in_progress: (s) => pc10.cyan(s),
3380
- done: (s) => pc10.green(s),
3381
- blocked: (s) => pc10.red(s)
3364
+ pending: (s) => pc11.dim(s),
3365
+ in_progress: (s) => pc11.cyan(s),
3366
+ done: (s) => pc11.green(s),
3367
+ blocked: (s) => pc11.red(s)
3382
3368
  };
3383
3369
  async function runStatus(cwd2, opts) {
3384
3370
  const config = await loadConfig(cwd2);
@@ -3399,11 +3385,11 @@ async function runStatus(cwd2, opts) {
3399
3385
  return;
3400
3386
  }
3401
3387
  if (tasks.length === 0) {
3402
- console.log(pc10.dim("No tasks yet. Run: ahk task add"));
3388
+ console.log(pc11.dim("No tasks yet. Run: ahk task add"));
3403
3389
  return;
3404
3390
  }
3405
3391
  const table = new Table({
3406
- head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc10.bold(h)),
3392
+ head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc11.bold(h)),
3407
3393
  style: { head: [], border: [] }
3408
3394
  });
3409
3395
  for (const t of tasks) {
@@ -3421,12 +3407,12 @@ async function runStatus(cwd2, opts) {
3421
3407
  const inProgress = tasks.filter((t) => t.status === "in_progress");
3422
3408
  if (inProgress.length > 0) {
3423
3409
  console.log("");
3424
- console.log(pc10.bold("Active actions:"));
3410
+ console.log(pc11.bold("Active actions:"));
3425
3411
  for (const t of inProgress) {
3426
3412
  const actions = await db.getActionsForTask(t.id);
3427
3413
  const active = actions.filter((a) => a.status === "in_progress");
3428
3414
  for (const a of active) {
3429
- console.log(` ${pc10.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3415
+ console.log(` ${pc11.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3430
3416
  }
3431
3417
  }
3432
3418
  }
@@ -3435,10 +3421,10 @@ async function runStatus(cwd2, opts) {
3435
3421
  const fn = STATUS_COLOR[s.status] ?? ((x) => x);
3436
3422
  return `${fn(s.status)}: ${s.total}`;
3437
3423
  });
3438
- console.log(pc10.dim("Tasks \u2014 ") + parts.join(pc10.dim(" | ")));
3424
+ console.log(pc11.dim("Tasks \u2014 ") + parts.join(pc11.dim(" | ")));
3439
3425
  const archivedTasks = await db.getArchivedTasks();
3440
3426
  if (archivedTasks.length > 0) {
3441
- console.log(pc10.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3427
+ console.log(pc11.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3442
3428
  }
3443
3429
  } finally {
3444
3430
  await db.close();
@@ -3446,13 +3432,13 @@ async function runStatus(cwd2, opts) {
3446
3432
  }
3447
3433
 
3448
3434
  // src/commands/sync.ts
3449
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
3450
- import { join as join18, resolve as resolve11 } from "path";
3451
- import pc11 from "picocolors";
3435
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3436
+ import { join as join19, resolve as resolve11 } from "path";
3437
+ import pc12 from "picocolors";
3452
3438
  async function runSync(cwd2, opts) {
3453
3439
  const config = await loadConfig(cwd2);
3454
3440
  const direction = opts.direction ?? "both";
3455
- const featureListPath = resolve11(join18(cwd2, config.storage.dir, "feature_list.json"));
3441
+ const featureListPath = resolve11(join19(cwd2, config.storage.dir, "feature_list.json"));
3456
3442
  const db = await openDB(config, cwd2);
3457
3443
  try {
3458
3444
  if (direction === "in" || direction === "both") {
@@ -3466,44 +3452,44 @@ async function runSync(cwd2, opts) {
3466
3452
  }
3467
3453
  }
3468
3454
  async function syncIn(featureListPath, db, dryRun) {
3469
- if (!existsSync13(featureListPath)) {
3470
- console.log(pc11.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3455
+ if (!existsSync16(featureListPath)) {
3456
+ console.log(pc12.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3471
3457
  return;
3472
3458
  }
3473
3459
  let seeds;
3474
3460
  try {
3475
- seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
3461
+ seeds = JSON.parse(readFileSync11(featureListPath, "utf8"));
3476
3462
  } catch (err) {
3477
- console.error(pc11.red(`Failed to parse feature_list.json: ${err}`));
3463
+ console.error(pc12.red(`Failed to parse feature_list.json: ${err}`));
3478
3464
  process.exit(1);
3479
3465
  }
3480
3466
  if (dryRun) {
3481
- console.log(pc11.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3467
+ console.log(pc12.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3482
3468
  for (const t of seeds) {
3483
3469
  const existing = await db.getTaskBySlug(t.slug);
3484
- console.log(` ${existing ? pc11.dim("skip") : pc11.green("add ")} ${t.slug}`);
3470
+ console.log(` ${existing ? pc12.dim("skip") : pc12.green("add ")} ${t.slug}`);
3485
3471
  }
3486
3472
  return;
3487
3473
  }
3488
3474
  const result = await db.syncFromFeatureList(seeds);
3489
- console.log(pc11.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3475
+ console.log(pc12.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3490
3476
  }
3491
3477
  async function syncOut(db, cwd2, dryRun) {
3492
3478
  if (dryRun) {
3493
3479
  const tasks = await db.getTasks();
3494
- console.log(pc11.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3480
+ console.log(pc12.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3495
3481
  console.log(` ${tasks.length} tasks would be written`);
3496
3482
  return;
3497
3483
  }
3498
3484
  await db.writeFeatureList(cwd2);
3499
- console.log(pc11.green("\u2713 Out-sync: feature_list.json updated"));
3485
+ console.log(pc12.green("\u2713 Out-sync: feature_list.json updated"));
3500
3486
  }
3501
3487
 
3502
3488
  // src/commands/task/add.ts
3503
3489
  import * as p6 from "@clack/prompts";
3504
- import pc12 from "picocolors";
3490
+ import pc13 from "picocolors";
3505
3491
  async function runTaskAdd(cwd2) {
3506
- p6.intro(pc12.bold("agent-harness-kit \u2014 add task"));
3492
+ p6.intro(pc13.bold("agent-harness-kit \u2014 add task"));
3507
3493
  const title = await cliFormWithRetry(
3508
3494
  async () => {
3509
3495
  const val = await p6.text({ message: "Task title" });
@@ -3546,10 +3532,10 @@ async function runTaskAdd(cwd2) {
3546
3532
  await db.writeFeatureList(cwd2);
3547
3533
  await db.close();
3548
3534
  spinner6.stop("");
3549
- console.log(pc12.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
3550
- console.log(pc12.cyan("\u2192") + " " + pc12.cyan("ahk status") + " to see all tasks");
3535
+ console.log(pc13.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
3536
+ console.log(pc13.cyan("\u2192") + " " + pc13.cyan("ahk status") + " to see all tasks");
3551
3537
  } catch (err) {
3552
- spinner6.stop(pc12.red("Failed"));
3538
+ spinner6.stop(pc13.red("Failed"));
3553
3539
  p6.log.error(err instanceof Error ? err.message : String(err));
3554
3540
  process.exit(1);
3555
3541
  }
@@ -3557,17 +3543,17 @@ async function runTaskAdd(cwd2) {
3557
3543
 
3558
3544
  // src/commands/task/done.ts
3559
3545
  import { spawnSync as spawnSync2 } from "child_process";
3560
- import { existsSync as existsSync14 } from "fs";
3546
+ import { existsSync as existsSync17 } from "fs";
3561
3547
  import { resolve as resolve12 } from "path";
3562
- import pc13 from "picocolors";
3548
+ import pc14 from "picocolors";
3563
3549
  async function runTaskDone(cwd2, idOrSlug) {
3564
3550
  const config = await loadConfig(cwd2);
3565
3551
  if (config.health.required) {
3566
3552
  const scriptPath = resolve12(cwd2, config.health.scriptPath);
3567
- if (existsSync14(scriptPath)) {
3553
+ if (existsSync17(scriptPath)) {
3568
3554
  const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
3569
3555
  if (result.status !== 0) {
3570
- console.error(pc13.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3556
+ console.error(pc14.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3571
3557
  if (result.stdout) console.error(result.stdout);
3572
3558
  if (result.stderr) console.error(result.stderr);
3573
3559
  process.exit(1);
@@ -3580,16 +3566,16 @@ async function runTaskDone(cwd2, idOrSlug) {
3580
3566
  const isId = !isNaN(parsed);
3581
3567
  const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
3582
3568
  if (!task2) {
3583
- console.error(pc13.red(`Task not found: ${idOrSlug}`));
3569
+ console.error(pc14.red(`Task not found: ${idOrSlug}`));
3584
3570
  process.exit(1);
3585
3571
  }
3586
3572
  if (task2.status === "done") {
3587
- console.log(pc13.dim(`Task #${task2.id} is already done.`));
3573
+ console.log(pc14.dim(`Task #${task2.id} is already done.`));
3588
3574
  return;
3589
3575
  }
3590
3576
  await db.updateTaskStatus(task2.id, "done");
3591
3577
  await db.writeFeatureList(cwd2);
3592
- console.log(pc13.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3578
+ console.log(pc14.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3593
3579
  } finally {
3594
3580
  await db.close();
3595
3581
  }
@@ -3597,9 +3583,9 @@ async function runTaskDone(cwd2, idOrSlug) {
3597
3583
 
3598
3584
  // src/commands/task/edit.ts
3599
3585
  import * as p7 from "@clack/prompts";
3600
- import pc14 from "picocolors";
3586
+ import pc15 from "picocolors";
3601
3587
  async function runTaskEdit(cwd2) {
3602
- p7.intro(pc14.bold("agent-harness-kit \u2014 edit task"));
3588
+ p7.intro(pc15.bold("agent-harness-kit \u2014 edit task"));
3603
3589
  const config = await loadConfig(cwd2);
3604
3590
  const db = await openDB(config, cwd2);
3605
3591
  try {
@@ -3677,9 +3663,9 @@ async function runTaskEdit(cwd2) {
3677
3663
  await db.updateTaskAcceptance(task2.id, newAcceptance);
3678
3664
  await db.writeFeatureList(cwd2);
3679
3665
  spinner6.stop("");
3680
- console.log(pc14.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3666
+ console.log(pc15.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3681
3667
  } catch (err) {
3682
- spinner6.stop(pc14.red("Failed"));
3668
+ spinner6.stop(pc15.red("Failed"));
3683
3669
  p7.log.error(err instanceof Error ? err.message : String(err));
3684
3670
  process.exit(1);
3685
3671
  }
@@ -3690,12 +3676,12 @@ async function runTaskEdit(cwd2) {
3690
3676
 
3691
3677
  // src/commands/task/list.ts
3692
3678
  import Table2 from "cli-table3";
3693
- import pc15 from "picocolors";
3679
+ import pc16 from "picocolors";
3694
3680
  var STATUS_COLOR2 = {
3695
- pending: (s) => pc15.dim(s),
3696
- in_progress: (s) => pc15.cyan(s),
3697
- done: (s) => pc15.green(s),
3698
- blocked: (s) => pc15.red(s)
3681
+ pending: (s) => pc16.dim(s),
3682
+ in_progress: (s) => pc16.cyan(s),
3683
+ done: (s) => pc16.green(s),
3684
+ blocked: (s) => pc16.red(s)
3699
3685
  };
3700
3686
  async function runTaskList(cwd2, opts) {
3701
3687
  const config = await loadConfig(cwd2);
@@ -3712,11 +3698,11 @@ async function runTaskList(cwd2, opts) {
3712
3698
  let msg = "No tasks";
3713
3699
  if (filterStatus) msg += ` with status: ${filterStatus}`;
3714
3700
  if (opts.archived) msg += " (archived)";
3715
- console.log(pc15.dim(msg + "."));
3701
+ console.log(pc16.dim(msg + "."));
3716
3702
  return;
3717
3703
  }
3718
3704
  const table = new Table2({
3719
- head: ["ID", "Slug", "Title", "Status"].map((h) => pc15.bold(h)),
3705
+ head: ["ID", "Slug", "Title", "Status"].map((h) => pc16.bold(h)),
3720
3706
  style: { head: [], border: [] }
3721
3707
  });
3722
3708
  for (const t of tasks) {
@@ -3727,7 +3713,7 @@ async function runTaskList(cwd2, opts) {
3727
3713
  if (!opts.archived && !opts.includeArchived) {
3728
3714
  const archivedTasks = await db.getArchivedTasks();
3729
3715
  if (archivedTasks.length > 0) {
3730
- console.log(pc15.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
3716
+ console.log(pc16.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
3731
3717
  }
3732
3718
  }
3733
3719
  } finally {
@@ -3735,8 +3721,42 @@ async function runTaskList(cwd2, opts) {
3735
3721
  }
3736
3722
  }
3737
3723
 
3724
+ // src/core/local-install-guard.ts
3725
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
3726
+ import { join as join20 } from "path";
3727
+ import pc17 from "picocolors";
3728
+ function isLocalInstallSatisfied(cwd2) {
3729
+ const selfPkgPath = join20(cwd2, "package.json");
3730
+ let projectPkg = null;
3731
+ if (existsSync18(selfPkgPath)) {
3732
+ try {
3733
+ const selfPkg = JSON.parse(readFileSync12(selfPkgPath, "utf8"));
3734
+ if (selfPkg?.name === pkg.name) return true;
3735
+ projectPkg = selfPkg;
3736
+ } catch {
3737
+ }
3738
+ }
3739
+ const [scope, name] = pkg.name.split("/");
3740
+ const localPath = pkg.name.startsWith("@") ? join20(cwd2, "node_modules", scope, name) : join20(cwd2, "node_modules", pkg.name);
3741
+ if (existsSync18(localPath)) return true;
3742
+ const isPnp = existsSync18(join20(cwd2, ".pnp.cjs")) || existsSync18(join20(cwd2, ".pnp.loader.mjs"));
3743
+ if (isPnp && projectPkg) {
3744
+ const deps = {
3745
+ ...projectPkg.dependencies ?? {},
3746
+ ...projectPkg.devDependencies ?? {}
3747
+ };
3748
+ if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
3749
+ }
3750
+ return false;
3751
+ }
3752
+ function printLocalInstallWarning() {
3753
+ console.error(pc17.red(`\u2717 ${pkg.name} must be installed locally in this project.`));
3754
+ console.error(pc17.dim(` Run: npm install --save-dev ${pkg.name}`));
3755
+ console.error(pc17.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
3756
+ }
3757
+
3738
3758
  // src/core/update-check.ts
3739
- import pc16 from "picocolors";
3759
+ import pc18 from "picocolors";
3740
3760
  var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
3741
3761
  var TIMEOUT_MS2 = 2500;
3742
3762
  function checkForUpdate(currentVersion) {
@@ -3754,8 +3774,8 @@ function checkForUpdate(currentVersion) {
3754
3774
  }
3755
3775
  function printUpdateMessage({ current, latest }) {
3756
3776
  const lines = [
3757
- ` Update available ${pc16.dim(current)} \u2192 ${pc16.green(latest)} `,
3758
- ` Run: ${pc16.cyan(`pnpm i ${pkg.name}@${latest}`)} `
3777
+ ` Update available ${pc18.dim(current)} \u2192 ${pc18.green(latest)} `,
3778
+ ` Run: ${pc18.cyan(`pnpm i ${pkg.name}@${latest}`)} `
3759
3779
  ];
3760
3780
  drawBox(lines);
3761
3781
  }
@@ -3773,7 +3793,7 @@ var cwd = process.cwd();
3773
3793
  var updateCheck = checkForUpdate(pkg.version);
3774
3794
  var program = new Command();
3775
3795
  program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for multi-agent harness systems").version(pkg.version, "-v, --version");
3776
- program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").action(async (opts) => {
3796
+ program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
3777
3797
  await runInit(cwd, opts);
3778
3798
  });
3779
3799
  program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").action(async (opts) => {
@@ -3807,9 +3827,20 @@ task.command("edit").description("Edit a task interactively").action(async () =>
3807
3827
  program.command("dashboard").description("Open web dashboard to visualize harness data").option("-p, --port <port>", "Port to listen on", "4242").option("--no-open", "Do not open browser automatically").action(async (opts) => {
3808
3828
  await runDashboard(cwd, { port: parseInt(opts.port), open: opts.open });
3809
3829
  });
3810
- program.command("migrate").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode").action(async (opts) => {
3830
+ var migrate = program.command("migrate").description("Migrate provider files to a different provider, or migrate harness storage (see subcommands)");
3831
+ migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli").action(async (opts) => {
3811
3832
  await runMigrate(cwd, opts);
3812
3833
  });
3834
+ migrate.command("storage").description(
3835
+ "Migrate harness DB storage between local/global scope or sqlite/postgres/mysql, based on agent-harness-kit.config.ts vs the real current state"
3836
+ ).option("--force", "Required to overwrite a non-empty destination (a backup is written first)").option("--dry-run", "Preview what would migrate without applying any changes").action(async (opts) => {
3837
+ try {
3838
+ await runMigrateStorage(cwd, { force: opts.force, dryRun: opts["dry-run"] });
3839
+ } catch (err) {
3840
+ console.error(pc19.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
3841
+ process.exit(1);
3842
+ }
3843
+ });
3813
3844
  program.command("export").description("Export the database").option("--sql", "SQL dump").option("--json", "JSON export of tasks and actions").option("--output <path>", "Output file path (default: stdout)").action(async (opts) => {
3814
3845
  await runExport(cwd, opts);
3815
3846
  });
@@ -3819,9 +3850,25 @@ program.command("reset").description("Reset/clear harness data (DB, feature list
3819
3850
  program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
3820
3851
  await runDoctor(cwd);
3821
3852
  });
3853
+ program.hook("preAction", () => {
3854
+ if (!isLocalInstallSatisfied(cwd)) {
3855
+ printLocalInstallWarning();
3856
+ process.exit(1);
3857
+ }
3858
+ });
3822
3859
  program.hook("postAction", async () => {
3823
3860
  const update = await updateCheck;
3824
3861
  if (update) printUpdateMessage(update);
3825
3862
  });
3826
- program.parse();
3863
+ function rewriteLegacyMigrateArgv(argv) {
3864
+ const migrateIdx = argv.indexOf("migrate");
3865
+ if (migrateIdx === -1) return argv;
3866
+ const next = argv[migrateIdx + 1];
3867
+ const isLegacyForm = next === void 0 || next === "--to";
3868
+ if (!isLegacyForm) return argv;
3869
+ const rewritten = [...argv];
3870
+ rewritten.splice(migrateIdx + 1, 0, "provider");
3871
+ return rewritten;
3872
+ }
3873
+ program.parse(rewriteLegacyMigrateArgv(process.argv));
3827
3874
  //# sourceMappingURL=cli.js.map