@cardor/agent-harness-kit 1.7.2 → 1.8.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
@@ -12,7 +12,7 @@ import * as p from "@clack/prompts";
12
12
  import pc from "picocolors";
13
13
 
14
14
  // src/core/materializer/claude-code.ts
15
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
15
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
16
16
  import { join as join4, resolve as resolve3 } from "path";
17
17
 
18
18
  // src/utils/file.ts
@@ -83,7 +83,8 @@ var MCP_CLAUDE_PERMISSIONS_LEAD = [
83
83
  "mcp__agent-harness-kit__tasks_archive",
84
84
  "mcp__agent-harness-kit__tasks_unarchive",
85
85
  "mcp__agent-harness-kit__tasks_acceptance_get",
86
- "mcp__agent-harness-kit__docs_search"
86
+ "mcp__agent-harness-kit__docs_search",
87
+ "mcp__agent-harness-kit__ahk_doctor"
87
88
  ];
88
89
  var MCP_CLAUDE_PERMISSIONS_EXPLORER = [
89
90
  "mcp__agent-harness-kit__actions_start",
@@ -95,7 +96,8 @@ var MCP_CLAUDE_PERMISSIONS_EXPLORER = [
95
96
  "mcp__agent-harness-kit__tasks_get",
96
97
  "mcp__agent-harness-kit__tasks_claim",
97
98
  "mcp__agent-harness-kit__tasks_acceptance_get",
98
- "mcp__agent-harness-kit__docs_search"
99
+ "mcp__agent-harness-kit__docs_search",
100
+ "mcp__agent-harness-kit__ahk_doctor"
99
101
  ];
100
102
  var MCP_CLAUDE_PERMISSIONS_BUILDER = [
101
103
  "mcp__agent-harness-kit__actions_start",
@@ -112,7 +114,8 @@ var MCP_CLAUDE_PERMISSIONS_BUILDER = [
112
114
  "mcp__agent-harness-kit__tasks_archive",
113
115
  "mcp__agent-harness-kit__tasks_unarchive",
114
116
  "mcp__agent-harness-kit__tasks_acceptance_get",
115
- "mcp__agent-harness-kit__docs_search"
117
+ "mcp__agent-harness-kit__docs_search",
118
+ "mcp__agent-harness-kit__ahk_doctor"
116
119
  ];
117
120
  var MCP_CLAUDE_PERMISSIONS_REVIEWER = [
118
121
  "mcp__agent-harness-kit__actions_start",
@@ -130,7 +133,8 @@ var MCP_CLAUDE_PERMISSIONS_REVIEWER = [
130
133
  "mcp__agent-harness-kit__tasks_unarchive",
131
134
  "mcp__agent-harness-kit__tasks_acceptance_update",
132
135
  "mcp__agent-harness-kit__tasks_acceptance_get",
133
- "mcp__agent-harness-kit__docs_search"
136
+ "mcp__agent-harness-kit__docs_search",
137
+ "mcp__agent-harness-kit__ahk_doctor"
134
138
  ];
135
139
  var MCP_CLAUDE_PERMISSIONS_CONSULTANT = [
136
140
  "mcp__agent-harness-kit__actions_start",
@@ -144,7 +148,8 @@ var MCP_CLAUDE_PERMISSIONS_CONSULTANT = [
144
148
  "mcp__agent-harness-kit__tasks_acceptance_get",
145
149
  "mcp__agent-harness-kit__deps_snapshot",
146
150
  "mcp__agent-harness-kit__deps_check",
147
- "mcp__agent-harness-kit__docs_search"
151
+ "mcp__agent-harness-kit__docs_search",
152
+ "mcp__agent-harness-kit__ahk_doctor"
148
153
  ];
149
154
  var MCP_CLAUDE_PERMISSIONS = [
150
155
  .../* @__PURE__ */ new Set([
@@ -249,7 +254,8 @@ function mergeCodexConfigToml(filePath, port) {
249
254
 
250
255
  // src/core/materializer/scaffold-utils.ts
251
256
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
252
- import { join as join3, resolve as resolve2 } from "path";
257
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
258
+ import { fileURLToPath as fileURLToPath2 } from "url";
253
259
 
254
260
  // src/core/materializer/templates.ts
255
261
  import { readFileSync as readFileSync2 } from "fs";
@@ -655,6 +661,7 @@ var GITIGNORE_ENTRIES = `
655
661
  `;
656
662
 
657
663
  // src/core/materializer/scaffold-utils.ts
664
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
658
665
  function writeAgentFile(cwd2, relPath, content) {
659
666
  const abs = join3(cwd2, relPath);
660
667
  if (existsSync2(abs)) return;
@@ -672,36 +679,14 @@ function appendGitignore(cwd2) {
672
679
  function slugify(title) {
673
680
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
674
681
  }
675
- var AGENT_TOOLS = {
676
- lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
677
- explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
678
- consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
679
- builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
680
- reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
681
- };
682
- async function syncAgentPermissions(cwd2) {
683
- for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
684
- const filePath = join3(cwd2, ".claude", "agents", `${agent}.md`);
685
- if (!existsSync2(filePath)) {
686
- console.log(` ${agent}.md not found \u2014 skipping`);
687
- continue;
688
- }
689
- const content = readFileSync3(filePath, "utf-8");
690
- const updated = content.replace(
691
- /(tools:\n)((?: - [^\n]+\n)*)/m,
692
- (_match, header, toolsSection) => {
693
- const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
694
- const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
695
- const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
696
- return header + nativeSection + mcpSection;
697
- }
698
- );
699
- if (updated === content) {
700
- console.log(` ${agent}.md already in sync`);
701
- } else {
702
- writeFileSync3(filePath, updated, "utf-8");
703
- console.log(` ${agent}.md updated`);
704
- }
682
+ function writeSkills(cwd2, skillsDir) {
683
+ const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage"];
684
+ 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");
688
+ mkdirSync3(destDir, { recursive: true });
689
+ writeFileSync3(dest, readFileSync3(src, "utf8"), "utf8");
705
690
  }
706
691
  }
707
692
 
@@ -741,6 +726,7 @@ No tasks in progress.
741
726
  mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
742
727
  mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
743
728
  appendGitignore(cwd2);
729
+ writeSkills(cwd2, ".claude/skills");
744
730
  }
745
731
  async build(config, cwd2) {
746
732
  const write2 = (relPath, content) => {
@@ -761,10 +747,43 @@ No tasks in progress.
761
747
  mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
762
748
  mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
763
749
  mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
750
+ writeSkills(cwd2, ".claude/skills");
764
751
  }
765
752
  async migrate(config, _to, _cwd) {
766
753
  void config;
767
754
  }
755
+ async syncPermissions(cwd2) {
756
+ const AGENT_TOOLS = {
757
+ lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
758
+ explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
759
+ consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
760
+ builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
761
+ reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
762
+ };
763
+ for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
764
+ const filePath = join4(cwd2, ".claude", "agents", `${agent}.md`);
765
+ if (!existsSync3(filePath)) {
766
+ console.log(` ${agent}.md not found \u2014 skipping`);
767
+ continue;
768
+ }
769
+ const content = readFileSync4(filePath, "utf-8");
770
+ const updated = content.replace(
771
+ /(tools:\n)((?: - [^\n]+\n)*)/m,
772
+ (_match, header, toolsSection) => {
773
+ const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
774
+ const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
775
+ const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
776
+ return header + nativeSection + mcpSection;
777
+ }
778
+ );
779
+ if (updated === content) {
780
+ console.log(` ${agent}.md already in sync`);
781
+ } else {
782
+ writeFileSync4(filePath, updated, "utf-8");
783
+ console.log(` ${agent}.md updated`);
784
+ }
785
+ }
786
+ }
768
787
  };
769
788
 
770
789
  // src/core/materializer/codex-cli.ts
@@ -807,6 +826,7 @@ No tasks in progress.
807
826
  writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
808
827
  mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
809
828
  appendGitignore(cwd2);
829
+ writeSkills(cwd2, ".agents/skills");
810
830
  }
811
831
  async build(config, cwd2) {
812
832
  const write2 = (relPath, content) => {
@@ -825,10 +845,14 @@ No tasks in progress.
825
845
  writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
826
846
  writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
827
847
  mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
848
+ writeSkills(cwd2, ".agents/skills");
828
849
  }
829
850
  async migrate(config, _to, _cwd) {
830
851
  void config;
831
852
  }
853
+ async syncPermissions(_cwd) {
854
+ console.log(" Permissions sync not needed for codex-cli \u2014 skipping");
855
+ }
832
856
  };
833
857
 
834
858
  // src/core/materializer/opencode.ts
@@ -870,6 +894,7 @@ No tasks in progress.
870
894
  writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
871
895
  mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
872
896
  appendGitignore(cwd2);
897
+ writeSkills(cwd2, ".opencode/skills");
873
898
  }
874
899
  async build(config, cwd2) {
875
900
  const write2 = (relPath, content) => {
@@ -887,10 +912,14 @@ No tasks in progress.
887
912
  writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
888
913
  writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
889
914
  mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
915
+ writeSkills(cwd2, ".opencode/skills");
890
916
  }
891
917
  async migrate(config, _to, _cwd) {
892
918
  void config;
893
919
  }
920
+ async syncPermissions(_cwd) {
921
+ console.log(" Permissions sync not needed for opencode \u2014 skipping");
922
+ }
894
923
  };
895
924
 
896
925
  // src/core/materializer/index.ts
@@ -911,7 +940,10 @@ function getMaterializer(provider) {
911
940
  async function runBuild(cwd2, opts) {
912
941
  await buildOnce(cwd2);
913
942
  if (opts.sync) {
914
- await syncAgentPermissions(cwd2);
943
+ p.log.step("Syncing agent permissions...");
944
+ const config = await loadConfig(cwd2);
945
+ const materializer = getMaterializer(config.provider);
946
+ await materializer.syncPermissions(cwd2);
915
947
  }
916
948
  if (opts.watch) {
917
949
  p.log.info(`Watching agent-harness-kit.config.ts for changes...`);
@@ -945,13 +977,13 @@ async function buildOnce(cwd2) {
945
977
  }
946
978
 
947
979
  // src/commands/dashboard.ts
948
- import { dirname as dirname4, join as join9, resolve as resolve7 } from "path";
949
- import { fileURLToPath as fileURLToPath2 } from "url";
980
+ import { dirname as dirname5, join as join9, resolve as resolve7 } from "path";
981
+ import { fileURLToPath as fileURLToPath3 } from "url";
950
982
  import pc2 from "picocolors";
951
983
 
952
984
  // src/core/dashboard-server.ts
953
985
  import { watch as watch2 } from "fs";
954
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
986
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
955
987
  import { extname, join as join7 } from "path";
956
988
  import { serve } from "@hono/node-server";
957
989
  import { Hono } from "hono";
@@ -994,7 +1026,7 @@ var MIME = {
994
1026
  ".ttf": "font/ttf"
995
1027
  };
996
1028
  function fileResponse(filePath) {
997
- const content = readFileSync4(filePath);
1029
+ const content = readFileSync5(filePath);
998
1030
  const mime = MIME[extname(filePath)] ?? "application/octet-stream";
999
1031
  return new Response(content, {
1000
1032
  headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
@@ -1156,7 +1188,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
1156
1188
  // src/core/db.ts
1157
1189
  import { randomUUID } from "crypto";
1158
1190
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
1159
- import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
1191
+ import { dirname as dirname4, join as join8, resolve as resolve6 } from "path";
1160
1192
 
1161
1193
  // src/core/repositories/ActionRepository.ts
1162
1194
  var ActionRepository = class {
@@ -1639,7 +1671,7 @@ var HarnessDB = class {
1639
1671
  async regenerateCurrentMd() {
1640
1672
  if (!this.config.storage.markdownFallback.enabled) return;
1641
1673
  const mdPath = resolve6(this.config.storage.markdownFallback.path);
1642
- mkdirSync7(dirname3(mdPath), { recursive: true });
1674
+ mkdirSync7(dirname4(mdPath), { recursive: true });
1643
1675
  const inProgress = await this.tasks.getAll("in_progress");
1644
1676
  const now = (/* @__PURE__ */ new Date()).toISOString();
1645
1677
  let md = `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
@@ -1752,7 +1784,7 @@ var HarnessDB = class {
1752
1784
  }))
1753
1785
  );
1754
1786
  const path = join8(resolve6(cwd2), this.config.storage.dir, "feature_list.json");
1755
- mkdirSync7(dirname3(path), { recursive: true });
1787
+ mkdirSync7(dirname4(path), { recursive: true });
1756
1788
  writeFileSync7(path, JSON.stringify(list, null, 2) + "\n", "utf8");
1757
1789
  }
1758
1790
  };
@@ -1777,12 +1809,12 @@ async function openDB(config, cwd2) {
1777
1809
  }
1778
1810
 
1779
1811
  // src/commands/dashboard.ts
1780
- var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
1812
+ var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
1781
1813
  async function runDashboard(cwd2, opts) {
1782
1814
  const config = await loadConfig(cwd2);
1783
1815
  const db = await openDB(config, cwd2);
1784
1816
  const dbPath = config.database.type === "sqlite" ? resolve7(cwd2, config.database.path) : null;
1785
- const staticPath = join9(__dirname2, "dashboard-dist");
1817
+ const staticPath = join9(__dirname3, "dashboard-dist");
1786
1818
  const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
1787
1819
  console.log(pc2.green(`\u2713`) + ` Dashboard running at ${pc2.bold(pc2.cyan(url))}`);
1788
1820
  console.log(pc2.dim(` WebSocket live updates enabled`));
@@ -1798,12 +1830,255 @@ async function runDashboard(cwd2, opts) {
1798
1830
  });
1799
1831
  }
1800
1832
 
1833
+ // src/commands/doctor.ts
1834
+ import pc3 from "picocolors";
1835
+
1836
+ // src/core/doctor.ts
1837
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1838
+ import { dirname as dirname7, join as join11 } from "path";
1839
+ import { fileURLToPath as fileURLToPath5 } from "url";
1840
+
1841
+ // src/core/package-data.ts
1842
+ import { createRequire } from "module";
1843
+ import { dirname as dirname6, join as join10 } from "path";
1844
+ import { fileURLToPath as fileURLToPath4 } from "url";
1845
+ var require2 = createRequire(import.meta.url);
1846
+ var pkgPath = join10(dirname6(fileURLToPath4(import.meta.url)), "..", "package.json");
1847
+ var pkg = require2(pkgPath);
1848
+
1849
+ // src/core/doctor.ts
1850
+ var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
1851
+ var TIMEOUT_MS = 2e3;
1852
+ 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));
1855
+ async function checkLibVersion() {
1856
+ const current = pkg.version;
1857
+ try {
1858
+ const controller = new AbortController();
1859
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
1860
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
1861
+ clearTimeout(timer);
1862
+ const data = await res.json();
1863
+ const latest = data.version;
1864
+ const outdated = isNewer(latest, current);
1865
+ return { current, latest, outdated };
1866
+ } catch {
1867
+ return { current, latest: null, outdated: false };
1868
+ }
1869
+ }
1870
+ function isNewer(latest, current) {
1871
+ const toNum = (v4) => v4.split(".").map(Number);
1872
+ const [lMaj, lMin, lPat] = toNum(latest);
1873
+ const [cMaj, cMin, cPat] = toNum(current);
1874
+ if (lMaj !== cMaj) return lMaj > cMaj;
1875
+ if (lMin !== cMin) return lMin > cMin;
1876
+ return lPat > cPat;
1877
+ }
1878
+ function getProviderAgentInfo(provider) {
1879
+ switch (provider) {
1880
+ case "claude-code":
1881
+ return { agentsDir: ".claude/agents", ext: ".md" };
1882
+ case "opencode":
1883
+ return { agentsDir: ".opencode/agents", ext: ".md" };
1884
+ case "codex-cli":
1885
+ return { agentsDir: ".codex/agents", ext: ".toml" };
1886
+ default:
1887
+ return { agentsDir: ".claude/agents", ext: ".md" };
1888
+ }
1889
+ }
1890
+ function generateExpectedAgentContent(agentName, provider, vars) {
1891
+ const { projectName, allowedPaths, writablePaths } = vars;
1892
+ if (provider === "claude-code") {
1893
+ const templateFns = {
1894
+ lead: () => agentLead({ projectName }),
1895
+ explorer: () => agentExplorer({ projectName, allowedPaths }),
1896
+ consultant: () => agentConsultant({ projectName }),
1897
+ builder: () => agentBuilder({ projectName, writablePaths }),
1898
+ reviewer: () => agentReviewer({ projectName })
1899
+ };
1900
+ return translateFrontmatterForClaudeCode(templateFns[agentName](), agentName);
1901
+ }
1902
+ if (provider === "opencode") {
1903
+ const templateFns = {
1904
+ lead: () => agentLead({ projectName }),
1905
+ explorer: () => agentExplorer({ projectName, allowedPaths }),
1906
+ consultant: () => agentConsultant({ projectName }),
1907
+ builder: () => agentBuilder({ projectName, writablePaths }),
1908
+ reviewer: () => agentReviewer({ projectName })
1909
+ };
1910
+ return translateFrontmatterForOpenCode(templateFns[agentName]());
1911
+ }
1912
+ const tomlFns = {
1913
+ lead: () => agentLeadToml({ projectName }),
1914
+ explorer: () => agentExplorerToml({ projectName, allowedPaths }),
1915
+ consultant: () => agentConsultantToml({ projectName }),
1916
+ builder: () => agentBuilderToml({ projectName, writablePaths }),
1917
+ reviewer: () => agentReviewerToml({ projectName })
1918
+ };
1919
+ return tomlFns[agentName]();
1920
+ }
1921
+ function checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths) {
1922
+ const { agentsDir, ext } = getProviderAgentInfo(provider);
1923
+ return AGENT_NAMES.map((name) => {
1924
+ const filePath = join11(cwd2, agentsDir, `${name}${ext}`);
1925
+ if (!existsSync7(filePath)) {
1926
+ return { name, status: "missing" };
1927
+ }
1928
+ try {
1929
+ const live = readFileSync6(filePath, "utf8");
1930
+ const expected = generateExpectedAgentContent(name, provider, {
1931
+ projectName,
1932
+ allowedPaths,
1933
+ writablePaths
1934
+ });
1935
+ return { name, status: live === expected ? "ok" : "outdated" };
1936
+ } catch {
1937
+ return { name, status: "outdated" };
1938
+ }
1939
+ });
1940
+ }
1941
+ function getProviderSkillsDir(provider) {
1942
+ switch (provider) {
1943
+ case "claude-code":
1944
+ return ".claude/skills";
1945
+ case "opencode":
1946
+ return ".opencode/skills";
1947
+ case "codex-cli":
1948
+ return ".agents/skills";
1949
+ default:
1950
+ return ".claude/skills";
1951
+ }
1952
+ }
1953
+ function checkSkills(cwd2, provider) {
1954
+ const skillsDir = getProviderSkillsDir(provider);
1955
+ const skillSourceBase = join11(__dirname4, "materializer", "skills");
1956
+ return SKILL_NAMES.map((name) => {
1957
+ const livePath = join11(cwd2, skillsDir, name, "SKILL.md");
1958
+ const sourcePath = join11(skillSourceBase, name, "SKILL.md");
1959
+ if (!existsSync7(livePath)) {
1960
+ return { name, status: "missing" };
1961
+ }
1962
+ try {
1963
+ const live = readFileSync6(livePath, "utf8");
1964
+ const source = readFileSync6(sourcePath, "utf8");
1965
+ return { name, status: live === source ? "ok" : "outdated" };
1966
+ } catch {
1967
+ return { name, status: "outdated" };
1968
+ }
1969
+ });
1970
+ }
1971
+ async function getDoctorStatus(cwd2) {
1972
+ const lib = await checkLibVersion();
1973
+ let config;
1974
+ try {
1975
+ config = await loadConfig(cwd2);
1976
+ } catch {
1977
+ return {
1978
+ lib,
1979
+ agents: [],
1980
+ skills: []
1981
+ };
1982
+ }
1983
+ const provider = config.provider;
1984
+ const projectName = config.project.name;
1985
+ const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
1986
+ const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
1987
+ const agents = checkAgentFiles(cwd2, provider, projectName, allowedPaths, writablePaths);
1988
+ const skills = checkSkills(cwd2, provider);
1989
+ return { lib, agents, skills };
1990
+ }
1991
+
1992
+ // src/commands/doctor.ts
1993
+ function ok(label, detail) {
1994
+ console.log(` ${pc3.cyan(label.padEnd(16))}${pc3.green("[\u2713]")} ${detail}`);
1995
+ }
1996
+ function warn(label, detail, hint) {
1997
+ console.log(` ${pc3.cyan(label.padEnd(16))}${pc3.yellow("[!]")} ${detail}`);
1998
+ if (hint) {
1999
+ console.log(` ${"".padEnd(16)} ${pc3.dim(hint)}`);
2000
+ }
2001
+ }
2002
+ function neutral(label, detail) {
2003
+ console.log(` ${pc3.cyan(label.padEnd(16))}${pc3.dim("[~]")} ${detail}`);
2004
+ }
2005
+ function printLibSection(lib) {
2006
+ if (lib.latest === null) {
2007
+ neutral("lib version", `${lib.current} (unknown \u2014 could not reach npm registry)`);
2008
+ } else if (lib.outdated) {
2009
+ warn(
2010
+ "lib version",
2011
+ `${lib.current} \u2192 ${lib.latest} available`,
2012
+ `run: npm i @cardor/agent-harness-kit@latest && ahk build`
2013
+ );
2014
+ } else {
2015
+ ok("lib version", `${lib.current} (up to date)`);
2016
+ }
2017
+ }
2018
+ function printAgentsSection(agents) {
2019
+ if (agents.length === 0) {
2020
+ warn("agent files", "no config found \u2014 run: ahk init");
2021
+ return;
2022
+ }
2023
+ const missing = agents.filter((a) => a.status === "missing");
2024
+ const outdated = agents.filter((a) => a.status === "outdated");
2025
+ if (missing.length === 0 && outdated.length === 0) {
2026
+ ok("agent files", "all up to date");
2027
+ return;
2028
+ }
2029
+ for (const agent of missing) {
2030
+ warn("agent files", `${agent.name} missing`, "run: ahk build");
2031
+ }
2032
+ for (const agent of outdated) {
2033
+ warn("agent files", `${agent.name} outdated`, "run: ahk build");
2034
+ }
2035
+ }
2036
+ function printSkillsSection(skills) {
2037
+ if (skills.length === 0) {
2038
+ warn("harness skills", "no config found \u2014 run: ahk init");
2039
+ return;
2040
+ }
2041
+ const issues = skills.filter((s) => s.status !== "ok");
2042
+ if (issues.length === 0) {
2043
+ ok("harness skills", "all present and up to date");
2044
+ return;
2045
+ }
2046
+ for (const skill of issues) {
2047
+ const detail = skill.status === "missing" ? `${skill.name} missing` : `${skill.name} outdated`;
2048
+ warn("harness skills", detail, "run: ahk build");
2049
+ }
2050
+ }
2051
+ async function runDoctor(cwd2) {
2052
+ let configFound = true;
2053
+ try {
2054
+ await loadConfig(cwd2);
2055
+ } catch {
2056
+ configFound = false;
2057
+ }
2058
+ console.log("");
2059
+ console.log(pc3.bold(`\u25CF ahk doctor ` + "\u2500".repeat(44)));
2060
+ console.log("");
2061
+ const status = await getDoctorStatus(cwd2);
2062
+ printLibSection(status.lib);
2063
+ if (!configFound) {
2064
+ console.log("");
2065
+ warn("config", "no agent-harness-kit.config found", "run: ahk init");
2066
+ console.log("");
2067
+ return;
2068
+ }
2069
+ console.log("");
2070
+ printAgentsSection(status.agents);
2071
+ console.log("");
2072
+ printSkillsSection(status.skills);
2073
+ console.log("");
2074
+ }
2075
+
1801
2076
  // src/commands/export.ts
1802
2077
  import { writeFileSync as writeFileSync8 } from "fs";
1803
- import pc3 from "picocolors";
2078
+ import pc4 from "picocolors";
1804
2079
  async function runExport(cwd2, opts) {
1805
2080
  if (!opts.sql && !opts.json) {
1806
- console.error(pc3.red("Specify --sql or --json"));
2081
+ console.error(pc4.red("Specify --sql or --json"));
1807
2082
  process.exit(1);
1808
2083
  }
1809
2084
  const config = await loadConfig(cwd2);
@@ -1814,13 +2089,13 @@ async function runExport(cwd2, opts) {
1814
2089
  const out = JSON.stringify(data, null, 2) + "\n";
1815
2090
  if (opts.output) {
1816
2091
  writeFileSync8(opts.output, out, "utf8");
1817
- console.log(pc3.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
2092
+ console.log(pc4.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
1818
2093
  } else {
1819
2094
  process.stdout.write(out);
1820
2095
  }
1821
2096
  }
1822
2097
  if (opts.sql) {
1823
- console.error(pc3.dim("SQL dump requires direct SQLite access \u2014 use: sqlite3 .harness/harness.db .dump"));
2098
+ console.error(pc4.dim("SQL dump requires direct SQLite access \u2014 use: sqlite3 .harness/harness.db .dump"));
1824
2099
  process.exit(1);
1825
2100
  }
1826
2101
  } finally {
@@ -1830,27 +2105,27 @@ async function runExport(cwd2, opts) {
1830
2105
 
1831
2106
  // src/commands/health.ts
1832
2107
  import { spawnSync } from "child_process";
1833
- import { existsSync as existsSync7 } from "fs";
1834
- import { join as join10, resolve as resolve8 } from "path";
1835
- import pc4 from "picocolors";
1836
- function checkLine(label, ok2, message, indent = 0) {
1837
- const prefix = label ? pc4.cyan(`[${label}] `) : " ".repeat(indent);
1838
- const icon = ok2 ? pc4.green("\u2713") : pc4.red("\u2717");
1839
- console.log(prefix + icon + " " + (ok2 ? pc4.green(message) : pc4.red(message)));
2108
+ import { existsSync as existsSync8 } from "fs";
2109
+ import { join as join12, resolve as resolve8 } from "path";
2110
+ import pc5 from "picocolors";
2111
+ function checkLine(label, ok3, message, indent = 0) {
2112
+ const prefix = label ? pc5.cyan(`[${label}] `) : " ".repeat(indent);
2113
+ const icon = ok3 ? pc5.green("\u2713") : pc5.red("\u2717");
2114
+ console.log(prefix + icon + " " + (ok3 ? pc5.green(message) : pc5.red(message)));
1840
2115
  }
1841
2116
  async function runHealth(cwd2) {
1842
2117
  let config;
1843
2118
  try {
1844
2119
  config = await loadConfig(cwd2);
1845
2120
  } catch {
1846
- console.error(pc4.red("\u2717 No config found. Run: ahk init"));
2121
+ console.error(pc5.red("\u2717 No config found. Run: ahk init"));
1847
2122
  process.exit(1);
1848
2123
  }
1849
2124
  let allOk = true;
1850
2125
  let dbOk;
1851
2126
  if (config.database.type === "sqlite") {
1852
2127
  const dbPath = resolve8(cwd2, config.database.path);
1853
- dbOk = existsSync7(dbPath);
2128
+ dbOk = existsSync8(dbPath);
1854
2129
  checkLine("checking DB", dbOk, `${config.database.path} reachable`);
1855
2130
  } else {
1856
2131
  dbOk = true;
@@ -1863,31 +2138,31 @@ async function runHealth(cwd2) {
1863
2138
  const agentsLabelWidth = "[checking agents] ".length;
1864
2139
  for (let i = 0; i < agentNames.length; i++) {
1865
2140
  const name = agentNames[i];
1866
- const agentPath = join10(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
1867
- const ok2 = existsSync7(agentPath);
2141
+ const agentPath = join12(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
2142
+ const ok3 = existsSync8(agentPath);
1868
2143
  checkLine(
1869
2144
  i === 0 ? "checking agents" : null,
1870
- ok2,
2145
+ ok3,
1871
2146
  `${name}${providerFiles.agentExtension} present`,
1872
2147
  agentsLabelWidth
1873
2148
  );
1874
- if (!ok2) allOk = false;
2149
+ if (!ok3) allOk = false;
1875
2150
  }
1876
2151
  if (config.tools.mcp.enabled) {
1877
2152
  const mcpFile = providerFiles.mcpFile;
1878
2153
  const mcpPath = resolve8(cwd2, mcpFile);
1879
- const mcpOk = existsSync7(mcpPath);
2154
+ const mcpOk = existsSync8(mcpPath);
1880
2155
  checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
1881
2156
  if (!mcpOk) allOk = false;
1882
2157
  }
1883
2158
  if (!allOk) {
1884
2159
  console.log("");
1885
- console.error(pc4.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
2160
+ console.error(pc5.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
1886
2161
  process.exit(1);
1887
2162
  }
1888
2163
  const scriptPath = resolve8(cwd2, config.health.scriptPath);
1889
- if (!existsSync7(scriptPath)) {
1890
- console.error(pc4.red(`\u2717 health.sh not found: ${scriptPath}`));
2164
+ if (!existsSync8(scriptPath)) {
2165
+ console.error(pc5.red(`\u2717 health.sh not found: ${scriptPath}`));
1891
2166
  console.error(" Run ahk init first.");
1892
2167
  process.exit(1);
1893
2168
  }
@@ -1897,14 +2172,14 @@ async function runHealth(cwd2) {
1897
2172
  encoding: "utf8"
1898
2173
  });
1899
2174
  if (result.error) {
1900
- console.error(pc4.red(`\u2717 Failed to run health.sh: ${result.error.message}`));
2175
+ console.error(pc5.red(`\u2717 Failed to run health.sh: ${result.error.message}`));
1901
2176
  process.exit(1);
1902
2177
  }
1903
2178
  if (result.status === 0) {
1904
- console.log(pc4.green("\u2713 Health check passed"));
2179
+ console.log(pc5.green("\u2713 Health check passed"));
1905
2180
  process.exit(0);
1906
2181
  } else {
1907
- console.error(pc4.red(`\u2717 Health check failed (exit ${result.status ?? "unknown"})`));
2182
+ console.error(pc5.red(`\u2717 Health check failed (exit ${result.status ?? "unknown"})`));
1908
2183
  process.exit(result.status ?? 1);
1909
2184
  }
1910
2185
  }
@@ -1923,9 +2198,9 @@ function getProviderHealthFiles(provider) {
1923
2198
 
1924
2199
  // src/commands/init.ts
1925
2200
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
1926
- import { join as join12 } from "path";
2201
+ import { join as join14 } from "path";
1927
2202
  import * as p3 from "@clack/prompts";
1928
- import pc6 from "picocolors";
2203
+ import pc7 from "picocolors";
1929
2204
 
1930
2205
  // src/schema/init.ts
1931
2206
  import * as v from "valibot";
@@ -1978,14 +2253,14 @@ var cliFormWithRetry = async (formFn, schema) => {
1978
2253
  };
1979
2254
 
1980
2255
  // src/commands/init-helpers.ts
1981
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
1982
- import { join as join11 } from "path";
1983
- import pc5 from "picocolors";
2256
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2257
+ import { join as join13 } from "path";
2258
+ import pc6 from "picocolors";
1984
2259
  function readProjectNameFromPackageJson(cwd2) {
1985
2260
  try {
1986
- const pkgPath2 = join11(cwd2, "package.json");
1987
- if (!existsSync8(pkgPath2)) return null;
1988
- const content = readFileSync5(pkgPath2, "utf8");
2261
+ const pkgPath2 = join13(cwd2, "package.json");
2262
+ if (!existsSync9(pkgPath2)) return null;
2263
+ const content = readFileSync7(pkgPath2, "utf8");
1989
2264
  const pkg2 = JSON.parse(content);
1990
2265
  const name = pkg2?.name;
1991
2266
  if (typeof name === "string" && name.trim()) return name.trim();
@@ -1996,10 +2271,10 @@ function readProjectNameFromPackageJson(cwd2) {
1996
2271
  }
1997
2272
  function detectConfigExtension(cwd2) {
1998
2273
  try {
1999
- if (existsSync8(join11(cwd2, "tsconfig.json"))) return "ts";
2000
- const pkgPath2 = join11(cwd2, "package.json");
2001
- if (!existsSync8(pkgPath2)) return "mjs";
2002
- const pkg2 = JSON.parse(readFileSync5(pkgPath2, "utf8"));
2274
+ if (existsSync9(join13(cwd2, "tsconfig.json"))) return "ts";
2275
+ const pkgPath2 = join13(cwd2, "package.json");
2276
+ if (!existsSync9(pkgPath2)) return "mjs";
2277
+ const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2003
2278
  if (pkg2?.type === "module") return "mjs";
2004
2279
  } catch {
2005
2280
  }
@@ -2050,27 +2325,27 @@ function stripAnsi(str2) {
2050
2325
  function drawBox(lines) {
2051
2326
  const width = Math.max(...lines.map((l) => stripAnsi(l).length));
2052
2327
  const border = "\u2500".repeat(width);
2053
- console.log(pc5.yellow(`\u250C${border}\u2510`));
2328
+ console.log(pc6.yellow(`\u250C${border}\u2510`));
2054
2329
  for (const line of lines) {
2055
2330
  const pad = width - stripAnsi(line).length;
2056
2331
  const padStr = pad > 0 ? " ".repeat(pad) : "";
2057
- console.log(pc5.yellow("\u2502") + line + padStr + pc5.yellow("\u2502"));
2332
+ console.log(pc6.yellow("\u2502") + line + padStr + pc6.yellow("\u2502"));
2058
2333
  }
2059
- console.log(pc5.yellow(`\u2514${border}\u2518`));
2334
+ console.log(pc6.yellow(`\u2514${border}\u2518`));
2060
2335
  }
2061
2336
  function printWelcomeMessage(projectName) {
2062
2337
  const sep = "\u2500".repeat(38);
2063
2338
  const lines = [
2064
- ` ${pc5.bold(pc5.white("agent-harness-kit"))} `,
2065
- ` ${pc5.gray("\u2014")} harness scaffolding ${pc5.gray("\u2014")} `,
2066
- ` ${pc5.gray(sep)} `,
2067
- ` ${pc5.bold("Project:")} ${projectName || "\u2014"} `,
2068
- ` ${pc5.bold("Status:")} ${pc5.green("ready to configure")} `,
2069
- ` ${pc5.gray(sep)} `,
2070
- ` ${pc5.gray("Next steps:")} `,
2071
- ` ${pc5.gray("\u2192")} ${pc5.gray("Set up your AI provider config")} `,
2072
- ` ${pc5.gray("\u2192")} ${pc5.gray("Run your health check to verify")} `,
2073
- ` ${pc5.gray("\u2192")} ${pc5.gray("Start adding tasks for your agents")} `
2339
+ ` ${pc6.bold(pc6.white("agent-harness-kit"))} `,
2340
+ ` ${pc6.gray("\u2014")} harness scaffolding ${pc6.gray("\u2014")} `,
2341
+ ` ${pc6.gray(sep)} `,
2342
+ ` ${pc6.bold("Project:")} ${projectName || "\u2014"} `,
2343
+ ` ${pc6.bold("Status:")} ${pc6.green("ready to configure")} `,
2344
+ ` ${pc6.gray(sep)} `,
2345
+ ` ${pc6.gray("Next steps:")} `,
2346
+ ` ${pc6.gray("\u2192")} ${pc6.gray("Set up your AI provider config")} `,
2347
+ ` ${pc6.gray("\u2192")} ${pc6.gray("Run your health check to verify")} `,
2348
+ ` ${pc6.gray("\u2192")} ${pc6.gray("Start adding tasks for your agents")} `
2074
2349
  ];
2075
2350
  console.log();
2076
2351
  drawBox(lines);
@@ -2082,18 +2357,18 @@ async function runInit(cwd2, flags) {
2082
2357
  const existingConfig = findConfigFile(cwd2);
2083
2358
  if (existingConfig) {
2084
2359
  console.log(
2085
- pc6.yellow("\u26A0") + " " + pc6.bold("Project already initialized.") + pc6.dim(` (${existingConfig})`)
2360
+ pc7.yellow("\u26A0") + " " + pc7.bold("Project already initialized.") + pc7.dim(` (${existingConfig})`)
2086
2361
  );
2087
2362
  console.log();
2088
- console.log(pc6.dim("Suggested next steps:"));
2363
+ console.log(pc7.dim("Suggested next steps:"));
2089
2364
  console.log(
2090
- " " + pc6.cyan("ahk build") + pc6.dim(" \u2014 re-sync agent files after updating the library")
2365
+ " " + pc7.cyan("ahk build") + pc7.dim(" \u2014 re-sync agent files after updating the library")
2091
2366
  );
2092
- console.log(" " + pc6.cyan("ahk build --sync") + pc6.dim(" \u2014 also sync agent permissions"));
2367
+ console.log(" " + pc7.cyan("ahk build --sync") + pc7.dim(" \u2014 also sync agent permissions"));
2093
2368
  console.log(
2094
- " " + pc6.cyan("ahk reset") + pc6.dim(" \u2014 wipe and re-initialize from scratch")
2369
+ " " + pc7.cyan("ahk reset") + pc7.dim(" \u2014 wipe and re-initialize from scratch")
2095
2370
  );
2096
- console.log(" " + pc6.cyan("ahk dashboard") + pc6.dim(" \u2014 open the harness dashboard"));
2371
+ console.log(" " + pc7.cyan("ahk dashboard") + pc7.dim(" \u2014 open the harness dashboard"));
2097
2372
  process.exit(0);
2098
2373
  }
2099
2374
  const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
@@ -2232,8 +2507,8 @@ async function runInit(cwd2, flags) {
2232
2507
  tasksAdapter,
2233
2508
  port: config.tools.mcp.port
2234
2509
  });
2235
- writeFileSync9(join12(installDir, configFileName), configContent, "utf8");
2236
- mkdirSync8(join12(installDir, config.storage.dir), { recursive: true });
2510
+ writeFileSync9(join14(installDir, configFileName), configContent, "utf8");
2511
+ mkdirSync8(join14(installDir, config.storage.dir), { recursive: true });
2237
2512
  const db = await openDB(config, installDir);
2238
2513
  await materializer.scaffold(config, { cwd: installDir, firstTask });
2239
2514
  if (firstTask) {
@@ -2252,30 +2527,30 @@ async function runInit(cwd2, flags) {
2252
2527
  p3.log.error(err instanceof Error ? err.message : String(err));
2253
2528
  throw err;
2254
2529
  }
2255
- console.log(pc6.green("\u2713 Scaffolded harness in current directory"));
2530
+ console.log(pc7.green("\u2713 Scaffolded harness in current directory"));
2256
2531
  const agentsDir = provider === "claude-code" ? ".claude/agents/" : ".opencode/agents/";
2257
2532
  const mcpFile = provider === "claude-code" ? ".claude/mcp.json" : "./opencode.json";
2258
2533
  console.log("");
2259
- console.log(pc6.green(`\u2713 agent-harness-kit.config.${configExt}`));
2260
- console.log(pc6.green("\u2713 AGENTS.md"));
2261
- console.log(pc6.green("\u2713 health.sh"));
2262
- console.log(pc6.green("\u2713 .harness/harness.db"));
2263
- console.log(pc6.green("\u2713 .harness/current.md"));
2264
- console.log(pc6.green(`\u2713 ${agentsDir}lead.md`));
2265
- console.log(pc6.green(`\u2713 ${agentsDir}explorer.md`));
2266
- console.log(pc6.green(`\u2713 ${agentsDir}builder.md`));
2267
- console.log(pc6.green(`\u2713 ${agentsDir}reviewer.md`));
2268
- console.log(pc6.green(`\u2713 ${mcpFile}`));
2269
- console.log(pc6.green("\u2713 .gitignore entries added"));
2534
+ console.log(pc7.green(`\u2713 agent-harness-kit.config.${configExt}`));
2535
+ console.log(pc7.green("\u2713 AGENTS.md"));
2536
+ 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"));
2539
+ console.log(pc7.green(`\u2713 ${agentsDir}lead.md`));
2540
+ console.log(pc7.green(`\u2713 ${agentsDir}explorer.md`));
2541
+ console.log(pc7.green(`\u2713 ${agentsDir}builder.md`));
2542
+ console.log(pc7.green(`\u2713 ${agentsDir}reviewer.md`));
2543
+ console.log(pc7.green(`\u2713 ${mcpFile}`));
2544
+ console.log(pc7.green("\u2713 .gitignore entries added"));
2270
2545
  console.log("");
2271
- console.log(pc6.cyan("\u2192") + ` Edit ${pc6.cyan("health.sh")} with your project checks`);
2272
- console.log(pc6.cyan("\u2192") + ` ${pc6.cyan("ahk task add")} to queue work for agents`);
2546
+ console.log(pc7.cyan("\u2192") + ` Edit ${pc7.cyan("health.sh")} with your project checks`);
2547
+ console.log(pc7.cyan("\u2192") + ` ${pc7.cyan("ahk task add")} to queue work for agents`);
2273
2548
  console.log(
2274
- pc6.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc6.cyan("https://github.com/safishamsi/graphify")}`
2549
+ pc7.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc7.cyan("https://github.com/safishamsi/graphify")}`
2275
2550
  );
2276
2551
  const recommendations = [
2277
2552
  ` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
2278
- ` Learn more: ${pc6.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
2553
+ ` Learn more: ${pc7.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
2279
2554
  ];
2280
2555
  console.log("");
2281
2556
  drawBox(recommendations);
@@ -2283,7 +2558,7 @@ async function runInit(cwd2, flags) {
2283
2558
 
2284
2559
  // src/commands/migrate.ts
2285
2560
  import * as p4 from "@clack/prompts";
2286
- import pc7 from "picocolors";
2561
+ import pc8 from "picocolors";
2287
2562
  async function runMigrate(cwd2, opts) {
2288
2563
  const config = await loadConfig(cwd2);
2289
2564
  let target;
@@ -2305,7 +2580,7 @@ async function runMigrate(cwd2, opts) {
2305
2580
  target = val;
2306
2581
  }
2307
2582
  if (target === config.provider) {
2308
- console.log(pc7.dim(`Already on ${target} \u2014 nothing to migrate.`));
2583
+ console.log(pc8.dim(`Already on ${target} \u2014 nothing to migrate.`));
2309
2584
  return;
2310
2585
  }
2311
2586
  const spinner6 = p4.spinner();
@@ -2313,27 +2588,27 @@ async function runMigrate(cwd2, opts) {
2313
2588
  try {
2314
2589
  const targetMaterializer = getMaterializer(target);
2315
2590
  await targetMaterializer.build(config, cwd2);
2316
- spinner6.stop(pc7.green(`Migrated to ${target}`));
2591
+ spinner6.stop(pc8.green(`Migrated to ${target}`));
2317
2592
  p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
2318
2593
  p4.log.warn(`Then run: ahk build`);
2319
2594
  } catch (err) {
2320
- spinner6.stop(pc7.red("Migration failed"));
2595
+ spinner6.stop(pc8.red("Migration failed"));
2321
2596
  p4.log.error(err instanceof Error ? err.message : String(err));
2322
2597
  process.exit(1);
2323
2598
  }
2324
2599
  }
2325
2600
 
2326
2601
  // src/commands/reset.ts
2327
- import { existsSync as existsSync9, readdirSync, rmSync } from "fs";
2328
- import { join as join13, resolve as resolve9 } from "path";
2602
+ import { existsSync as existsSync10, readdirSync, rmSync } from "fs";
2603
+ import { join as join15, resolve as resolve9 } from "path";
2329
2604
  import * as p5 from "@clack/prompts";
2330
- import pc8 from "picocolors";
2605
+ import pc9 from "picocolors";
2331
2606
  var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
2332
2607
  async function resetAgentMds(cwd2, provider) {
2333
2608
  const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
2334
2609
  const agentDirPath = resolve9(cwd2, agentDir);
2335
- if (!existsSync9(agentDirPath)) {
2336
- console.log(pc8.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2610
+ if (!existsSync10(agentDirPath)) {
2611
+ console.log(pc9.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2337
2612
  return;
2338
2613
  }
2339
2614
  const existingFiles = [];
@@ -2345,11 +2620,11 @@ async function resetAgentMds(cwd2, provider) {
2345
2620
  }
2346
2621
  }
2347
2622
  } catch {
2348
- console.log(pc8.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2623
+ console.log(pc9.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2349
2624
  return;
2350
2625
  }
2351
2626
  if (existingFiles.length === 0) {
2352
- console.log(pc8.yellow(` No agent MD files found in ${agentDir}/`));
2627
+ console.log(pc9.yellow(` No agent MD files found in ${agentDir}/`));
2353
2628
  return;
2354
2629
  }
2355
2630
  for (const file of existingFiles) {
@@ -2358,19 +2633,19 @@ async function resetAgentMds(cwd2, provider) {
2358
2633
  initialValue: true
2359
2634
  });
2360
2635
  if (p5.isCancel(confirm3)) {
2361
- console.log(pc8.red(" Cancelled by user."));
2636
+ console.log(pc9.red(" Cancelled by user."));
2362
2637
  return;
2363
2638
  }
2364
2639
  if (confirm3) {
2365
2640
  try {
2366
- const filePath = join13(agentDirPath, file);
2641
+ const filePath = join15(agentDirPath, file);
2367
2642
  rmSync(filePath, { force: true });
2368
- console.log(pc8.green(` Removed ${file}`));
2643
+ console.log(pc9.green(` Removed ${file}`));
2369
2644
  } catch {
2370
- console.error(pc8.red(` Failed to remove ${file}`));
2645
+ console.error(pc9.red(` Failed to remove ${file}`));
2371
2646
  }
2372
2647
  } else {
2373
- console.log(pc8.cyan(` Skipped ${file}`));
2648
+ console.log(pc9.cyan(` Skipped ${file}`));
2374
2649
  }
2375
2650
  }
2376
2651
  }
@@ -2379,7 +2654,7 @@ async function runReset(cwd2, opts) {
2379
2654
  try {
2380
2655
  config = await loadConfig(cwd2);
2381
2656
  } catch {
2382
- console.error(pc8.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2657
+ console.error(pc9.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2383
2658
  process.exit(1);
2384
2659
  }
2385
2660
  const storageDir = config.storage.dir || ".harness";
@@ -2388,12 +2663,12 @@ async function runReset(cwd2, opts) {
2388
2663
  let resetDb = false;
2389
2664
  let resetFeatureList = false;
2390
2665
  let resetAgentMdsFlag = false;
2391
- if (dbPath && existsSync9(dbPath)) {
2666
+ if (dbPath && existsSync10(dbPath)) {
2392
2667
  if (opts.force) {
2393
2668
  resetDb = true;
2394
2669
  } else {
2395
2670
  if (config.database.type !== "sqlite") {
2396
- console.log(pc8.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2671
+ console.log(pc9.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2397
2672
  resetDb = false;
2398
2673
  } else {
2399
2674
  const confirm3 = await p5.confirm({
@@ -2401,16 +2676,16 @@ async function runReset(cwd2, opts) {
2401
2676
  initialValue: true
2402
2677
  });
2403
2678
  if (p5.isCancel(confirm3)) {
2404
- console.log(pc8.red(" Cancelled by user."));
2679
+ console.log(pc9.red(" Cancelled by user."));
2405
2680
  return;
2406
2681
  }
2407
2682
  resetDb = confirm3;
2408
2683
  }
2409
2684
  }
2410
2685
  } else if (!dbPath) {
2411
- console.log(pc8.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2686
+ console.log(pc9.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2412
2687
  }
2413
- if (existsSync9(featureListPath)) {
2688
+ if (existsSync10(featureListPath)) {
2414
2689
  if (opts.force) {
2415
2690
  resetFeatureList = true;
2416
2691
  } else {
@@ -2419,7 +2694,7 @@ async function runReset(cwd2, opts) {
2419
2694
  initialValue: true
2420
2695
  });
2421
2696
  if (p5.isCancel(confirm3)) {
2422
- console.log(pc8.red(" Cancelled by user."));
2697
+ console.log(pc9.red(" Cancelled by user."));
2423
2698
  return;
2424
2699
  }
2425
2700
  resetFeatureList = confirm3;
@@ -2433,17 +2708,17 @@ async function runReset(cwd2, opts) {
2433
2708
  rmSync(dbPath, { force: true });
2434
2709
  rmSync(`${dbPath}-wal`, { force: true });
2435
2710
  rmSync(`${dbPath}-shm`, { force: true });
2436
- console.log(pc8.green(` \u2713 Removed ${dbPath}`));
2711
+ console.log(pc9.green(` \u2713 Removed ${dbPath}`));
2437
2712
  } catch {
2438
- console.error(pc8.red(` \u2717 Failed to remove ${dbPath}`));
2713
+ console.error(pc9.red(` \u2717 Failed to remove ${dbPath}`));
2439
2714
  }
2440
2715
  }
2441
2716
  if (resetFeatureList) {
2442
2717
  try {
2443
2718
  rmSync(featureListPath, { force: true });
2444
- console.log(pc8.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2719
+ console.log(pc9.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2445
2720
  } catch {
2446
- console.error(pc8.red(` \u2717 Failed to remove ${featureListPath}`));
2721
+ console.error(pc9.red(` \u2717 Failed to remove ${featureListPath}`));
2447
2722
  }
2448
2723
  }
2449
2724
  if (resetAgentMdsFlag) {
@@ -2451,16 +2726,16 @@ async function runReset(cwd2, opts) {
2451
2726
  await resetAgentMds(cwd2, opts.provider || "claude-code");
2452
2727
  }
2453
2728
  if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
2454
- console.log(pc8.yellow(" Nothing to reset (all items missing or skipped)."));
2729
+ console.log(pc9.yellow(" Nothing to reset (all items missing or skipped)."));
2455
2730
  return;
2456
2731
  }
2457
2732
  console.log("");
2458
- console.log(pc8.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2733
+ console.log(pc9.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2459
2734
  }
2460
2735
 
2461
2736
  // src/core/mcp-server.ts
2462
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync10 } from "fs";
2463
- import { join as join15, resolve as resolve10 } from "path";
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";
2464
2739
  import { Server } from "@modelcontextprotocol/sdk/server";
2465
2740
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2466
2741
  import {
@@ -2469,8 +2744,8 @@ import {
2469
2744
  } from "@modelcontextprotocol/sdk/types.js";
2470
2745
 
2471
2746
  // src/core/permissions-check.ts
2472
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
2473
- import { join as join14 } from "path";
2747
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2748
+ import { join as join16 } from "path";
2474
2749
  var CANONICAL = {
2475
2750
  lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
2476
2751
  explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
@@ -2486,25 +2761,28 @@ function parseToolsFromFrontmatter(content) {
2486
2761
  if (!toolsMatch) return [];
2487
2762
  return toolsMatch[1].split("\n").map((l) => l.trim().replace(/^- /, "")).filter((l) => l.startsWith("mcp__"));
2488
2763
  }
2489
- function checkPermissionsSync(cwd2) {
2764
+ function checkPermissionsSync(cwd2, config) {
2765
+ if (config.provider !== "claude-code") {
2766
+ return { in_sync: true };
2767
+ }
2490
2768
  const agents = {};
2491
2769
  let in_sync = true;
2492
2770
  for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
2493
- const filePath = join14(cwd2, ".claude", "agents", `${agent}.md`);
2494
- if (!existsSync10(filePath)) {
2771
+ const filePath = join16(cwd2, ".claude", "agents", `${agent}.md`);
2772
+ if (!existsSync11(filePath)) {
2495
2773
  const missing2 = CANONICAL[agent];
2496
2774
  agents[agent] = { ok: false, missing: missing2, extra: [] };
2497
2775
  in_sync = false;
2498
2776
  continue;
2499
2777
  }
2500
- const content = readFileSync6(filePath, "utf-8");
2778
+ const content = readFileSync8(filePath, "utf-8");
2501
2779
  const installed = parseToolsFromFrontmatter(content);
2502
2780
  const canonical = CANONICAL[agent];
2503
2781
  const missing = canonical.filter((t) => !installed.includes(t));
2504
2782
  const extra = installed.filter((t) => !canonical.includes(t));
2505
- const ok2 = missing.length === 0 && extra.length === 0;
2506
- if (!ok2) in_sync = false;
2507
- agents[agent] = { ok: ok2, missing, extra };
2783
+ const ok3 = missing.length === 0 && extra.length === 0;
2784
+ if (!ok3) in_sync = false;
2785
+ agents[agent] = { ok: ok3, missing, extra };
2508
2786
  }
2509
2787
  return { in_sync, agents };
2510
2788
  }
@@ -2646,7 +2924,10 @@ var TOOLS = [
2646
2924
  inputSchema: {
2647
2925
  type: "object",
2648
2926
  properties: {
2649
- criterionId: { type: "number", description: "The id of the acceptance criterion to mark as met" }
2927
+ criterionId: {
2928
+ type: "number",
2929
+ description: "The id of the acceptance criterion to mark as met"
2930
+ }
2650
2931
  },
2651
2932
  required: ["criterionId"]
2652
2933
  }
@@ -2669,7 +2950,10 @@ var TOOLS = [
2669
2950
  type: "object",
2670
2951
  properties: {
2671
2952
  title: { type: "string", description: "Short human-readable title for the task" },
2672
- slug: { type: "string", description: "URL-safe identifier (lowercase, hyphens). Auto-derived from title if omitted." },
2953
+ slug: {
2954
+ type: "string",
2955
+ description: "URL-safe identifier (lowercase, hyphens). Auto-derived from title if omitted."
2956
+ },
2673
2957
  description: { type: "string", description: "Longer description of the task goal" },
2674
2958
  acceptance: {
2675
2959
  type: "array",
@@ -2687,8 +2971,14 @@ var TOOLS = [
2687
2971
  type: "object",
2688
2972
  properties: {
2689
2973
  actionId: { type: "string", description: "UUID returned by actions.start" },
2690
- toolName: { type: "string", description: "Name of the tool that was called (e.g. Read, Bash, Edit)" },
2691
- argsJson: { type: "string", description: "Optional JSON string of the arguments passed to the tool" },
2974
+ toolName: {
2975
+ type: "string",
2976
+ description: "Name of the tool that was called (e.g. Read, Bash, Edit)"
2977
+ },
2978
+ argsJson: {
2979
+ type: "string",
2980
+ description: "Optional JSON string of the arguments passed to the tool"
2981
+ },
2692
2982
  resultSummary: { type: "string", description: "Optional short summary of the tool result" }
2693
2983
  },
2694
2984
  required: ["actionId", "toolName"]
@@ -2748,6 +3038,11 @@ var TOOLS = [
2748
3038
  name: "deps.check",
2749
3039
  description: "Compare current package.json against .harness/deps-lock.json and report changes",
2750
3040
  inputSchema: { type: "object", properties: {}, required: [] }
3041
+ },
3042
+ {
3043
+ name: "ahk.doctor",
3044
+ description: "Check lib version, agent files, and harness skills sync status. Returns structured JSON.",
3045
+ inputSchema: { type: "object", properties: {}, required: [] }
2751
3046
  }
2752
3047
  ];
2753
3048
  async function startMcpServer(config, cwd2) {
@@ -2762,35 +3057,37 @@ async function startMcpServer(config, cwd2) {
2762
3057
  const { name, arguments: args } = request.params;
2763
3058
  const a = args ?? {};
2764
3059
  try {
2765
- const result = await dispatch(name, a, db, docsPath, cwd2);
3060
+ const result = await dispatch(name, a, db, docsPath, cwd2, config);
2766
3061
  return result;
2767
3062
  } catch (err) {
2768
- return ok(`Error: ${err instanceof Error ? err.message : String(err)}`, true);
3063
+ return ok2(`Error: ${err instanceof Error ? err.message : String(err)}`, true);
2769
3064
  }
2770
3065
  });
2771
3066
  const transport = new StdioServerTransport();
2772
3067
  await server.connect(transport);
2773
3068
  }
2774
- async function dispatch(name, args, db, docsPath, cwd2) {
3069
+ async function dispatch(name, args, db, docsPath, cwd2, config) {
2775
3070
  switch (name) {
2776
3071
  case "actions.start": {
2777
3072
  const taskId = num(args, "taskId");
2778
3073
  const agent = str(args, "agent");
2779
3074
  const action = await db.startAction(taskId, agent);
2780
- return ok(JSON.stringify({ actionId: action.id, taskId, agent, status: "in_progress" }));
3075
+ return ok2(JSON.stringify({ actionId: action.id, taskId, agent, status: "in_progress" }));
2781
3076
  }
2782
3077
  case "actions.write": {
2783
3078
  const actionId = str(args, "actionId");
2784
3079
  const sectionType = str(args, "sectionType");
2785
3080
  const content = str(args, "content");
2786
3081
  await db.writeSection(actionId, sectionType, content);
2787
- return ok(JSON.stringify({ actionId, sectionType, recorded: true }));
3082
+ return ok2(JSON.stringify({ actionId, sectionType, recorded: true }));
2788
3083
  }
2789
3084
  case "actions.complete": {
2790
3085
  const actionId = str(args, "actionId");
2791
3086
  const summary = str(args, "summary");
2792
3087
  const action = await db.completeAction(actionId, summary);
2793
- return ok(JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at }));
3088
+ return ok2(
3089
+ JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at })
3090
+ );
2794
3091
  }
2795
3092
  case "actions.get": {
2796
3093
  const taskId = num(args, "taskId");
@@ -2801,22 +3098,22 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2801
3098
  sections: await db.getActionSections(a.id)
2802
3099
  }))
2803
3100
  );
2804
- return ok(JSON.stringify(full, null, 2));
3101
+ return ok2(JSON.stringify(full, null, 2));
2805
3102
  }
2806
3103
  case "tasks.get": {
2807
3104
  const status = args["status"];
2808
3105
  const includeArchived = args["includeArchived"];
2809
3106
  const tasks = status ? await db.getTasks(status, includeArchived ?? false) : await db.getTasks(void 0, includeArchived ?? false);
2810
- return ok(JSON.stringify(tasks, null, 2));
3107
+ return ok2(JSON.stringify(tasks, null, 2));
2811
3108
  }
2812
3109
  case "tasks.claim": {
2813
3110
  const id = num(args, "id");
2814
3111
  const agent = str(args, "agent");
2815
3112
  const task2 = await db.claimTask(id, agent);
2816
3113
  if (!task2) {
2817
- return ok(JSON.stringify({ error: "task_already_claimed", taskId: id }));
3114
+ return ok2(JSON.stringify({ error: "task_already_claimed", taskId: id }));
2818
3115
  }
2819
- return ok(JSON.stringify(task2));
3116
+ return ok2(JSON.stringify(task2));
2820
3117
  }
2821
3118
  case "tasks.add": {
2822
3119
  const title = str(args, "title");
@@ -2824,7 +3121,7 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2824
3121
  const description = args["description"];
2825
3122
  const acceptance = args["acceptance"];
2826
3123
  const task2 = await db.addTask({ slug, title, description, acceptance });
2827
- return ok(JSON.stringify(task2));
3124
+ return ok2(JSON.stringify(task2));
2828
3125
  }
2829
3126
  case "tasks.update": {
2830
3127
  const id = num(args, "id");
@@ -2833,12 +3130,12 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2833
3130
  await db.closeOrphanedActions(id);
2834
3131
  }
2835
3132
  const task2 = await db.updateTaskStatus(id, status);
2836
- return ok(JSON.stringify(task2));
3133
+ return ok2(JSON.stringify(task2));
2837
3134
  }
2838
3135
  case "docs.search": {
2839
3136
  const query = str(args, "query");
2840
3137
  const results = searchDocs(docsPath, query);
2841
- return ok(JSON.stringify(results, null, 2));
3138
+ return ok2(JSON.stringify(results, null, 2));
2842
3139
  }
2843
3140
  case "actions.record_file": {
2844
3141
  const actionId = str(args, "actionId");
@@ -2846,17 +3143,17 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2846
3143
  const operation = str(args, "operation");
2847
3144
  const notes = args["notes"];
2848
3145
  await db.recordFile(actionId, filePath, operation, notes);
2849
- return ok(JSON.stringify({ actionId, filePath, operation, recorded: true }));
3146
+ return ok2(JSON.stringify({ actionId, filePath, operation, recorded: true }));
2850
3147
  }
2851
3148
  case "tasks.acceptance.update": {
2852
3149
  const criterionId = num(args, "criterionId");
2853
3150
  await db.markAcceptanceMet(criterionId);
2854
- return ok(JSON.stringify({ criterionId, met: true }));
3151
+ return ok2(JSON.stringify({ criterionId, met: true }));
2855
3152
  }
2856
3153
  case "tasks.acceptance.get": {
2857
3154
  const taskId = num(args, "taskId");
2858
3155
  const criteria = await db.getTaskAcceptance(taskId);
2859
- return ok(JSON.stringify(criteria, null, 2));
3156
+ return ok2(JSON.stringify(criteria, null, 2));
2860
3157
  }
2861
3158
  case "actions.record_tool": {
2862
3159
  const actionId = str(args, "actionId");
@@ -2864,7 +3161,7 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2864
3161
  const argsJson = args["argsJson"];
2865
3162
  const resultSummary = args["resultSummary"];
2866
3163
  await db.recordTool(actionId, toolName, argsJson, resultSummary);
2867
- return ok(JSON.stringify({ actionId, toolName, recorded: true }));
3164
+ return ok2(JSON.stringify({ actionId, toolName, recorded: true }));
2868
3165
  }
2869
3166
  case "tasks.edit": {
2870
3167
  const id = num(args, "id");
@@ -2872,55 +3169,68 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2872
3169
  const description = args["description"];
2873
3170
  const acceptance = args["acceptance"];
2874
3171
  const task2 = await db.getTaskById(id);
2875
- if (!task2) return ok(JSON.stringify({ error: "Task not found", taskId: id }), true);
2876
- await db.updateTask(id, { title, description: description !== void 0 ? description : void 0 });
3172
+ if (!task2) return ok2(JSON.stringify({ error: "Task not found", taskId: id }), true);
3173
+ await db.updateTask(id, {
3174
+ title,
3175
+ description: description !== void 0 ? description : void 0
3176
+ });
2877
3177
  if (acceptance !== void 0 && acceptance !== null) {
2878
3178
  await db.updateTaskAcceptance(id, acceptance);
2879
3179
  }
2880
3180
  const updated = await db.getTaskById(id);
2881
- return ok(JSON.stringify(updated));
3181
+ return ok2(JSON.stringify(updated));
2882
3182
  }
2883
3183
  case "tasks.archive": {
2884
3184
  const id = num(args, "id");
2885
3185
  const task2 = await db.archiveTask(id);
2886
- return ok(JSON.stringify(task2));
3186
+ return ok2(JSON.stringify(task2));
2887
3187
  }
2888
3188
  case "tasks.unarchive": {
2889
3189
  const id = num(args, "id");
2890
3190
  const task2 = await db.unarchiveTask(id);
2891
- return ok(JSON.stringify(task2));
3191
+ return ok2(JSON.stringify(task2));
2892
3192
  }
2893
3193
  case "permissions.check": {
2894
- const result = checkPermissionsSync(cwd2);
2895
- return ok(JSON.stringify(result, null, 2));
3194
+ const result = checkPermissionsSync(cwd2, config);
3195
+ return ok2(JSON.stringify(result, null, 2));
2896
3196
  }
2897
3197
  case "deps.snapshot": {
2898
- const pkgPath2 = join15(cwd2, "package.json");
2899
- if (!existsSync11(pkgPath2)) {
2900
- return ok("package.json not found in project root", true);
3198
+ const pkgPath2 = join17(cwd2, "package.json");
3199
+ if (!existsSync12(pkgPath2)) {
3200
+ return ok2("package.json not found in project root", true);
2901
3201
  }
2902
- const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
3202
+ const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
2903
3203
  const snapshot = {
2904
3204
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
2905
3205
  dependencies: pkg2.dependencies ?? {},
2906
3206
  devDependencies: pkg2.devDependencies ?? {}
2907
3207
  };
2908
- const harnessDir = join15(cwd2, ".harness");
3208
+ const harnessDir = join17(cwd2, ".harness");
2909
3209
  mkdirSync9(harnessDir, { recursive: true });
2910
- writeFileSync10(join15(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
2911
- return ok(JSON.stringify({ message: "Snapshot saved to .harness/deps-lock.json", capturedAt: snapshot.capturedAt }));
3210
+ writeFileSync10(join17(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
3211
+ return ok2(
3212
+ JSON.stringify({
3213
+ message: "Snapshot saved to .harness/deps-lock.json",
3214
+ capturedAt: snapshot.capturedAt
3215
+ })
3216
+ );
2912
3217
  }
2913
3218
  case "deps.check": {
2914
- const pkgPath2 = join15(cwd2, "package.json");
2915
- const lockPath = join15(cwd2, ".harness", "deps-lock.json");
2916
- if (!existsSync11(pkgPath2)) {
2917
- return ok("package.json not found in project root", true);
3219
+ const pkgPath2 = join17(cwd2, "package.json");
3220
+ const lockPath = join17(cwd2, ".harness", "deps-lock.json");
3221
+ if (!existsSync12(pkgPath2)) {
3222
+ return ok2("package.json not found in project root", true);
2918
3223
  }
2919
- if (!existsSync11(lockPath)) {
2920
- return ok(JSON.stringify({ status: "no-snapshot", message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline." }));
3224
+ if (!existsSync12(lockPath)) {
3225
+ return ok2(
3226
+ JSON.stringify({
3227
+ status: "no-snapshot",
3228
+ message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline."
3229
+ })
3230
+ );
2921
3231
  }
2922
- const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
2923
- const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
3232
+ const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
3233
+ const lock = JSON.parse(readFileSync9(lockPath, "utf8"));
2924
3234
  const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
2925
3235
  const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
2926
3236
  const added = [];
@@ -2930,7 +3240,10 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2930
3240
  if (!(name2 in previous)) {
2931
3241
  added.push(`${name2}@${version}`);
2932
3242
  } else {
2933
- const prevMajor = parseInt(previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
3243
+ const prevMajor = parseInt(
3244
+ previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0",
3245
+ 10
3246
+ );
2934
3247
  const curMajor = parseInt(version.replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
2935
3248
  if (!isNaN(prevMajor) && !isNaN(curMajor) && curMajor > prevMajor) {
2936
3249
  majorBumps.push({ name: name2, from: previous[name2], to: version });
@@ -2944,10 +3257,36 @@ async function dispatch(name, args, db, docsPath, cwd2) {
2944
3257
  }
2945
3258
  const significant = added.length > 0 || removed.length > 0 || majorBumps.length > 0;
2946
3259
  const advisory = significant ? "Significant dependency changes detected. Consider running `pnpx autoskills` (or `npx autoskills` if pnpm is unavailable) to refresh agent skills. Clearing stale skills before re-running is recommended." : "No significant dependency changes detected.";
2947
- return ok(JSON.stringify({ significant, added, removed, majorBumps, advisory, snapshotDate: lock.capturedAt }));
3260
+ return ok2(
3261
+ JSON.stringify({
3262
+ significant,
3263
+ added,
3264
+ removed,
3265
+ majorBumps,
3266
+ advisory,
3267
+ snapshotDate: lock.capturedAt
3268
+ })
3269
+ );
3270
+ }
3271
+ case "ahk.doctor": {
3272
+ const status = await getDoctorStatus(cwd2);
3273
+ const result = {
3274
+ lib: { current: status.lib.current, latest: status.lib.latest, outdated: status.lib.outdated },
3275
+ agents: {
3276
+ missing: status.agents.filter((a) => a.status === "missing").map((a) => a.name),
3277
+ outdated: status.agents.filter((a) => a.status === "outdated").map((a) => a.name),
3278
+ ok: status.agents.filter((a) => a.status === "ok").map((a) => a.name)
3279
+ },
3280
+ skills: {
3281
+ missing: status.skills.filter((s) => s.status === "missing").map((s) => s.name),
3282
+ outdated: status.skills.filter((s) => s.status === "outdated").map((s) => s.name),
3283
+ ok: status.skills.filter((s) => s.status === "ok").map((s) => s.name)
3284
+ }
3285
+ };
3286
+ return ok2(JSON.stringify(result, null, 2));
2948
3287
  }
2949
3288
  default:
2950
- return ok(`Unknown tool: ${name}`, true);
3289
+ return ok2(`Unknown tool: ${name}`, true);
2951
3290
  }
2952
3291
  }
2953
3292
  function searchDocs(docsPath, query, maxResults = 10) {
@@ -2958,12 +3297,16 @@ function searchDocs(docsPath, query, maxResults = 10) {
2958
3297
  for (const file of files) {
2959
3298
  if (results.length >= maxResults) break;
2960
3299
  try {
2961
- const content = readFileSync7(file, "utf8");
3300
+ const content = readFileSync9(file, "utf8");
2962
3301
  const lines = content.split("\n");
2963
3302
  for (let i = 0; i < lines.length; i++) {
2964
3303
  const lower = lines[i].toLowerCase();
2965
3304
  if (terms.every((t) => lower.includes(t))) {
2966
- results.push({ file: file.replace(docsPath + "/", ""), line: i + 1, text: lines[i].trim() });
3305
+ results.push({
3306
+ file: file.replace(docsPath + "/", ""),
3307
+ line: i + 1,
3308
+ text: lines[i].trim()
3309
+ });
2967
3310
  if (results.length >= maxResults) break;
2968
3311
  }
2969
3312
  }
@@ -2979,7 +3322,7 @@ function collectMarkdownFiles(dir) {
2979
3322
  const files = [];
2980
3323
  try {
2981
3324
  for (const entry of readdirSync2(dir)) {
2982
- const full = join15(dir, entry);
3325
+ const full = join17(dir, entry);
2983
3326
  const stat = statSync(full);
2984
3327
  if (stat.isDirectory()) {
2985
3328
  files.push(...collectMarkdownFiles(full));
@@ -2991,7 +3334,7 @@ function collectMarkdownFiles(dir) {
2991
3334
  }
2992
3335
  return files;
2993
3336
  }
2994
- function ok(text4, isError = false) {
3337
+ function ok2(text4, isError = false) {
2995
3338
  return { content: [{ type: "text", text: text4 }], isError };
2996
3339
  }
2997
3340
  function str(args, key) {
@@ -3013,8 +3356,8 @@ async function runServe(cwd2, opts) {
3013
3356
  }
3014
3357
  process.stderr.write(`[agent-harness-kit] MCP server starting (stdio)
3015
3358
  `);
3016
- const syncResult = checkPermissionsSync(cwd2);
3017
- if (!syncResult.in_sync) {
3359
+ const syncResult = checkPermissionsSync(cwd2, config);
3360
+ if (!syncResult.in_sync && syncResult.agents) {
3018
3361
  const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name, r]) => {
3019
3362
  const parts = [];
3020
3363
  if (r.missing.length) parts.push(`missing: ${r.missing.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
@@ -3030,12 +3373,12 @@ async function runServe(cwd2, opts) {
3030
3373
 
3031
3374
  // src/commands/status.ts
3032
3375
  import Table from "cli-table3";
3033
- import pc9 from "picocolors";
3376
+ import pc10 from "picocolors";
3034
3377
  var STATUS_COLOR = {
3035
- pending: (s) => pc9.dim(s),
3036
- in_progress: (s) => pc9.cyan(s),
3037
- done: (s) => pc9.green(s),
3038
- blocked: (s) => pc9.red(s)
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)
3039
3382
  };
3040
3383
  async function runStatus(cwd2, opts) {
3041
3384
  const config = await loadConfig(cwd2);
@@ -3056,11 +3399,11 @@ async function runStatus(cwd2, opts) {
3056
3399
  return;
3057
3400
  }
3058
3401
  if (tasks.length === 0) {
3059
- console.log(pc9.dim("No tasks yet. Run: ahk task add"));
3402
+ console.log(pc10.dim("No tasks yet. Run: ahk task add"));
3060
3403
  return;
3061
3404
  }
3062
3405
  const table = new Table({
3063
- head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc9.bold(h)),
3406
+ head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc10.bold(h)),
3064
3407
  style: { head: [], border: [] }
3065
3408
  });
3066
3409
  for (const t of tasks) {
@@ -3078,12 +3421,12 @@ async function runStatus(cwd2, opts) {
3078
3421
  const inProgress = tasks.filter((t) => t.status === "in_progress");
3079
3422
  if (inProgress.length > 0) {
3080
3423
  console.log("");
3081
- console.log(pc9.bold("Active actions:"));
3424
+ console.log(pc10.bold("Active actions:"));
3082
3425
  for (const t of inProgress) {
3083
3426
  const actions = await db.getActionsForTask(t.id);
3084
3427
  const active = actions.filter((a) => a.status === "in_progress");
3085
3428
  for (const a of active) {
3086
- console.log(` ${pc9.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3429
+ console.log(` ${pc10.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3087
3430
  }
3088
3431
  }
3089
3432
  }
@@ -3092,10 +3435,10 @@ async function runStatus(cwd2, opts) {
3092
3435
  const fn = STATUS_COLOR[s.status] ?? ((x) => x);
3093
3436
  return `${fn(s.status)}: ${s.total}`;
3094
3437
  });
3095
- console.log(pc9.dim("Tasks \u2014 ") + parts.join(pc9.dim(" | ")));
3438
+ console.log(pc10.dim("Tasks \u2014 ") + parts.join(pc10.dim(" | ")));
3096
3439
  const archivedTasks = await db.getArchivedTasks();
3097
3440
  if (archivedTasks.length > 0) {
3098
- console.log(pc9.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3441
+ console.log(pc10.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3099
3442
  }
3100
3443
  } finally {
3101
3444
  await db.close();
@@ -3103,13 +3446,13 @@ async function runStatus(cwd2, opts) {
3103
3446
  }
3104
3447
 
3105
3448
  // src/commands/sync.ts
3106
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
3107
- import { join as join16, resolve as resolve11 } from "path";
3108
- import pc10 from "picocolors";
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";
3109
3452
  async function runSync(cwd2, opts) {
3110
3453
  const config = await loadConfig(cwd2);
3111
3454
  const direction = opts.direction ?? "both";
3112
- const featureListPath = resolve11(join16(cwd2, config.storage.dir, "feature_list.json"));
3455
+ const featureListPath = resolve11(join18(cwd2, config.storage.dir, "feature_list.json"));
3113
3456
  const db = await openDB(config, cwd2);
3114
3457
  try {
3115
3458
  if (direction === "in" || direction === "both") {
@@ -3123,44 +3466,44 @@ async function runSync(cwd2, opts) {
3123
3466
  }
3124
3467
  }
3125
3468
  async function syncIn(featureListPath, db, dryRun) {
3126
- if (!existsSync12(featureListPath)) {
3127
- console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3469
+ if (!existsSync13(featureListPath)) {
3470
+ console.log(pc11.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3128
3471
  return;
3129
3472
  }
3130
3473
  let seeds;
3131
3474
  try {
3132
- seeds = JSON.parse(readFileSync8(featureListPath, "utf8"));
3475
+ seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
3133
3476
  } catch (err) {
3134
- console.error(pc10.red(`Failed to parse feature_list.json: ${err}`));
3477
+ console.error(pc11.red(`Failed to parse feature_list.json: ${err}`));
3135
3478
  process.exit(1);
3136
3479
  }
3137
3480
  if (dryRun) {
3138
- console.log(pc10.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3481
+ console.log(pc11.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3139
3482
  for (const t of seeds) {
3140
3483
  const existing = await db.getTaskBySlug(t.slug);
3141
- console.log(` ${existing ? pc10.dim("skip") : pc10.green("add ")} ${t.slug}`);
3484
+ console.log(` ${existing ? pc11.dim("skip") : pc11.green("add ")} ${t.slug}`);
3142
3485
  }
3143
3486
  return;
3144
3487
  }
3145
3488
  const result = await db.syncFromFeatureList(seeds);
3146
- console.log(pc10.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3489
+ console.log(pc11.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3147
3490
  }
3148
3491
  async function syncOut(db, cwd2, dryRun) {
3149
3492
  if (dryRun) {
3150
3493
  const tasks = await db.getTasks();
3151
- console.log(pc10.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3494
+ console.log(pc11.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3152
3495
  console.log(` ${tasks.length} tasks would be written`);
3153
3496
  return;
3154
3497
  }
3155
3498
  await db.writeFeatureList(cwd2);
3156
- console.log(pc10.green("\u2713 Out-sync: feature_list.json updated"));
3499
+ console.log(pc11.green("\u2713 Out-sync: feature_list.json updated"));
3157
3500
  }
3158
3501
 
3159
3502
  // src/commands/task/add.ts
3160
3503
  import * as p6 from "@clack/prompts";
3161
- import pc11 from "picocolors";
3504
+ import pc12 from "picocolors";
3162
3505
  async function runTaskAdd(cwd2) {
3163
- p6.intro(pc11.bold("agent-harness-kit \u2014 add task"));
3506
+ p6.intro(pc12.bold("agent-harness-kit \u2014 add task"));
3164
3507
  const title = await cliFormWithRetry(
3165
3508
  async () => {
3166
3509
  const val = await p6.text({ message: "Task title" });
@@ -3203,10 +3546,10 @@ async function runTaskAdd(cwd2) {
3203
3546
  await db.writeFeatureList(cwd2);
3204
3547
  await db.close();
3205
3548
  spinner6.stop("");
3206
- console.log(pc11.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
3207
- console.log(pc11.cyan("\u2192") + " " + pc11.cyan("ahk status") + " to see all tasks");
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");
3208
3551
  } catch (err) {
3209
- spinner6.stop(pc11.red("Failed"));
3552
+ spinner6.stop(pc12.red("Failed"));
3210
3553
  p6.log.error(err instanceof Error ? err.message : String(err));
3211
3554
  process.exit(1);
3212
3555
  }
@@ -3214,17 +3557,17 @@ async function runTaskAdd(cwd2) {
3214
3557
 
3215
3558
  // src/commands/task/done.ts
3216
3559
  import { spawnSync as spawnSync2 } from "child_process";
3217
- import { existsSync as existsSync13 } from "fs";
3560
+ import { existsSync as existsSync14 } from "fs";
3218
3561
  import { resolve as resolve12 } from "path";
3219
- import pc12 from "picocolors";
3562
+ import pc13 from "picocolors";
3220
3563
  async function runTaskDone(cwd2, idOrSlug) {
3221
3564
  const config = await loadConfig(cwd2);
3222
3565
  if (config.health.required) {
3223
3566
  const scriptPath = resolve12(cwd2, config.health.scriptPath);
3224
- if (existsSync13(scriptPath)) {
3567
+ if (existsSync14(scriptPath)) {
3225
3568
  const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
3226
3569
  if (result.status !== 0) {
3227
- console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3570
+ console.error(pc13.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3228
3571
  if (result.stdout) console.error(result.stdout);
3229
3572
  if (result.stderr) console.error(result.stderr);
3230
3573
  process.exit(1);
@@ -3237,16 +3580,16 @@ async function runTaskDone(cwd2, idOrSlug) {
3237
3580
  const isId = !isNaN(parsed);
3238
3581
  const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
3239
3582
  if (!task2) {
3240
- console.error(pc12.red(`Task not found: ${idOrSlug}`));
3583
+ console.error(pc13.red(`Task not found: ${idOrSlug}`));
3241
3584
  process.exit(1);
3242
3585
  }
3243
3586
  if (task2.status === "done") {
3244
- console.log(pc12.dim(`Task #${task2.id} is already done.`));
3587
+ console.log(pc13.dim(`Task #${task2.id} is already done.`));
3245
3588
  return;
3246
3589
  }
3247
3590
  await db.updateTaskStatus(task2.id, "done");
3248
3591
  await db.writeFeatureList(cwd2);
3249
- console.log(pc12.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3592
+ console.log(pc13.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3250
3593
  } finally {
3251
3594
  await db.close();
3252
3595
  }
@@ -3254,9 +3597,9 @@ async function runTaskDone(cwd2, idOrSlug) {
3254
3597
 
3255
3598
  // src/commands/task/edit.ts
3256
3599
  import * as p7 from "@clack/prompts";
3257
- import pc13 from "picocolors";
3600
+ import pc14 from "picocolors";
3258
3601
  async function runTaskEdit(cwd2) {
3259
- p7.intro(pc13.bold("agent-harness-kit \u2014 edit task"));
3602
+ p7.intro(pc14.bold("agent-harness-kit \u2014 edit task"));
3260
3603
  const config = await loadConfig(cwd2);
3261
3604
  const db = await openDB(config, cwd2);
3262
3605
  try {
@@ -3334,9 +3677,9 @@ async function runTaskEdit(cwd2) {
3334
3677
  await db.updateTaskAcceptance(task2.id, newAcceptance);
3335
3678
  await db.writeFeatureList(cwd2);
3336
3679
  spinner6.stop("");
3337
- console.log(pc13.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3680
+ console.log(pc14.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3338
3681
  } catch (err) {
3339
- spinner6.stop(pc13.red("Failed"));
3682
+ spinner6.stop(pc14.red("Failed"));
3340
3683
  p7.log.error(err instanceof Error ? err.message : String(err));
3341
3684
  process.exit(1);
3342
3685
  }
@@ -3347,12 +3690,12 @@ async function runTaskEdit(cwd2) {
3347
3690
 
3348
3691
  // src/commands/task/list.ts
3349
3692
  import Table2 from "cli-table3";
3350
- import pc14 from "picocolors";
3693
+ import pc15 from "picocolors";
3351
3694
  var STATUS_COLOR2 = {
3352
- pending: (s) => pc14.dim(s),
3353
- in_progress: (s) => pc14.cyan(s),
3354
- done: (s) => pc14.green(s),
3355
- blocked: (s) => pc14.red(s)
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)
3356
3699
  };
3357
3700
  async function runTaskList(cwd2, opts) {
3358
3701
  const config = await loadConfig(cwd2);
@@ -3369,11 +3712,11 @@ async function runTaskList(cwd2, opts) {
3369
3712
  let msg = "No tasks";
3370
3713
  if (filterStatus) msg += ` with status: ${filterStatus}`;
3371
3714
  if (opts.archived) msg += " (archived)";
3372
- console.log(pc14.dim(msg + "."));
3715
+ console.log(pc15.dim(msg + "."));
3373
3716
  return;
3374
3717
  }
3375
3718
  const table = new Table2({
3376
- head: ["ID", "Slug", "Title", "Status"].map((h) => pc14.bold(h)),
3719
+ head: ["ID", "Slug", "Title", "Status"].map((h) => pc15.bold(h)),
3377
3720
  style: { head: [], border: [] }
3378
3721
  });
3379
3722
  for (const t of tasks) {
@@ -3384,7 +3727,7 @@ async function runTaskList(cwd2, opts) {
3384
3727
  if (!opts.archived && !opts.includeArchived) {
3385
3728
  const archivedTasks = await db.getArchivedTasks();
3386
3729
  if (archivedTasks.length > 0) {
3387
- console.log(pc14.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
3730
+ console.log(pc15.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
3388
3731
  }
3389
3732
  }
3390
3733
  } finally {
@@ -3392,25 +3735,17 @@ async function runTaskList(cwd2, opts) {
3392
3735
  }
3393
3736
  }
3394
3737
 
3395
- // src/core/package-data.ts
3396
- import { createRequire } from "module";
3397
- import { dirname as dirname5, join as join17 } from "path";
3398
- import { fileURLToPath as fileURLToPath3 } from "url";
3399
- var require2 = createRequire(import.meta.url);
3400
- var pkgPath = join17(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
3401
- var pkg = require2(pkgPath);
3402
-
3403
3738
  // src/core/update-check.ts
3404
- import pc15 from "picocolors";
3405
- var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
3406
- var TIMEOUT_MS = 2500;
3739
+ import pc16 from "picocolors";
3740
+ var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
3741
+ var TIMEOUT_MS2 = 2500;
3407
3742
  function checkForUpdate(currentVersion) {
3408
3743
  return new Promise((resolve13) => {
3409
- const timer = setTimeout(() => resolve13(null), TIMEOUT_MS);
3410
- fetch(REGISTRY_URL).then((res) => res.json()).then((data) => {
3744
+ const timer = setTimeout(() => resolve13(null), TIMEOUT_MS2);
3745
+ fetch(REGISTRY_URL2).then((res) => res.json()).then((data) => {
3411
3746
  clearTimeout(timer);
3412
3747
  const latest = data.version;
3413
- resolve13(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
3748
+ resolve13(isNewer2(latest, currentVersion) ? { current: currentVersion, latest } : null);
3414
3749
  }).catch(() => {
3415
3750
  clearTimeout(timer);
3416
3751
  resolve13(null);
@@ -3419,12 +3754,12 @@ function checkForUpdate(currentVersion) {
3419
3754
  }
3420
3755
  function printUpdateMessage({ current, latest }) {
3421
3756
  const lines = [
3422
- ` Update available ${pc15.dim(current)} \u2192 ${pc15.green(latest)} `,
3423
- ` Run: ${pc15.cyan(`pnpm i ${pkg.name}@${latest}`)} `
3757
+ ` Update available ${pc16.dim(current)} \u2192 ${pc16.green(latest)} `,
3758
+ ` Run: ${pc16.cyan(`pnpm i ${pkg.name}@${latest}`)} `
3424
3759
  ];
3425
3760
  drawBox(lines);
3426
3761
  }
3427
- function isNewer(latest, current) {
3762
+ function isNewer2(latest, current) {
3428
3763
  const toNum = (v4) => v4.split(".").map(Number);
3429
3764
  const [lMaj, lMin, lPat] = toNum(latest);
3430
3765
  const [cMaj, cMin, cPat] = toNum(current);
@@ -3481,6 +3816,9 @@ program.command("export").description("Export the database").option("--sql", "SQ
3481
3816
  program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode>", "Reset agent MD files for specified provider").action(async (opts) => {
3482
3817
  await runReset(cwd, opts);
3483
3818
  });
3819
+ program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
3820
+ await runDoctor(cwd);
3821
+ });
3484
3822
  program.hook("postAction", async () => {
3485
3823
  const update = await updateCheck;
3486
3824
  if (update) printUpdateMessage(update);