@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/README.md +22 -1
- package/dist/agent-templates/consultant.md +34 -0
- package/dist/agent-templates/lead.md +59 -0
- package/dist/cli.js +615 -277
- 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
|
@@ -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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
};
|
|
682
|
-
|
|
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
|
-
|
|
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
|
|
949
|
-
import { fileURLToPath as
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
2078
|
+
import pc4 from "picocolors";
|
|
1804
2079
|
async function runExport(cwd2, opts) {
|
|
1805
2080
|
if (!opts.sql && !opts.json) {
|
|
1806
|
-
console.error(
|
|
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(
|
|
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(
|
|
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
|
|
1834
|
-
import { join as
|
|
1835
|
-
import
|
|
1836
|
-
function checkLine(label,
|
|
1837
|
-
const prefix = label ?
|
|
1838
|
-
const icon =
|
|
1839
|
-
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)));
|
|
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(
|
|
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 =
|
|
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 =
|
|
1867
|
-
const
|
|
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
|
-
|
|
2145
|
+
ok3,
|
|
1871
2146
|
`${name}${providerFiles.agentExtension} present`,
|
|
1872
2147
|
agentsLabelWidth
|
|
1873
2148
|
);
|
|
1874
|
-
if (!
|
|
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 =
|
|
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(
|
|
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 (!
|
|
1890
|
-
console.error(
|
|
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(
|
|
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(
|
|
2179
|
+
console.log(pc5.green("\u2713 Health check passed"));
|
|
1905
2180
|
process.exit(0);
|
|
1906
2181
|
} else {
|
|
1907
|
-
console.error(
|
|
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
|
|
2201
|
+
import { join as join14 } from "path";
|
|
1927
2202
|
import * as p3 from "@clack/prompts";
|
|
1928
|
-
import
|
|
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
|
|
1982
|
-
import { join as
|
|
1983
|
-
import
|
|
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 =
|
|
1987
|
-
if (!
|
|
1988
|
-
const content =
|
|
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 (
|
|
2000
|
-
const pkgPath2 =
|
|
2001
|
-
if (!
|
|
2002
|
-
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"));
|
|
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(
|
|
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(
|
|
2332
|
+
console.log(pc6.yellow("\u2502") + line + padStr + pc6.yellow("\u2502"));
|
|
2058
2333
|
}
|
|
2059
|
-
console.log(
|
|
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
|
-
` ${
|
|
2065
|
-
` ${
|
|
2066
|
-
` ${
|
|
2067
|
-
` ${
|
|
2068
|
-
` ${
|
|
2069
|
-
` ${
|
|
2070
|
-
` ${
|
|
2071
|
-
` ${
|
|
2072
|
-
` ${
|
|
2073
|
-
` ${
|
|
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
|
-
|
|
2360
|
+
pc7.yellow("\u26A0") + " " + pc7.bold("Project already initialized.") + pc7.dim(` (${existingConfig})`)
|
|
2086
2361
|
);
|
|
2087
2362
|
console.log();
|
|
2088
|
-
console.log(
|
|
2363
|
+
console.log(pc7.dim("Suggested next steps:"));
|
|
2089
2364
|
console.log(
|
|
2090
|
-
" " +
|
|
2365
|
+
" " + pc7.cyan("ahk build") + pc7.dim(" \u2014 re-sync agent files after updating the library")
|
|
2091
2366
|
);
|
|
2092
|
-
console.log(" " +
|
|
2367
|
+
console.log(" " + pc7.cyan("ahk build --sync") + pc7.dim(" \u2014 also sync agent permissions"));
|
|
2093
2368
|
console.log(
|
|
2094
|
-
" " +
|
|
2369
|
+
" " + pc7.cyan("ahk reset") + pc7.dim(" \u2014 wipe and re-initialize from scratch")
|
|
2095
2370
|
);
|
|
2096
|
-
console.log(" " +
|
|
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(
|
|
2236
|
-
mkdirSync8(
|
|
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(
|
|
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(
|
|
2260
|
-
console.log(
|
|
2261
|
-
console.log(
|
|
2262
|
-
console.log(
|
|
2263
|
-
console.log(
|
|
2264
|
-
console.log(
|
|
2265
|
-
console.log(
|
|
2266
|
-
console.log(
|
|
2267
|
-
console.log(
|
|
2268
|
-
console.log(
|
|
2269
|
-
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"));
|
|
2270
2545
|
console.log("");
|
|
2271
|
-
console.log(
|
|
2272
|
-
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`);
|
|
2273
2548
|
console.log(
|
|
2274
|
-
|
|
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: ${
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2328
|
-
import { join as
|
|
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
|
|
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 (!
|
|
2336
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
2636
|
+
console.log(pc9.red(" Cancelled by user."));
|
|
2362
2637
|
return;
|
|
2363
2638
|
}
|
|
2364
2639
|
if (confirm3) {
|
|
2365
2640
|
try {
|
|
2366
|
-
const filePath =
|
|
2641
|
+
const filePath = join15(agentDirPath, file);
|
|
2367
2642
|
rmSync(filePath, { force: true });
|
|
2368
|
-
console.log(
|
|
2643
|
+
console.log(pc9.green(` Removed ${file}`));
|
|
2369
2644
|
} catch {
|
|
2370
|
-
console.error(
|
|
2645
|
+
console.error(pc9.red(` Failed to remove ${file}`));
|
|
2371
2646
|
}
|
|
2372
2647
|
} else {
|
|
2373
|
-
console.log(
|
|
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(
|
|
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 &&
|
|
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(
|
|
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(
|
|
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(
|
|
2686
|
+
console.log(pc9.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2412
2687
|
}
|
|
2413
|
-
if (
|
|
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(
|
|
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(
|
|
2711
|
+
console.log(pc9.green(` \u2713 Removed ${dbPath}`));
|
|
2437
2712
|
} catch {
|
|
2438
|
-
console.error(
|
|
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(
|
|
2719
|
+
console.log(pc9.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2445
2720
|
} catch {
|
|
2446
|
-
console.error(
|
|
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(
|
|
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(
|
|
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
|
|
2463
|
-
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";
|
|
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
|
|
2473
|
-
import { join as
|
|
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 =
|
|
2494
|
-
if (!
|
|
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 =
|
|
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
|
|
2506
|
-
if (!
|
|
2507
|
-
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 };
|
|
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: {
|
|
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: {
|
|
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: {
|
|
2691
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3114
|
+
return ok2(JSON.stringify({ error: "task_already_claimed", taskId: id }));
|
|
2818
3115
|
}
|
|
2819
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2876
|
-
await db.updateTask(id, {
|
|
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
|
|
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
|
|
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
|
|
3191
|
+
return ok2(JSON.stringify(task2));
|
|
2892
3192
|
}
|
|
2893
3193
|
case "permissions.check": {
|
|
2894
|
-
const result = checkPermissionsSync(cwd2);
|
|
2895
|
-
return
|
|
3194
|
+
const result = checkPermissionsSync(cwd2, config);
|
|
3195
|
+
return ok2(JSON.stringify(result, null, 2));
|
|
2896
3196
|
}
|
|
2897
3197
|
case "deps.snapshot": {
|
|
2898
|
-
const pkgPath2 =
|
|
2899
|
-
if (!
|
|
2900
|
-
return
|
|
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(
|
|
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 =
|
|
3208
|
+
const harnessDir = join17(cwd2, ".harness");
|
|
2909
3209
|
mkdirSync9(harnessDir, { recursive: true });
|
|
2910
|
-
writeFileSync10(
|
|
2911
|
-
return
|
|
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 =
|
|
2915
|
-
const lockPath =
|
|
2916
|
-
if (!
|
|
2917
|
-
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);
|
|
2918
3223
|
}
|
|
2919
|
-
if (!
|
|
2920
|
-
return
|
|
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(
|
|
2923
|
-
const lock = JSON.parse(
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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({
|
|
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 =
|
|
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
|
|
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
|
|
3376
|
+
import pc10 from "picocolors";
|
|
3034
3377
|
var STATUS_COLOR = {
|
|
3035
|
-
pending: (s) =>
|
|
3036
|
-
in_progress: (s) =>
|
|
3037
|
-
done: (s) =>
|
|
3038
|
-
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)
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(` ${
|
|
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(
|
|
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(
|
|
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
|
|
3107
|
-
import { join as
|
|
3108
|
-
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";
|
|
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(
|
|
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 (!
|
|
3127
|
-
console.log(
|
|
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(
|
|
3475
|
+
seeds = JSON.parse(readFileSync10(featureListPath, "utf8"));
|
|
3133
3476
|
} catch (err) {
|
|
3134
|
-
console.error(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3504
|
+
import pc12 from "picocolors";
|
|
3162
3505
|
async function runTaskAdd(cwd2) {
|
|
3163
|
-
p6.intro(
|
|
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(
|
|
3207
|
-
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");
|
|
3208
3551
|
} catch (err) {
|
|
3209
|
-
spinner6.stop(
|
|
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
|
|
3560
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3218
3561
|
import { resolve as resolve12 } from "path";
|
|
3219
|
-
import
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3600
|
+
import pc14 from "picocolors";
|
|
3258
3601
|
async function runTaskEdit(cwd2) {
|
|
3259
|
-
p7.intro(
|
|
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(
|
|
3680
|
+
console.log(pc14.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3338
3681
|
} catch (err) {
|
|
3339
|
-
spinner6.stop(
|
|
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
|
|
3693
|
+
import pc15 from "picocolors";
|
|
3351
3694
|
var STATUS_COLOR2 = {
|
|
3352
|
-
pending: (s) =>
|
|
3353
|
-
in_progress: (s) =>
|
|
3354
|
-
done: (s) =>
|
|
3355
|
-
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)
|
|
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(
|
|
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) =>
|
|
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(
|
|
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
|
|
3405
|
-
var
|
|
3406
|
-
var
|
|
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),
|
|
3410
|
-
fetch(
|
|
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(
|
|
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 ${
|
|
3423
|
-
` Run: ${
|
|
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
|
|
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);
|