@cardor/agent-harness-kit 1.7.5 → 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/README.md +21 -0
- package/dist/agent-templates/consultant.md +34 -0
- package/dist/agent-templates/lead.md +59 -0
- package/dist/cli.js +513 -230
- package/dist/cli.js.map +1 -1
- package/dist/skills/ahk-ask/SKILL.md +29 -0
- package/dist/skills/ahk-consultant/SKILL.md +28 -0
- package/dist/skills/ahk-triage/SKILL.md +44 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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,6 +679,16 @@ 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
|
}
|
|
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");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
675
692
|
|
|
676
693
|
// src/core/materializer/claude-code.ts
|
|
677
694
|
var ClaudeCodeMaterializer = class {
|
|
@@ -709,6 +726,7 @@ No tasks in progress.
|
|
|
709
726
|
mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
|
|
710
727
|
mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
|
|
711
728
|
appendGitignore(cwd2);
|
|
729
|
+
writeSkills(cwd2, ".claude/skills");
|
|
712
730
|
}
|
|
713
731
|
async build(config, cwd2) {
|
|
714
732
|
const write2 = (relPath, content) => {
|
|
@@ -729,6 +747,7 @@ No tasks in progress.
|
|
|
729
747
|
mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
|
|
730
748
|
mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
|
|
731
749
|
mergeClaudeSettingsLocalJson(join4(cwd2, ".claude/settings.local.json"));
|
|
750
|
+
writeSkills(cwd2, ".claude/skills");
|
|
732
751
|
}
|
|
733
752
|
async migrate(config, _to, _cwd) {
|
|
734
753
|
void config;
|
|
@@ -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,6 +845,7 @@ 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;
|
|
@@ -873,6 +894,7 @@ No tasks in progress.
|
|
|
873
894
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
874
895
|
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
875
896
|
appendGitignore(cwd2);
|
|
897
|
+
writeSkills(cwd2, ".opencode/skills");
|
|
876
898
|
}
|
|
877
899
|
async build(config, cwd2) {
|
|
878
900
|
const write2 = (relPath, content) => {
|
|
@@ -890,6 +912,7 @@ No tasks in progress.
|
|
|
890
912
|
writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
|
|
891
913
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
892
914
|
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
915
|
+
writeSkills(cwd2, ".opencode/skills");
|
|
893
916
|
}
|
|
894
917
|
async migrate(config, _to, _cwd) {
|
|
895
918
|
void config;
|
|
@@ -954,8 +977,8 @@ async function buildOnce(cwd2) {
|
|
|
954
977
|
}
|
|
955
978
|
|
|
956
979
|
// src/commands/dashboard.ts
|
|
957
|
-
import { dirname as
|
|
958
|
-
import { fileURLToPath as
|
|
980
|
+
import { dirname as dirname5, join as join9, resolve as resolve7 } from "path";
|
|
981
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
959
982
|
import pc2 from "picocolors";
|
|
960
983
|
|
|
961
984
|
// src/core/dashboard-server.ts
|
|
@@ -1165,7 +1188,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1165
1188
|
// src/core/db.ts
|
|
1166
1189
|
import { randomUUID } from "crypto";
|
|
1167
1190
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
1168
|
-
import { dirname as
|
|
1191
|
+
import { dirname as dirname4, join as join8, resolve as resolve6 } from "path";
|
|
1169
1192
|
|
|
1170
1193
|
// src/core/repositories/ActionRepository.ts
|
|
1171
1194
|
var ActionRepository = class {
|
|
@@ -1648,7 +1671,7 @@ var HarnessDB = class {
|
|
|
1648
1671
|
async regenerateCurrentMd() {
|
|
1649
1672
|
if (!this.config.storage.markdownFallback.enabled) return;
|
|
1650
1673
|
const mdPath = resolve6(this.config.storage.markdownFallback.path);
|
|
1651
|
-
mkdirSync7(
|
|
1674
|
+
mkdirSync7(dirname4(mdPath), { recursive: true });
|
|
1652
1675
|
const inProgress = await this.tasks.getAll("in_progress");
|
|
1653
1676
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1654
1677
|
let md = `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -1761,7 +1784,7 @@ var HarnessDB = class {
|
|
|
1761
1784
|
}))
|
|
1762
1785
|
);
|
|
1763
1786
|
const path = join8(resolve6(cwd2), this.config.storage.dir, "feature_list.json");
|
|
1764
|
-
mkdirSync7(
|
|
1787
|
+
mkdirSync7(dirname4(path), { recursive: true });
|
|
1765
1788
|
writeFileSync7(path, JSON.stringify(list, null, 2) + "\n", "utf8");
|
|
1766
1789
|
}
|
|
1767
1790
|
};
|
|
@@ -1786,12 +1809,12 @@ async function openDB(config, cwd2) {
|
|
|
1786
1809
|
}
|
|
1787
1810
|
|
|
1788
1811
|
// src/commands/dashboard.ts
|
|
1789
|
-
var
|
|
1812
|
+
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
1790
1813
|
async function runDashboard(cwd2, opts) {
|
|
1791
1814
|
const config = await loadConfig(cwd2);
|
|
1792
1815
|
const db = await openDB(config, cwd2);
|
|
1793
1816
|
const dbPath = config.database.type === "sqlite" ? resolve7(cwd2, config.database.path) : null;
|
|
1794
|
-
const staticPath = join9(
|
|
1817
|
+
const staticPath = join9(__dirname3, "dashboard-dist");
|
|
1795
1818
|
const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1796
1819
|
console.log(pc2.green(`\u2713`) + ` Dashboard running at ${pc2.bold(pc2.cyan(url))}`);
|
|
1797
1820
|
console.log(pc2.dim(` WebSocket live updates enabled`));
|
|
@@ -1807,12 +1830,255 @@ async function runDashboard(cwd2, opts) {
|
|
|
1807
1830
|
});
|
|
1808
1831
|
}
|
|
1809
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
|
+
|
|
1810
2076
|
// src/commands/export.ts
|
|
1811
2077
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
1812
|
-
import
|
|
2078
|
+
import pc4 from "picocolors";
|
|
1813
2079
|
async function runExport(cwd2, opts) {
|
|
1814
2080
|
if (!opts.sql && !opts.json) {
|
|
1815
|
-
console.error(
|
|
2081
|
+
console.error(pc4.red("Specify --sql or --json"));
|
|
1816
2082
|
process.exit(1);
|
|
1817
2083
|
}
|
|
1818
2084
|
const config = await loadConfig(cwd2);
|
|
@@ -1823,13 +2089,13 @@ async function runExport(cwd2, opts) {
|
|
|
1823
2089
|
const out = JSON.stringify(data, null, 2) + "\n";
|
|
1824
2090
|
if (opts.output) {
|
|
1825
2091
|
writeFileSync8(opts.output, out, "utf8");
|
|
1826
|
-
console.log(
|
|
2092
|
+
console.log(pc4.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
|
|
1827
2093
|
} else {
|
|
1828
2094
|
process.stdout.write(out);
|
|
1829
2095
|
}
|
|
1830
2096
|
}
|
|
1831
2097
|
if (opts.sql) {
|
|
1832
|
-
console.error(
|
|
2098
|
+
console.error(pc4.dim("SQL dump requires direct SQLite access \u2014 use: sqlite3 .harness/harness.db .dump"));
|
|
1833
2099
|
process.exit(1);
|
|
1834
2100
|
}
|
|
1835
2101
|
} finally {
|
|
@@ -1839,27 +2105,27 @@ async function runExport(cwd2, opts) {
|
|
|
1839
2105
|
|
|
1840
2106
|
// src/commands/health.ts
|
|
1841
2107
|
import { spawnSync } from "child_process";
|
|
1842
|
-
import { existsSync as
|
|
1843
|
-
import { join as
|
|
1844
|
-
import
|
|
1845
|
-
function checkLine(label,
|
|
1846
|
-
const prefix = label ?
|
|
1847
|
-
const icon =
|
|
1848
|
-
console.log(prefix + icon + " " + (
|
|
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)));
|
|
1849
2115
|
}
|
|
1850
2116
|
async function runHealth(cwd2) {
|
|
1851
2117
|
let config;
|
|
1852
2118
|
try {
|
|
1853
2119
|
config = await loadConfig(cwd2);
|
|
1854
2120
|
} catch {
|
|
1855
|
-
console.error(
|
|
2121
|
+
console.error(pc5.red("\u2717 No config found. Run: ahk init"));
|
|
1856
2122
|
process.exit(1);
|
|
1857
2123
|
}
|
|
1858
2124
|
let allOk = true;
|
|
1859
2125
|
let dbOk;
|
|
1860
2126
|
if (config.database.type === "sqlite") {
|
|
1861
2127
|
const dbPath = resolve8(cwd2, config.database.path);
|
|
1862
|
-
dbOk =
|
|
2128
|
+
dbOk = existsSync8(dbPath);
|
|
1863
2129
|
checkLine("checking DB", dbOk, `${config.database.path} reachable`);
|
|
1864
2130
|
} else {
|
|
1865
2131
|
dbOk = true;
|
|
@@ -1872,31 +2138,31 @@ async function runHealth(cwd2) {
|
|
|
1872
2138
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1873
2139
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1874
2140
|
const name = agentNames[i];
|
|
1875
|
-
const agentPath =
|
|
1876
|
-
const
|
|
2141
|
+
const agentPath = join12(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
|
|
2142
|
+
const ok3 = existsSync8(agentPath);
|
|
1877
2143
|
checkLine(
|
|
1878
2144
|
i === 0 ? "checking agents" : null,
|
|
1879
|
-
|
|
2145
|
+
ok3,
|
|
1880
2146
|
`${name}${providerFiles.agentExtension} present`,
|
|
1881
2147
|
agentsLabelWidth
|
|
1882
2148
|
);
|
|
1883
|
-
if (!
|
|
2149
|
+
if (!ok3) allOk = false;
|
|
1884
2150
|
}
|
|
1885
2151
|
if (config.tools.mcp.enabled) {
|
|
1886
2152
|
const mcpFile = providerFiles.mcpFile;
|
|
1887
2153
|
const mcpPath = resolve8(cwd2, mcpFile);
|
|
1888
|
-
const mcpOk =
|
|
2154
|
+
const mcpOk = existsSync8(mcpPath);
|
|
1889
2155
|
checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
|
|
1890
2156
|
if (!mcpOk) allOk = false;
|
|
1891
2157
|
}
|
|
1892
2158
|
if (!allOk) {
|
|
1893
2159
|
console.log("");
|
|
1894
|
-
console.error(
|
|
2160
|
+
console.error(pc5.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
|
|
1895
2161
|
process.exit(1);
|
|
1896
2162
|
}
|
|
1897
2163
|
const scriptPath = resolve8(cwd2, config.health.scriptPath);
|
|
1898
|
-
if (!
|
|
1899
|
-
console.error(
|
|
2164
|
+
if (!existsSync8(scriptPath)) {
|
|
2165
|
+
console.error(pc5.red(`\u2717 health.sh not found: ${scriptPath}`));
|
|
1900
2166
|
console.error(" Run ahk init first.");
|
|
1901
2167
|
process.exit(1);
|
|
1902
2168
|
}
|
|
@@ -1906,14 +2172,14 @@ async function runHealth(cwd2) {
|
|
|
1906
2172
|
encoding: "utf8"
|
|
1907
2173
|
});
|
|
1908
2174
|
if (result.error) {
|
|
1909
|
-
console.error(
|
|
2175
|
+
console.error(pc5.red(`\u2717 Failed to run health.sh: ${result.error.message}`));
|
|
1910
2176
|
process.exit(1);
|
|
1911
2177
|
}
|
|
1912
2178
|
if (result.status === 0) {
|
|
1913
|
-
console.log(
|
|
2179
|
+
console.log(pc5.green("\u2713 Health check passed"));
|
|
1914
2180
|
process.exit(0);
|
|
1915
2181
|
} else {
|
|
1916
|
-
console.error(
|
|
2182
|
+
console.error(pc5.red(`\u2717 Health check failed (exit ${result.status ?? "unknown"})`));
|
|
1917
2183
|
process.exit(result.status ?? 1);
|
|
1918
2184
|
}
|
|
1919
2185
|
}
|
|
@@ -1932,9 +2198,9 @@ function getProviderHealthFiles(provider) {
|
|
|
1932
2198
|
|
|
1933
2199
|
// src/commands/init.ts
|
|
1934
2200
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
1935
|
-
import { join as
|
|
2201
|
+
import { join as join14 } from "path";
|
|
1936
2202
|
import * as p3 from "@clack/prompts";
|
|
1937
|
-
import
|
|
2203
|
+
import pc7 from "picocolors";
|
|
1938
2204
|
|
|
1939
2205
|
// src/schema/init.ts
|
|
1940
2206
|
import * as v from "valibot";
|
|
@@ -1987,14 +2253,14 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1987
2253
|
};
|
|
1988
2254
|
|
|
1989
2255
|
// src/commands/init-helpers.ts
|
|
1990
|
-
import { existsSync as
|
|
1991
|
-
import { join as
|
|
1992
|
-
import
|
|
2256
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
2257
|
+
import { join as join13 } from "path";
|
|
2258
|
+
import pc6 from "picocolors";
|
|
1993
2259
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1994
2260
|
try {
|
|
1995
|
-
const pkgPath2 =
|
|
1996
|
-
if (!
|
|
1997
|
-
const content =
|
|
2261
|
+
const pkgPath2 = join13(cwd2, "package.json");
|
|
2262
|
+
if (!existsSync9(pkgPath2)) return null;
|
|
2263
|
+
const content = readFileSync7(pkgPath2, "utf8");
|
|
1998
2264
|
const pkg2 = JSON.parse(content);
|
|
1999
2265
|
const name = pkg2?.name;
|
|
2000
2266
|
if (typeof name === "string" && name.trim()) return name.trim();
|
|
@@ -2005,10 +2271,10 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
2005
2271
|
}
|
|
2006
2272
|
function detectConfigExtension(cwd2) {
|
|
2007
2273
|
try {
|
|
2008
|
-
if (
|
|
2009
|
-
const pkgPath2 =
|
|
2010
|
-
if (!
|
|
2011
|
-
const pkg2 = JSON.parse(
|
|
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"));
|
|
2012
2278
|
if (pkg2?.type === "module") return "mjs";
|
|
2013
2279
|
} catch {
|
|
2014
2280
|
}
|
|
@@ -2059,27 +2325,27 @@ function stripAnsi(str2) {
|
|
|
2059
2325
|
function drawBox(lines) {
|
|
2060
2326
|
const width = Math.max(...lines.map((l) => stripAnsi(l).length));
|
|
2061
2327
|
const border = "\u2500".repeat(width);
|
|
2062
|
-
console.log(
|
|
2328
|
+
console.log(pc6.yellow(`\u250C${border}\u2510`));
|
|
2063
2329
|
for (const line of lines) {
|
|
2064
2330
|
const pad = width - stripAnsi(line).length;
|
|
2065
2331
|
const padStr = pad > 0 ? " ".repeat(pad) : "";
|
|
2066
|
-
console.log(
|
|
2332
|
+
console.log(pc6.yellow("\u2502") + line + padStr + pc6.yellow("\u2502"));
|
|
2067
2333
|
}
|
|
2068
|
-
console.log(
|
|
2334
|
+
console.log(pc6.yellow(`\u2514${border}\u2518`));
|
|
2069
2335
|
}
|
|
2070
2336
|
function printWelcomeMessage(projectName) {
|
|
2071
2337
|
const sep = "\u2500".repeat(38);
|
|
2072
2338
|
const lines = [
|
|
2073
|
-
` ${
|
|
2074
|
-
` ${
|
|
2075
|
-
` ${
|
|
2076
|
-
` ${
|
|
2077
|
-
` ${
|
|
2078
|
-
` ${
|
|
2079
|
-
` ${
|
|
2080
|
-
` ${
|
|
2081
|
-
` ${
|
|
2082
|
-
` ${
|
|
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")} `
|
|
2083
2349
|
];
|
|
2084
2350
|
console.log();
|
|
2085
2351
|
drawBox(lines);
|
|
@@ -2091,18 +2357,18 @@ async function runInit(cwd2, flags) {
|
|
|
2091
2357
|
const existingConfig = findConfigFile(cwd2);
|
|
2092
2358
|
if (existingConfig) {
|
|
2093
2359
|
console.log(
|
|
2094
|
-
|
|
2360
|
+
pc7.yellow("\u26A0") + " " + pc7.bold("Project already initialized.") + pc7.dim(` (${existingConfig})`)
|
|
2095
2361
|
);
|
|
2096
2362
|
console.log();
|
|
2097
|
-
console.log(
|
|
2363
|
+
console.log(pc7.dim("Suggested next steps:"));
|
|
2098
2364
|
console.log(
|
|
2099
|
-
" " +
|
|
2365
|
+
" " + pc7.cyan("ahk build") + pc7.dim(" \u2014 re-sync agent files after updating the library")
|
|
2100
2366
|
);
|
|
2101
|
-
console.log(" " +
|
|
2367
|
+
console.log(" " + pc7.cyan("ahk build --sync") + pc7.dim(" \u2014 also sync agent permissions"));
|
|
2102
2368
|
console.log(
|
|
2103
|
-
" " +
|
|
2369
|
+
" " + pc7.cyan("ahk reset") + pc7.dim(" \u2014 wipe and re-initialize from scratch")
|
|
2104
2370
|
);
|
|
2105
|
-
console.log(" " +
|
|
2371
|
+
console.log(" " + pc7.cyan("ahk dashboard") + pc7.dim(" \u2014 open the harness dashboard"));
|
|
2106
2372
|
process.exit(0);
|
|
2107
2373
|
}
|
|
2108
2374
|
const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
|
|
@@ -2241,8 +2507,8 @@ async function runInit(cwd2, flags) {
|
|
|
2241
2507
|
tasksAdapter,
|
|
2242
2508
|
port: config.tools.mcp.port
|
|
2243
2509
|
});
|
|
2244
|
-
writeFileSync9(
|
|
2245
|
-
mkdirSync8(
|
|
2510
|
+
writeFileSync9(join14(installDir, configFileName), configContent, "utf8");
|
|
2511
|
+
mkdirSync8(join14(installDir, config.storage.dir), { recursive: true });
|
|
2246
2512
|
const db = await openDB(config, installDir);
|
|
2247
2513
|
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
2248
2514
|
if (firstTask) {
|
|
@@ -2261,30 +2527,30 @@ async function runInit(cwd2, flags) {
|
|
|
2261
2527
|
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
2262
2528
|
throw err;
|
|
2263
2529
|
}
|
|
2264
|
-
console.log(
|
|
2530
|
+
console.log(pc7.green("\u2713 Scaffolded harness in current directory"));
|
|
2265
2531
|
const agentsDir = provider === "claude-code" ? ".claude/agents/" : ".opencode/agents/";
|
|
2266
2532
|
const mcpFile = provider === "claude-code" ? ".claude/mcp.json" : "./opencode.json";
|
|
2267
2533
|
console.log("");
|
|
2268
|
-
console.log(
|
|
2269
|
-
console.log(
|
|
2270
|
-
console.log(
|
|
2271
|
-
console.log(
|
|
2272
|
-
console.log(
|
|
2273
|
-
console.log(
|
|
2274
|
-
console.log(
|
|
2275
|
-
console.log(
|
|
2276
|
-
console.log(
|
|
2277
|
-
console.log(
|
|
2278
|
-
console.log(
|
|
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"));
|
|
2279
2545
|
console.log("");
|
|
2280
|
-
console.log(
|
|
2281
|
-
console.log(
|
|
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`);
|
|
2282
2548
|
console.log(
|
|
2283
|
-
|
|
2549
|
+
pc7.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc7.cyan("https://github.com/safishamsi/graphify")}`
|
|
2284
2550
|
);
|
|
2285
2551
|
const recommendations = [
|
|
2286
2552
|
` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
|
|
2287
|
-
` Learn more: ${
|
|
2553
|
+
` Learn more: ${pc7.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
|
|
2288
2554
|
];
|
|
2289
2555
|
console.log("");
|
|
2290
2556
|
drawBox(recommendations);
|
|
@@ -2292,7 +2558,7 @@ async function runInit(cwd2, flags) {
|
|
|
2292
2558
|
|
|
2293
2559
|
// src/commands/migrate.ts
|
|
2294
2560
|
import * as p4 from "@clack/prompts";
|
|
2295
|
-
import
|
|
2561
|
+
import pc8 from "picocolors";
|
|
2296
2562
|
async function runMigrate(cwd2, opts) {
|
|
2297
2563
|
const config = await loadConfig(cwd2);
|
|
2298
2564
|
let target;
|
|
@@ -2314,7 +2580,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2314
2580
|
target = val;
|
|
2315
2581
|
}
|
|
2316
2582
|
if (target === config.provider) {
|
|
2317
|
-
console.log(
|
|
2583
|
+
console.log(pc8.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2318
2584
|
return;
|
|
2319
2585
|
}
|
|
2320
2586
|
const spinner6 = p4.spinner();
|
|
@@ -2322,27 +2588,27 @@ async function runMigrate(cwd2, opts) {
|
|
|
2322
2588
|
try {
|
|
2323
2589
|
const targetMaterializer = getMaterializer(target);
|
|
2324
2590
|
await targetMaterializer.build(config, cwd2);
|
|
2325
|
-
spinner6.stop(
|
|
2591
|
+
spinner6.stop(pc8.green(`Migrated to ${target}`));
|
|
2326
2592
|
p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2327
2593
|
p4.log.warn(`Then run: ahk build`);
|
|
2328
2594
|
} catch (err) {
|
|
2329
|
-
spinner6.stop(
|
|
2595
|
+
spinner6.stop(pc8.red("Migration failed"));
|
|
2330
2596
|
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
2331
2597
|
process.exit(1);
|
|
2332
2598
|
}
|
|
2333
2599
|
}
|
|
2334
2600
|
|
|
2335
2601
|
// src/commands/reset.ts
|
|
2336
|
-
import { existsSync as
|
|
2337
|
-
import { join as
|
|
2602
|
+
import { existsSync as existsSync10, readdirSync, rmSync } from "fs";
|
|
2603
|
+
import { join as join15, resolve as resolve9 } from "path";
|
|
2338
2604
|
import * as p5 from "@clack/prompts";
|
|
2339
|
-
import
|
|
2605
|
+
import pc9 from "picocolors";
|
|
2340
2606
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2341
2607
|
async function resetAgentMds(cwd2, provider) {
|
|
2342
2608
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
2343
2609
|
const agentDirPath = resolve9(cwd2, agentDir);
|
|
2344
|
-
if (!
|
|
2345
|
-
console.log(
|
|
2610
|
+
if (!existsSync10(agentDirPath)) {
|
|
2611
|
+
console.log(pc9.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2346
2612
|
return;
|
|
2347
2613
|
}
|
|
2348
2614
|
const existingFiles = [];
|
|
@@ -2354,11 +2620,11 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2354
2620
|
}
|
|
2355
2621
|
}
|
|
2356
2622
|
} catch {
|
|
2357
|
-
console.log(
|
|
2623
|
+
console.log(pc9.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2358
2624
|
return;
|
|
2359
2625
|
}
|
|
2360
2626
|
if (existingFiles.length === 0) {
|
|
2361
|
-
console.log(
|
|
2627
|
+
console.log(pc9.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2362
2628
|
return;
|
|
2363
2629
|
}
|
|
2364
2630
|
for (const file of existingFiles) {
|
|
@@ -2367,19 +2633,19 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2367
2633
|
initialValue: true
|
|
2368
2634
|
});
|
|
2369
2635
|
if (p5.isCancel(confirm3)) {
|
|
2370
|
-
console.log(
|
|
2636
|
+
console.log(pc9.red(" Cancelled by user."));
|
|
2371
2637
|
return;
|
|
2372
2638
|
}
|
|
2373
2639
|
if (confirm3) {
|
|
2374
2640
|
try {
|
|
2375
|
-
const filePath =
|
|
2641
|
+
const filePath = join15(agentDirPath, file);
|
|
2376
2642
|
rmSync(filePath, { force: true });
|
|
2377
|
-
console.log(
|
|
2643
|
+
console.log(pc9.green(` Removed ${file}`));
|
|
2378
2644
|
} catch {
|
|
2379
|
-
console.error(
|
|
2645
|
+
console.error(pc9.red(` Failed to remove ${file}`));
|
|
2380
2646
|
}
|
|
2381
2647
|
} else {
|
|
2382
|
-
console.log(
|
|
2648
|
+
console.log(pc9.cyan(` Skipped ${file}`));
|
|
2383
2649
|
}
|
|
2384
2650
|
}
|
|
2385
2651
|
}
|
|
@@ -2388,7 +2654,7 @@ async function runReset(cwd2, opts) {
|
|
|
2388
2654
|
try {
|
|
2389
2655
|
config = await loadConfig(cwd2);
|
|
2390
2656
|
} catch {
|
|
2391
|
-
console.error(
|
|
2657
|
+
console.error(pc9.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2392
2658
|
process.exit(1);
|
|
2393
2659
|
}
|
|
2394
2660
|
const storageDir = config.storage.dir || ".harness";
|
|
@@ -2397,12 +2663,12 @@ async function runReset(cwd2, opts) {
|
|
|
2397
2663
|
let resetDb = false;
|
|
2398
2664
|
let resetFeatureList = false;
|
|
2399
2665
|
let resetAgentMdsFlag = false;
|
|
2400
|
-
if (dbPath &&
|
|
2666
|
+
if (dbPath && existsSync10(dbPath)) {
|
|
2401
2667
|
if (opts.force) {
|
|
2402
2668
|
resetDb = true;
|
|
2403
2669
|
} else {
|
|
2404
2670
|
if (config.database.type !== "sqlite") {
|
|
2405
|
-
console.log(
|
|
2671
|
+
console.log(pc9.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2406
2672
|
resetDb = false;
|
|
2407
2673
|
} else {
|
|
2408
2674
|
const confirm3 = await p5.confirm({
|
|
@@ -2410,16 +2676,16 @@ async function runReset(cwd2, opts) {
|
|
|
2410
2676
|
initialValue: true
|
|
2411
2677
|
});
|
|
2412
2678
|
if (p5.isCancel(confirm3)) {
|
|
2413
|
-
console.log(
|
|
2679
|
+
console.log(pc9.red(" Cancelled by user."));
|
|
2414
2680
|
return;
|
|
2415
2681
|
}
|
|
2416
2682
|
resetDb = confirm3;
|
|
2417
2683
|
}
|
|
2418
2684
|
}
|
|
2419
2685
|
} else if (!dbPath) {
|
|
2420
|
-
console.log(
|
|
2686
|
+
console.log(pc9.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2421
2687
|
}
|
|
2422
|
-
if (
|
|
2688
|
+
if (existsSync10(featureListPath)) {
|
|
2423
2689
|
if (opts.force) {
|
|
2424
2690
|
resetFeatureList = true;
|
|
2425
2691
|
} else {
|
|
@@ -2428,7 +2694,7 @@ async function runReset(cwd2, opts) {
|
|
|
2428
2694
|
initialValue: true
|
|
2429
2695
|
});
|
|
2430
2696
|
if (p5.isCancel(confirm3)) {
|
|
2431
|
-
console.log(
|
|
2697
|
+
console.log(pc9.red(" Cancelled by user."));
|
|
2432
2698
|
return;
|
|
2433
2699
|
}
|
|
2434
2700
|
resetFeatureList = confirm3;
|
|
@@ -2442,17 +2708,17 @@ async function runReset(cwd2, opts) {
|
|
|
2442
2708
|
rmSync(dbPath, { force: true });
|
|
2443
2709
|
rmSync(`${dbPath}-wal`, { force: true });
|
|
2444
2710
|
rmSync(`${dbPath}-shm`, { force: true });
|
|
2445
|
-
console.log(
|
|
2711
|
+
console.log(pc9.green(` \u2713 Removed ${dbPath}`));
|
|
2446
2712
|
} catch {
|
|
2447
|
-
console.error(
|
|
2713
|
+
console.error(pc9.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2448
2714
|
}
|
|
2449
2715
|
}
|
|
2450
2716
|
if (resetFeatureList) {
|
|
2451
2717
|
try {
|
|
2452
2718
|
rmSync(featureListPath, { force: true });
|
|
2453
|
-
console.log(
|
|
2719
|
+
console.log(pc9.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2454
2720
|
} catch {
|
|
2455
|
-
console.error(
|
|
2721
|
+
console.error(pc9.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2456
2722
|
}
|
|
2457
2723
|
}
|
|
2458
2724
|
if (resetAgentMdsFlag) {
|
|
@@ -2460,16 +2726,16 @@ async function runReset(cwd2, opts) {
|
|
|
2460
2726
|
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2461
2727
|
}
|
|
2462
2728
|
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2463
|
-
console.log(
|
|
2729
|
+
console.log(pc9.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2464
2730
|
return;
|
|
2465
2731
|
}
|
|
2466
2732
|
console.log("");
|
|
2467
|
-
console.log(
|
|
2733
|
+
console.log(pc9.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2468
2734
|
}
|
|
2469
2735
|
|
|
2470
2736
|
// src/core/mcp-server.ts
|
|
2471
|
-
import { existsSync as
|
|
2472
|
-
import { join as
|
|
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";
|
|
2473
2739
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2474
2740
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2475
2741
|
import {
|
|
@@ -2478,8 +2744,8 @@ import {
|
|
|
2478
2744
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2479
2745
|
|
|
2480
2746
|
// src/core/permissions-check.ts
|
|
2481
|
-
import { existsSync as
|
|
2482
|
-
import { join as
|
|
2747
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2748
|
+
import { join as join16 } from "path";
|
|
2483
2749
|
var CANONICAL = {
|
|
2484
2750
|
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
2485
2751
|
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
@@ -2502,21 +2768,21 @@ function checkPermissionsSync(cwd2, config) {
|
|
|
2502
2768
|
const agents = {};
|
|
2503
2769
|
let in_sync = true;
|
|
2504
2770
|
for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
|
|
2505
|
-
const filePath =
|
|
2506
|
-
if (!
|
|
2771
|
+
const filePath = join16(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2772
|
+
if (!existsSync11(filePath)) {
|
|
2507
2773
|
const missing2 = CANONICAL[agent];
|
|
2508
2774
|
agents[agent] = { ok: false, missing: missing2, extra: [] };
|
|
2509
2775
|
in_sync = false;
|
|
2510
2776
|
continue;
|
|
2511
2777
|
}
|
|
2512
|
-
const content =
|
|
2778
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
2513
2779
|
const installed = parseToolsFromFrontmatter(content);
|
|
2514
2780
|
const canonical = CANONICAL[agent];
|
|
2515
2781
|
const missing = canonical.filter((t) => !installed.includes(t));
|
|
2516
2782
|
const extra = installed.filter((t) => !canonical.includes(t));
|
|
2517
|
-
const
|
|
2518
|
-
if (!
|
|
2519
|
-
agents[agent] = { ok:
|
|
2783
|
+
const ok3 = missing.length === 0 && extra.length === 0;
|
|
2784
|
+
if (!ok3) in_sync = false;
|
|
2785
|
+
agents[agent] = { ok: ok3, missing, extra };
|
|
2520
2786
|
}
|
|
2521
2787
|
return { in_sync, agents };
|
|
2522
2788
|
}
|
|
@@ -2772,6 +3038,11 @@ var TOOLS = [
|
|
|
2772
3038
|
name: "deps.check",
|
|
2773
3039
|
description: "Compare current package.json against .harness/deps-lock.json and report changes",
|
|
2774
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: [] }
|
|
2775
3046
|
}
|
|
2776
3047
|
];
|
|
2777
3048
|
async function startMcpServer(config, cwd2) {
|
|
@@ -2789,7 +3060,7 @@ async function startMcpServer(config, cwd2) {
|
|
|
2789
3060
|
const result = await dispatch(name, a, db, docsPath, cwd2, config);
|
|
2790
3061
|
return result;
|
|
2791
3062
|
} catch (err) {
|
|
2792
|
-
return
|
|
3063
|
+
return ok2(`Error: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
2793
3064
|
}
|
|
2794
3065
|
});
|
|
2795
3066
|
const transport = new StdioServerTransport();
|
|
@@ -2801,20 +3072,20 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2801
3072
|
const taskId = num(args, "taskId");
|
|
2802
3073
|
const agent = str(args, "agent");
|
|
2803
3074
|
const action = await db.startAction(taskId, agent);
|
|
2804
|
-
return
|
|
3075
|
+
return ok2(JSON.stringify({ actionId: action.id, taskId, agent, status: "in_progress" }));
|
|
2805
3076
|
}
|
|
2806
3077
|
case "actions.write": {
|
|
2807
3078
|
const actionId = str(args, "actionId");
|
|
2808
3079
|
const sectionType = str(args, "sectionType");
|
|
2809
3080
|
const content = str(args, "content");
|
|
2810
3081
|
await db.writeSection(actionId, sectionType, content);
|
|
2811
|
-
return
|
|
3082
|
+
return ok2(JSON.stringify({ actionId, sectionType, recorded: true }));
|
|
2812
3083
|
}
|
|
2813
3084
|
case "actions.complete": {
|
|
2814
3085
|
const actionId = str(args, "actionId");
|
|
2815
3086
|
const summary = str(args, "summary");
|
|
2816
3087
|
const action = await db.completeAction(actionId, summary);
|
|
2817
|
-
return
|
|
3088
|
+
return ok2(
|
|
2818
3089
|
JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at })
|
|
2819
3090
|
);
|
|
2820
3091
|
}
|
|
@@ -2827,22 +3098,22 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2827
3098
|
sections: await db.getActionSections(a.id)
|
|
2828
3099
|
}))
|
|
2829
3100
|
);
|
|
2830
|
-
return
|
|
3101
|
+
return ok2(JSON.stringify(full, null, 2));
|
|
2831
3102
|
}
|
|
2832
3103
|
case "tasks.get": {
|
|
2833
3104
|
const status = args["status"];
|
|
2834
3105
|
const includeArchived = args["includeArchived"];
|
|
2835
3106
|
const tasks = status ? await db.getTasks(status, includeArchived ?? false) : await db.getTasks(void 0, includeArchived ?? false);
|
|
2836
|
-
return
|
|
3107
|
+
return ok2(JSON.stringify(tasks, null, 2));
|
|
2837
3108
|
}
|
|
2838
3109
|
case "tasks.claim": {
|
|
2839
3110
|
const id = num(args, "id");
|
|
2840
3111
|
const agent = str(args, "agent");
|
|
2841
3112
|
const task2 = await db.claimTask(id, agent);
|
|
2842
3113
|
if (!task2) {
|
|
2843
|
-
return
|
|
3114
|
+
return ok2(JSON.stringify({ error: "task_already_claimed", taskId: id }));
|
|
2844
3115
|
}
|
|
2845
|
-
return
|
|
3116
|
+
return ok2(JSON.stringify(task2));
|
|
2846
3117
|
}
|
|
2847
3118
|
case "tasks.add": {
|
|
2848
3119
|
const title = str(args, "title");
|
|
@@ -2850,7 +3121,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2850
3121
|
const description = args["description"];
|
|
2851
3122
|
const acceptance = args["acceptance"];
|
|
2852
3123
|
const task2 = await db.addTask({ slug, title, description, acceptance });
|
|
2853
|
-
return
|
|
3124
|
+
return ok2(JSON.stringify(task2));
|
|
2854
3125
|
}
|
|
2855
3126
|
case "tasks.update": {
|
|
2856
3127
|
const id = num(args, "id");
|
|
@@ -2859,12 +3130,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2859
3130
|
await db.closeOrphanedActions(id);
|
|
2860
3131
|
}
|
|
2861
3132
|
const task2 = await db.updateTaskStatus(id, status);
|
|
2862
|
-
return
|
|
3133
|
+
return ok2(JSON.stringify(task2));
|
|
2863
3134
|
}
|
|
2864
3135
|
case "docs.search": {
|
|
2865
3136
|
const query = str(args, "query");
|
|
2866
3137
|
const results = searchDocs(docsPath, query);
|
|
2867
|
-
return
|
|
3138
|
+
return ok2(JSON.stringify(results, null, 2));
|
|
2868
3139
|
}
|
|
2869
3140
|
case "actions.record_file": {
|
|
2870
3141
|
const actionId = str(args, "actionId");
|
|
@@ -2872,17 +3143,17 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2872
3143
|
const operation = str(args, "operation");
|
|
2873
3144
|
const notes = args["notes"];
|
|
2874
3145
|
await db.recordFile(actionId, filePath, operation, notes);
|
|
2875
|
-
return
|
|
3146
|
+
return ok2(JSON.stringify({ actionId, filePath, operation, recorded: true }));
|
|
2876
3147
|
}
|
|
2877
3148
|
case "tasks.acceptance.update": {
|
|
2878
3149
|
const criterionId = num(args, "criterionId");
|
|
2879
3150
|
await db.markAcceptanceMet(criterionId);
|
|
2880
|
-
return
|
|
3151
|
+
return ok2(JSON.stringify({ criterionId, met: true }));
|
|
2881
3152
|
}
|
|
2882
3153
|
case "tasks.acceptance.get": {
|
|
2883
3154
|
const taskId = num(args, "taskId");
|
|
2884
3155
|
const criteria = await db.getTaskAcceptance(taskId);
|
|
2885
|
-
return
|
|
3156
|
+
return ok2(JSON.stringify(criteria, null, 2));
|
|
2886
3157
|
}
|
|
2887
3158
|
case "actions.record_tool": {
|
|
2888
3159
|
const actionId = str(args, "actionId");
|
|
@@ -2890,7 +3161,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2890
3161
|
const argsJson = args["argsJson"];
|
|
2891
3162
|
const resultSummary = args["resultSummary"];
|
|
2892
3163
|
await db.recordTool(actionId, toolName, argsJson, resultSummary);
|
|
2893
|
-
return
|
|
3164
|
+
return ok2(JSON.stringify({ actionId, toolName, recorded: true }));
|
|
2894
3165
|
}
|
|
2895
3166
|
case "tasks.edit": {
|
|
2896
3167
|
const id = num(args, "id");
|
|
@@ -2898,7 +3169,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2898
3169
|
const description = args["description"];
|
|
2899
3170
|
const acceptance = args["acceptance"];
|
|
2900
3171
|
const task2 = await db.getTaskById(id);
|
|
2901
|
-
if (!task2) return
|
|
3172
|
+
if (!task2) return ok2(JSON.stringify({ error: "Task not found", taskId: id }), true);
|
|
2902
3173
|
await db.updateTask(id, {
|
|
2903
3174
|
title,
|
|
2904
3175
|
description: description !== void 0 ? description : void 0
|
|
@@ -2907,37 +3178,37 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2907
3178
|
await db.updateTaskAcceptance(id, acceptance);
|
|
2908
3179
|
}
|
|
2909
3180
|
const updated = await db.getTaskById(id);
|
|
2910
|
-
return
|
|
3181
|
+
return ok2(JSON.stringify(updated));
|
|
2911
3182
|
}
|
|
2912
3183
|
case "tasks.archive": {
|
|
2913
3184
|
const id = num(args, "id");
|
|
2914
3185
|
const task2 = await db.archiveTask(id);
|
|
2915
|
-
return
|
|
3186
|
+
return ok2(JSON.stringify(task2));
|
|
2916
3187
|
}
|
|
2917
3188
|
case "tasks.unarchive": {
|
|
2918
3189
|
const id = num(args, "id");
|
|
2919
3190
|
const task2 = await db.unarchiveTask(id);
|
|
2920
|
-
return
|
|
3191
|
+
return ok2(JSON.stringify(task2));
|
|
2921
3192
|
}
|
|
2922
3193
|
case "permissions.check": {
|
|
2923
3194
|
const result = checkPermissionsSync(cwd2, config);
|
|
2924
|
-
return
|
|
3195
|
+
return ok2(JSON.stringify(result, null, 2));
|
|
2925
3196
|
}
|
|
2926
3197
|
case "deps.snapshot": {
|
|
2927
|
-
const pkgPath2 =
|
|
2928
|
-
if (!
|
|
2929
|
-
return
|
|
3198
|
+
const pkgPath2 = join17(cwd2, "package.json");
|
|
3199
|
+
if (!existsSync12(pkgPath2)) {
|
|
3200
|
+
return ok2("package.json not found in project root", true);
|
|
2930
3201
|
}
|
|
2931
|
-
const pkg2 = JSON.parse(
|
|
3202
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
2932
3203
|
const snapshot = {
|
|
2933
3204
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2934
3205
|
dependencies: pkg2.dependencies ?? {},
|
|
2935
3206
|
devDependencies: pkg2.devDependencies ?? {}
|
|
2936
3207
|
};
|
|
2937
|
-
const harnessDir =
|
|
3208
|
+
const harnessDir = join17(cwd2, ".harness");
|
|
2938
3209
|
mkdirSync9(harnessDir, { recursive: true });
|
|
2939
|
-
writeFileSync10(
|
|
2940
|
-
return
|
|
3210
|
+
writeFileSync10(join17(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3211
|
+
return ok2(
|
|
2941
3212
|
JSON.stringify({
|
|
2942
3213
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
2943
3214
|
capturedAt: snapshot.capturedAt
|
|
@@ -2945,21 +3216,21 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2945
3216
|
);
|
|
2946
3217
|
}
|
|
2947
3218
|
case "deps.check": {
|
|
2948
|
-
const pkgPath2 =
|
|
2949
|
-
const lockPath =
|
|
2950
|
-
if (!
|
|
2951
|
-
return
|
|
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);
|
|
2952
3223
|
}
|
|
2953
|
-
if (!
|
|
2954
|
-
return
|
|
3224
|
+
if (!existsSync12(lockPath)) {
|
|
3225
|
+
return ok2(
|
|
2955
3226
|
JSON.stringify({
|
|
2956
3227
|
status: "no-snapshot",
|
|
2957
3228
|
message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline."
|
|
2958
3229
|
})
|
|
2959
3230
|
);
|
|
2960
3231
|
}
|
|
2961
|
-
const pkg2 = JSON.parse(
|
|
2962
|
-
const lock = JSON.parse(
|
|
3232
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath2, "utf8"));
|
|
3233
|
+
const lock = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
2963
3234
|
const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
|
|
2964
3235
|
const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
|
|
2965
3236
|
const added = [];
|
|
@@ -2986,7 +3257,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2986
3257
|
}
|
|
2987
3258
|
const significant = added.length > 0 || removed.length > 0 || majorBumps.length > 0;
|
|
2988
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.";
|
|
2989
|
-
return
|
|
3260
|
+
return ok2(
|
|
2990
3261
|
JSON.stringify({
|
|
2991
3262
|
significant,
|
|
2992
3263
|
added,
|
|
@@ -2997,8 +3268,25 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2997
3268
|
})
|
|
2998
3269
|
);
|
|
2999
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));
|
|
3287
|
+
}
|
|
3000
3288
|
default:
|
|
3001
|
-
return
|
|
3289
|
+
return ok2(`Unknown tool: ${name}`, true);
|
|
3002
3290
|
}
|
|
3003
3291
|
}
|
|
3004
3292
|
function searchDocs(docsPath, query, maxResults = 10) {
|
|
@@ -3009,7 +3297,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
3009
3297
|
for (const file of files) {
|
|
3010
3298
|
if (results.length >= maxResults) break;
|
|
3011
3299
|
try {
|
|
3012
|
-
const content =
|
|
3300
|
+
const content = readFileSync9(file, "utf8");
|
|
3013
3301
|
const lines = content.split("\n");
|
|
3014
3302
|
for (let i = 0; i < lines.length; i++) {
|
|
3015
3303
|
const lower = lines[i].toLowerCase();
|
|
@@ -3034,7 +3322,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3034
3322
|
const files = [];
|
|
3035
3323
|
try {
|
|
3036
3324
|
for (const entry of readdirSync2(dir)) {
|
|
3037
|
-
const full =
|
|
3325
|
+
const full = join17(dir, entry);
|
|
3038
3326
|
const stat = statSync(full);
|
|
3039
3327
|
if (stat.isDirectory()) {
|
|
3040
3328
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3046,7 +3334,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3046
3334
|
}
|
|
3047
3335
|
return files;
|
|
3048
3336
|
}
|
|
3049
|
-
function
|
|
3337
|
+
function ok2(text4, isError = false) {
|
|
3050
3338
|
return { content: [{ type: "text", text: text4 }], isError };
|
|
3051
3339
|
}
|
|
3052
3340
|
function str(args, key) {
|
|
@@ -3085,12 +3373,12 @@ async function runServe(cwd2, opts) {
|
|
|
3085
3373
|
|
|
3086
3374
|
// src/commands/status.ts
|
|
3087
3375
|
import Table from "cli-table3";
|
|
3088
|
-
import
|
|
3376
|
+
import pc10 from "picocolors";
|
|
3089
3377
|
var STATUS_COLOR = {
|
|
3090
|
-
pending: (s) =>
|
|
3091
|
-
in_progress: (s) =>
|
|
3092
|
-
done: (s) =>
|
|
3093
|
-
blocked: (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)
|
|
3094
3382
|
};
|
|
3095
3383
|
async function runStatus(cwd2, opts) {
|
|
3096
3384
|
const config = await loadConfig(cwd2);
|
|
@@ -3111,11 +3399,11 @@ async function runStatus(cwd2, opts) {
|
|
|
3111
3399
|
return;
|
|
3112
3400
|
}
|
|
3113
3401
|
if (tasks.length === 0) {
|
|
3114
|
-
console.log(
|
|
3402
|
+
console.log(pc10.dim("No tasks yet. Run: ahk task add"));
|
|
3115
3403
|
return;
|
|
3116
3404
|
}
|
|
3117
3405
|
const table = new Table({
|
|
3118
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
3406
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc10.bold(h)),
|
|
3119
3407
|
style: { head: [], border: [] }
|
|
3120
3408
|
});
|
|
3121
3409
|
for (const t of tasks) {
|
|
@@ -3133,12 +3421,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3133
3421
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3134
3422
|
if (inProgress.length > 0) {
|
|
3135
3423
|
console.log("");
|
|
3136
|
-
console.log(
|
|
3424
|
+
console.log(pc10.bold("Active actions:"));
|
|
3137
3425
|
for (const t of inProgress) {
|
|
3138
3426
|
const actions = await db.getActionsForTask(t.id);
|
|
3139
3427
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
3140
3428
|
for (const a of active) {
|
|
3141
|
-
console.log(` ${
|
|
3429
|
+
console.log(` ${pc10.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
3142
3430
|
}
|
|
3143
3431
|
}
|
|
3144
3432
|
}
|
|
@@ -3147,10 +3435,10 @@ async function runStatus(cwd2, opts) {
|
|
|
3147
3435
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
3148
3436
|
return `${fn(s.status)}: ${s.total}`;
|
|
3149
3437
|
});
|
|
3150
|
-
console.log(
|
|
3438
|
+
console.log(pc10.dim("Tasks \u2014 ") + parts.join(pc10.dim(" | ")));
|
|
3151
3439
|
const archivedTasks = await db.getArchivedTasks();
|
|
3152
3440
|
if (archivedTasks.length > 0) {
|
|
3153
|
-
console.log(
|
|
3441
|
+
console.log(pc10.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
|
|
3154
3442
|
}
|
|
3155
3443
|
} finally {
|
|
3156
3444
|
await db.close();
|
|
@@ -3158,13 +3446,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3158
3446
|
}
|
|
3159
3447
|
|
|
3160
3448
|
// src/commands/sync.ts
|
|
3161
|
-
import { existsSync as
|
|
3162
|
-
import { join as
|
|
3163
|
-
import
|
|
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";
|
|
3164
3452
|
async function runSync(cwd2, opts) {
|
|
3165
3453
|
const config = await loadConfig(cwd2);
|
|
3166
3454
|
const direction = opts.direction ?? "both";
|
|
3167
|
-
const featureListPath = resolve11(
|
|
3455
|
+
const featureListPath = resolve11(join18(cwd2, config.storage.dir, "feature_list.json"));
|
|
3168
3456
|
const db = await openDB(config, cwd2);
|
|
3169
3457
|
try {
|
|
3170
3458
|
if (direction === "in" || direction === "both") {
|
|
@@ -3178,44 +3466,44 @@ async function runSync(cwd2, opts) {
|
|
|
3178
3466
|
}
|
|
3179
3467
|
}
|
|
3180
3468
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3181
|
-
if (!
|
|
3182
|
-
console.log(
|
|
3469
|
+
if (!existsSync13(featureListPath)) {
|
|
3470
|
+
console.log(pc11.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3183
3471
|
return;
|
|
3184
3472
|
}
|
|
3185
3473
|
let seeds;
|
|
3186
3474
|
try {
|
|
3187
|
-
seeds = JSON.parse(
|
|
3475
|
+
seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
|
|
3188
3476
|
} catch (err) {
|
|
3189
|
-
console.error(
|
|
3477
|
+
console.error(pc11.red(`Failed to parse feature_list.json: ${err}`));
|
|
3190
3478
|
process.exit(1);
|
|
3191
3479
|
}
|
|
3192
3480
|
if (dryRun) {
|
|
3193
|
-
console.log(
|
|
3481
|
+
console.log(pc11.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
3194
3482
|
for (const t of seeds) {
|
|
3195
3483
|
const existing = await db.getTaskBySlug(t.slug);
|
|
3196
|
-
console.log(` ${existing ?
|
|
3484
|
+
console.log(` ${existing ? pc11.dim("skip") : pc11.green("add ")} ${t.slug}`);
|
|
3197
3485
|
}
|
|
3198
3486
|
return;
|
|
3199
3487
|
}
|
|
3200
3488
|
const result = await db.syncFromFeatureList(seeds);
|
|
3201
|
-
console.log(
|
|
3489
|
+
console.log(pc11.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
3202
3490
|
}
|
|
3203
3491
|
async function syncOut(db, cwd2, dryRun) {
|
|
3204
3492
|
if (dryRun) {
|
|
3205
3493
|
const tasks = await db.getTasks();
|
|
3206
|
-
console.log(
|
|
3494
|
+
console.log(pc11.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
3207
3495
|
console.log(` ${tasks.length} tasks would be written`);
|
|
3208
3496
|
return;
|
|
3209
3497
|
}
|
|
3210
3498
|
await db.writeFeatureList(cwd2);
|
|
3211
|
-
console.log(
|
|
3499
|
+
console.log(pc11.green("\u2713 Out-sync: feature_list.json updated"));
|
|
3212
3500
|
}
|
|
3213
3501
|
|
|
3214
3502
|
// src/commands/task/add.ts
|
|
3215
3503
|
import * as p6 from "@clack/prompts";
|
|
3216
|
-
import
|
|
3504
|
+
import pc12 from "picocolors";
|
|
3217
3505
|
async function runTaskAdd(cwd2) {
|
|
3218
|
-
p6.intro(
|
|
3506
|
+
p6.intro(pc12.bold("agent-harness-kit \u2014 add task"));
|
|
3219
3507
|
const title = await cliFormWithRetry(
|
|
3220
3508
|
async () => {
|
|
3221
3509
|
const val = await p6.text({ message: "Task title" });
|
|
@@ -3258,10 +3546,10 @@ async function runTaskAdd(cwd2) {
|
|
|
3258
3546
|
await db.writeFeatureList(cwd2);
|
|
3259
3547
|
await db.close();
|
|
3260
3548
|
spinner6.stop("");
|
|
3261
|
-
console.log(
|
|
3262
|
-
console.log(
|
|
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");
|
|
3263
3551
|
} catch (err) {
|
|
3264
|
-
spinner6.stop(
|
|
3552
|
+
spinner6.stop(pc12.red("Failed"));
|
|
3265
3553
|
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
3266
3554
|
process.exit(1);
|
|
3267
3555
|
}
|
|
@@ -3269,17 +3557,17 @@ async function runTaskAdd(cwd2) {
|
|
|
3269
3557
|
|
|
3270
3558
|
// src/commands/task/done.ts
|
|
3271
3559
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3272
|
-
import { existsSync as
|
|
3560
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3273
3561
|
import { resolve as resolve12 } from "path";
|
|
3274
|
-
import
|
|
3562
|
+
import pc13 from "picocolors";
|
|
3275
3563
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3276
3564
|
const config = await loadConfig(cwd2);
|
|
3277
3565
|
if (config.health.required) {
|
|
3278
3566
|
const scriptPath = resolve12(cwd2, config.health.scriptPath);
|
|
3279
|
-
if (
|
|
3567
|
+
if (existsSync14(scriptPath)) {
|
|
3280
3568
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3281
3569
|
if (result.status !== 0) {
|
|
3282
|
-
console.error(
|
|
3570
|
+
console.error(pc13.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
3283
3571
|
if (result.stdout) console.error(result.stdout);
|
|
3284
3572
|
if (result.stderr) console.error(result.stderr);
|
|
3285
3573
|
process.exit(1);
|
|
@@ -3292,16 +3580,16 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3292
3580
|
const isId = !isNaN(parsed);
|
|
3293
3581
|
const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
|
|
3294
3582
|
if (!task2) {
|
|
3295
|
-
console.error(
|
|
3583
|
+
console.error(pc13.red(`Task not found: ${idOrSlug}`));
|
|
3296
3584
|
process.exit(1);
|
|
3297
3585
|
}
|
|
3298
3586
|
if (task2.status === "done") {
|
|
3299
|
-
console.log(
|
|
3587
|
+
console.log(pc13.dim(`Task #${task2.id} is already done.`));
|
|
3300
3588
|
return;
|
|
3301
3589
|
}
|
|
3302
3590
|
await db.updateTaskStatus(task2.id, "done");
|
|
3303
3591
|
await db.writeFeatureList(cwd2);
|
|
3304
|
-
console.log(
|
|
3592
|
+
console.log(pc13.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
3305
3593
|
} finally {
|
|
3306
3594
|
await db.close();
|
|
3307
3595
|
}
|
|
@@ -3309,9 +3597,9 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3309
3597
|
|
|
3310
3598
|
// src/commands/task/edit.ts
|
|
3311
3599
|
import * as p7 from "@clack/prompts";
|
|
3312
|
-
import
|
|
3600
|
+
import pc14 from "picocolors";
|
|
3313
3601
|
async function runTaskEdit(cwd2) {
|
|
3314
|
-
p7.intro(
|
|
3602
|
+
p7.intro(pc14.bold("agent-harness-kit \u2014 edit task"));
|
|
3315
3603
|
const config = await loadConfig(cwd2);
|
|
3316
3604
|
const db = await openDB(config, cwd2);
|
|
3317
3605
|
try {
|
|
@@ -3389,9 +3677,9 @@ async function runTaskEdit(cwd2) {
|
|
|
3389
3677
|
await db.updateTaskAcceptance(task2.id, newAcceptance);
|
|
3390
3678
|
await db.writeFeatureList(cwd2);
|
|
3391
3679
|
spinner6.stop("");
|
|
3392
|
-
console.log(
|
|
3680
|
+
console.log(pc14.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3393
3681
|
} catch (err) {
|
|
3394
|
-
spinner6.stop(
|
|
3682
|
+
spinner6.stop(pc14.red("Failed"));
|
|
3395
3683
|
p7.log.error(err instanceof Error ? err.message : String(err));
|
|
3396
3684
|
process.exit(1);
|
|
3397
3685
|
}
|
|
@@ -3402,12 +3690,12 @@ async function runTaskEdit(cwd2) {
|
|
|
3402
3690
|
|
|
3403
3691
|
// src/commands/task/list.ts
|
|
3404
3692
|
import Table2 from "cli-table3";
|
|
3405
|
-
import
|
|
3693
|
+
import pc15 from "picocolors";
|
|
3406
3694
|
var STATUS_COLOR2 = {
|
|
3407
|
-
pending: (s) =>
|
|
3408
|
-
in_progress: (s) =>
|
|
3409
|
-
done: (s) =>
|
|
3410
|
-
blocked: (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)
|
|
3411
3699
|
};
|
|
3412
3700
|
async function runTaskList(cwd2, opts) {
|
|
3413
3701
|
const config = await loadConfig(cwd2);
|
|
@@ -3424,11 +3712,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
3424
3712
|
let msg = "No tasks";
|
|
3425
3713
|
if (filterStatus) msg += ` with status: ${filterStatus}`;
|
|
3426
3714
|
if (opts.archived) msg += " (archived)";
|
|
3427
|
-
console.log(
|
|
3715
|
+
console.log(pc15.dim(msg + "."));
|
|
3428
3716
|
return;
|
|
3429
3717
|
}
|
|
3430
3718
|
const table = new Table2({
|
|
3431
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
3719
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc15.bold(h)),
|
|
3432
3720
|
style: { head: [], border: [] }
|
|
3433
3721
|
});
|
|
3434
3722
|
for (const t of tasks) {
|
|
@@ -3439,7 +3727,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3439
3727
|
if (!opts.archived && !opts.includeArchived) {
|
|
3440
3728
|
const archivedTasks = await db.getArchivedTasks();
|
|
3441
3729
|
if (archivedTasks.length > 0) {
|
|
3442
|
-
console.log(
|
|
3730
|
+
console.log(pc15.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
|
|
3443
3731
|
}
|
|
3444
3732
|
}
|
|
3445
3733
|
} finally {
|
|
@@ -3447,25 +3735,17 @@ async function runTaskList(cwd2, opts) {
|
|
|
3447
3735
|
}
|
|
3448
3736
|
}
|
|
3449
3737
|
|
|
3450
|
-
// src/core/package-data.ts
|
|
3451
|
-
import { createRequire } from "module";
|
|
3452
|
-
import { dirname as dirname5, join as join17 } from "path";
|
|
3453
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3454
|
-
var require2 = createRequire(import.meta.url);
|
|
3455
|
-
var pkgPath = join17(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
3456
|
-
var pkg = require2(pkgPath);
|
|
3457
|
-
|
|
3458
3738
|
// src/core/update-check.ts
|
|
3459
|
-
import
|
|
3460
|
-
var
|
|
3461
|
-
var
|
|
3739
|
+
import pc16 from "picocolors";
|
|
3740
|
+
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
3741
|
+
var TIMEOUT_MS2 = 2500;
|
|
3462
3742
|
function checkForUpdate(currentVersion) {
|
|
3463
3743
|
return new Promise((resolve13) => {
|
|
3464
|
-
const timer = setTimeout(() => resolve13(null),
|
|
3465
|
-
fetch(
|
|
3744
|
+
const timer = setTimeout(() => resolve13(null), TIMEOUT_MS2);
|
|
3745
|
+
fetch(REGISTRY_URL2).then((res) => res.json()).then((data) => {
|
|
3466
3746
|
clearTimeout(timer);
|
|
3467
3747
|
const latest = data.version;
|
|
3468
|
-
resolve13(
|
|
3748
|
+
resolve13(isNewer2(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
3469
3749
|
}).catch(() => {
|
|
3470
3750
|
clearTimeout(timer);
|
|
3471
3751
|
resolve13(null);
|
|
@@ -3474,12 +3754,12 @@ function checkForUpdate(currentVersion) {
|
|
|
3474
3754
|
}
|
|
3475
3755
|
function printUpdateMessage({ current, latest }) {
|
|
3476
3756
|
const lines = [
|
|
3477
|
-
` Update available ${
|
|
3478
|
-
` Run: ${
|
|
3757
|
+
` Update available ${pc16.dim(current)} \u2192 ${pc16.green(latest)} `,
|
|
3758
|
+
` Run: ${pc16.cyan(`pnpm i ${pkg.name}@${latest}`)} `
|
|
3479
3759
|
];
|
|
3480
3760
|
drawBox(lines);
|
|
3481
3761
|
}
|
|
3482
|
-
function
|
|
3762
|
+
function isNewer2(latest, current) {
|
|
3483
3763
|
const toNum = (v4) => v4.split(".").map(Number);
|
|
3484
3764
|
const [lMaj, lMin, lPat] = toNum(latest);
|
|
3485
3765
|
const [cMaj, cMin, cPat] = toNum(current);
|
|
@@ -3536,6 +3816,9 @@ program.command("export").description("Export the database").option("--sql", "SQ
|
|
|
3536
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) => {
|
|
3537
3817
|
await runReset(cwd, opts);
|
|
3538
3818
|
});
|
|
3819
|
+
program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
|
|
3820
|
+
await runDoctor(cwd);
|
|
3821
|
+
});
|
|
3539
3822
|
program.hook("postAction", async () => {
|
|
3540
3823
|
const update = await updateCheck;
|
|
3541
3824
|
if (update) printUpdateMessage(update);
|