@cardor/agent-harness-kit 2.0.0 → 2.1.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 +65 -10
- package/dist/cli.js +236 -125
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -380,6 +380,17 @@ function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
380
380
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
381
381
|
writeFileSync2(filePath, content, "utf8");
|
|
382
382
|
}
|
|
383
|
+
function mergeGrokConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
384
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
385
|
+
let content = "";
|
|
386
|
+
if (existsSync4(filePath)) {
|
|
387
|
+
content = readFileSync3(filePath, "utf8");
|
|
388
|
+
}
|
|
389
|
+
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
390
|
+
const sectionBody = [`command = ${JSON.stringify(command)}`, `args = ${JSON.stringify(args)}`].join("\n");
|
|
391
|
+
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
392
|
+
writeFileSync2(filePath, content, "utf8");
|
|
393
|
+
}
|
|
383
394
|
|
|
384
395
|
// src/core/materializer/scaffold-utils.ts
|
|
385
396
|
import { createHash } from "crypto";
|
|
@@ -420,6 +431,9 @@ These tools may still appear available to you. The sandbox will reject the call.
|
|
|
420
431
|
function codexRestrictionNotice(agentName) {
|
|
421
432
|
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
422
433
|
}
|
|
434
|
+
function grokToolsAllowlist(agentName) {
|
|
435
|
+
return restrictionFor(agentName) === "no-write" ? ["Bash", "Read", "NotebookRead", "Grep", "Glob", "WebFetch", "WebSearch", "search_tool", "use_tool"] : [];
|
|
436
|
+
}
|
|
423
437
|
|
|
424
438
|
// src/core/materializer/templates.ts
|
|
425
439
|
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -849,6 +863,11 @@ function translateFrontmatterForOpenCode(md, agentName) {
|
|
|
849
863
|
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
850
864
|
return appendFrontmatterMapping(result, "permission", opencodePermissions(agentName));
|
|
851
865
|
}
|
|
866
|
+
function translateFrontmatterForGrok(md, agentName) {
|
|
867
|
+
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
868
|
+
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
869
|
+
return appendFrontmatterBlockSequence(result, "tools", grokToolsAllowlist(agentName));
|
|
870
|
+
}
|
|
852
871
|
var GITIGNORE_ENTRIES = `
|
|
853
872
|
# agent-harness-kit
|
|
854
873
|
.harness/harness.db
|
|
@@ -1163,9 +1182,73 @@ No tasks in progress.
|
|
|
1163
1182
|
}
|
|
1164
1183
|
};
|
|
1165
1184
|
|
|
1166
|
-
// src/core/materializer/
|
|
1185
|
+
// src/core/materializer/grok.ts
|
|
1167
1186
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1168
1187
|
import { join as join9, resolve as resolve4 } from "path";
|
|
1188
|
+
function grokAgentFiles(config) {
|
|
1189
|
+
const projectName = config.project.name;
|
|
1190
|
+
return [
|
|
1191
|
+
{ relPath: ".grok/agents/lead.md", content: translateFrontmatterForGrok(agentLead({ projectName }), "lead") },
|
|
1192
|
+
{ relPath: ".grok/agents/explorer.md", content: translateFrontmatterForGrok(agentExplorer({ projectName }), "explorer") },
|
|
1193
|
+
{ relPath: ".grok/agents/consultant.md", content: translateFrontmatterForGrok(agentConsultant({ projectName }), "consultant") },
|
|
1194
|
+
{ relPath: ".grok/agents/builder.md", content: translateFrontmatterForGrok(agentBuilder({ projectName }), "builder") },
|
|
1195
|
+
{ relPath: ".grok/agents/reviewer.md", content: translateFrontmatterForGrok(agentReviewer({ projectName }), "reviewer") }
|
|
1196
|
+
];
|
|
1197
|
+
}
|
|
1198
|
+
var GrokMaterializer = class {
|
|
1199
|
+
async scaffold(config, opts) {
|
|
1200
|
+
const { cwd: cwd2 } = opts;
|
|
1201
|
+
const write2 = (relPath, content, mode) => {
|
|
1202
|
+
const abs = join9(cwd2, relPath);
|
|
1203
|
+
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
1204
|
+
writeFileSync5(abs, content, { encoding: "utf8", mode });
|
|
1205
|
+
};
|
|
1206
|
+
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1207
|
+
if (!existsSync8(join9(cwd2, "health.sh"))) {
|
|
1208
|
+
write2("health.sh", HEALTH_SH, 493);
|
|
1209
|
+
}
|
|
1210
|
+
if (config.storage.scope === "local" && !existsSync8(join9(cwd2, config.storage.markdownFallback.path))) {
|
|
1211
|
+
write2(
|
|
1212
|
+
config.storage.markdownFallback.path,
|
|
1213
|
+
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
1214
|
+
<!-- Run ahk status to refresh -->
|
|
1215
|
+
|
|
1216
|
+
# Current Session
|
|
1217
|
+
|
|
1218
|
+
No tasks in progress.
|
|
1219
|
+
`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
writeAgentFiles(cwd2, grokAgentFiles(config));
|
|
1223
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1224
|
+
appendGitignore(cwd2);
|
|
1225
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1226
|
+
}
|
|
1227
|
+
async build(config, cwd2, opts = {}) {
|
|
1228
|
+
const derived = reconcileGeneratedFiles(
|
|
1229
|
+
cwd2,
|
|
1230
|
+
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1231
|
+
{ force: opts.force, backupRoot: join9(cwd2, config.storage.dir, "backups") }
|
|
1232
|
+
);
|
|
1233
|
+
const agents = writeAgentFiles(cwd2, grokAgentFiles(config), {
|
|
1234
|
+
force: opts.force,
|
|
1235
|
+
backupRoot: join9(cwd2, config.storage.dir, "backups")
|
|
1236
|
+
});
|
|
1237
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1238
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1239
|
+
return { agents, derived };
|
|
1240
|
+
}
|
|
1241
|
+
async migrate(config, _to, _cwd) {
|
|
1242
|
+
void config;
|
|
1243
|
+
}
|
|
1244
|
+
async syncPermissions(_cwd) {
|
|
1245
|
+
console.log(" Permissions sync not needed for grok-cli \u2014 skipping");
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
// src/core/materializer/opencode.ts
|
|
1250
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1251
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
1169
1252
|
function opencodeAgentFiles(config) {
|
|
1170
1253
|
const projectName = config.project.name;
|
|
1171
1254
|
return [
|
|
@@ -1180,15 +1263,15 @@ var OpenCodeMaterializer = class {
|
|
|
1180
1263
|
async scaffold(config, opts) {
|
|
1181
1264
|
const { cwd: cwd2 } = opts;
|
|
1182
1265
|
const write2 = (relPath, content, mode) => {
|
|
1183
|
-
const abs =
|
|
1184
|
-
|
|
1185
|
-
|
|
1266
|
+
const abs = join10(cwd2, relPath);
|
|
1267
|
+
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
1268
|
+
writeFileSync6(abs, content, { encoding: "utf8", mode });
|
|
1186
1269
|
};
|
|
1187
1270
|
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1188
|
-
if (!
|
|
1271
|
+
if (!existsSync9(join10(cwd2, "health.sh"))) {
|
|
1189
1272
|
write2("health.sh", HEALTH_SH, 493);
|
|
1190
1273
|
}
|
|
1191
|
-
if (config.storage.scope === "local" && !
|
|
1274
|
+
if (config.storage.scope === "local" && !existsSync9(join10(cwd2, config.storage.markdownFallback.path))) {
|
|
1192
1275
|
write2(
|
|
1193
1276
|
config.storage.markdownFallback.path,
|
|
1194
1277
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -1201,7 +1284,7 @@ No tasks in progress.
|
|
|
1201
1284
|
);
|
|
1202
1285
|
}
|
|
1203
1286
|
writeAgentFiles(cwd2, opencodeAgentFiles(config));
|
|
1204
|
-
mergeOpencodeJson(
|
|
1287
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1205
1288
|
appendGitignore(cwd2);
|
|
1206
1289
|
writeSkills(cwd2, ".opencode/skills");
|
|
1207
1290
|
}
|
|
@@ -1209,13 +1292,13 @@ No tasks in progress.
|
|
|
1209
1292
|
const derived = reconcileGeneratedFiles(
|
|
1210
1293
|
cwd2,
|
|
1211
1294
|
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1212
|
-
{ force: opts.force, backupRoot:
|
|
1295
|
+
{ force: opts.force, backupRoot: join10(cwd2, config.storage.dir, "backups") }
|
|
1213
1296
|
);
|
|
1214
1297
|
const agents = writeAgentFiles(cwd2, opencodeAgentFiles(config), {
|
|
1215
1298
|
force: opts.force,
|
|
1216
|
-
backupRoot:
|
|
1299
|
+
backupRoot: join10(cwd2, config.storage.dir, "backups")
|
|
1217
1300
|
});
|
|
1218
|
-
mergeOpencodeJson(
|
|
1301
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1219
1302
|
writeSkills(cwd2, ".opencode/skills");
|
|
1220
1303
|
return { agents, derived };
|
|
1221
1304
|
}
|
|
@@ -1236,6 +1319,8 @@ function getMaterializer(provider) {
|
|
|
1236
1319
|
return new OpenCodeMaterializer();
|
|
1237
1320
|
case "codex-cli":
|
|
1238
1321
|
return new CodexCliMaterializer();
|
|
1322
|
+
case "grok-cli":
|
|
1323
|
+
return new GrokMaterializer();
|
|
1239
1324
|
default:
|
|
1240
1325
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1241
1326
|
}
|
|
@@ -1335,14 +1420,14 @@ async function buildOnce(cwd2, force) {
|
|
|
1335
1420
|
|
|
1336
1421
|
// src/commands/dashboard.ts
|
|
1337
1422
|
import { homedir } from "os";
|
|
1338
|
-
import { dirname as dirname5, join as
|
|
1423
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
1339
1424
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1340
1425
|
import pc3 from "picocolors";
|
|
1341
1426
|
|
|
1342
1427
|
// src/core/dashboard-server.ts
|
|
1343
1428
|
import { watch as watch2 } from "fs";
|
|
1344
|
-
import { existsSync as
|
|
1345
|
-
import { extname, join as
|
|
1429
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
1430
|
+
import { extname, join as join11 } from "path";
|
|
1346
1431
|
import { serve } from "@hono/node-server";
|
|
1347
1432
|
import { Hono } from "hono";
|
|
1348
1433
|
import { WebSocketServer } from "ws";
|
|
@@ -1351,11 +1436,11 @@ import { WebSocketServer } from "ws";
|
|
|
1351
1436
|
import { createServer } from "net";
|
|
1352
1437
|
var DASHBOARD_BIND_HOST = void 0;
|
|
1353
1438
|
function isPortFree(port, host = DASHBOARD_BIND_HOST) {
|
|
1354
|
-
return new Promise((
|
|
1439
|
+
return new Promise((resolve12) => {
|
|
1355
1440
|
const server = createServer();
|
|
1356
|
-
server.once("error", () =>
|
|
1441
|
+
server.once("error", () => resolve12(false));
|
|
1357
1442
|
server.once("listening", () => {
|
|
1358
|
-
server.close(() =>
|
|
1443
|
+
server.close(() => resolve12(true));
|
|
1359
1444
|
});
|
|
1360
1445
|
server.listen(port, host);
|
|
1361
1446
|
});
|
|
@@ -1398,7 +1483,7 @@ function fileResponse(filePath) {
|
|
|
1398
1483
|
});
|
|
1399
1484
|
}
|
|
1400
1485
|
function awaitServerListening(server, port) {
|
|
1401
|
-
return new Promise((
|
|
1486
|
+
return new Promise((resolve12, reject) => {
|
|
1402
1487
|
const closeQuietly = () => {
|
|
1403
1488
|
try {
|
|
1404
1489
|
server.close(() => {
|
|
@@ -1421,7 +1506,7 @@ function awaitServerListening(server, port) {
|
|
|
1421
1506
|
};
|
|
1422
1507
|
const onListening = () => {
|
|
1423
1508
|
server.off("error", onError);
|
|
1424
|
-
|
|
1509
|
+
resolve12();
|
|
1425
1510
|
};
|
|
1426
1511
|
server.once("error", onError);
|
|
1427
1512
|
server.once("listening", onListening);
|
|
@@ -1527,15 +1612,15 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1527
1612
|
app.get("/*", (c) => {
|
|
1528
1613
|
const urlPath = c.req.path;
|
|
1529
1614
|
if (urlPath !== "/") {
|
|
1530
|
-
const candidate =
|
|
1531
|
-
if (
|
|
1615
|
+
const candidate = join11(staticPath, urlPath);
|
|
1616
|
+
if (existsSync10(candidate)) {
|
|
1532
1617
|
try {
|
|
1533
1618
|
return fileResponse(candidate);
|
|
1534
1619
|
} catch {
|
|
1535
1620
|
}
|
|
1536
1621
|
}
|
|
1537
1622
|
}
|
|
1538
|
-
return fileResponse(
|
|
1623
|
+
return fileResponse(join11(staticPath, "index.html"));
|
|
1539
1624
|
});
|
|
1540
1625
|
const resolvedPort = await findFreePort(port, { host: DASHBOARD_BIND_HOST });
|
|
1541
1626
|
if (resolvedPort !== port) {
|
|
@@ -1574,7 +1659,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1574
1659
|
let watcher = null;
|
|
1575
1660
|
if (dbPath) {
|
|
1576
1661
|
const walPath = `${dbPath}-wal`;
|
|
1577
|
-
const watchTarget =
|
|
1662
|
+
const watchTarget = existsSync10(walPath) ? walPath : dbPath;
|
|
1578
1663
|
watcher = watch2(watchTarget, broadcast);
|
|
1579
1664
|
}
|
|
1580
1665
|
return {
|
|
@@ -1594,7 +1679,7 @@ async function runDashboard(cwd2, opts) {
|
|
|
1594
1679
|
const config = await loadConfig(cwd2);
|
|
1595
1680
|
const db = await openDB(config, cwd2);
|
|
1596
1681
|
const dbPath = config.database.type === "sqlite" ? resolveSqlitePath(config, cwd2, homedir()) : null;
|
|
1597
|
-
const staticPath =
|
|
1682
|
+
const staticPath = join12(__dirname3, "dashboard-dist");
|
|
1598
1683
|
const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1599
1684
|
console.log(pc3.green(`\u2713`) + ` Dashboard running at ${pc3.bold(pc3.cyan(url))}`);
|
|
1600
1685
|
console.log(pc3.dim(` WebSocket live updates enabled`));
|
|
@@ -1614,8 +1699,8 @@ async function runDashboard(cwd2, opts) {
|
|
|
1614
1699
|
import pc4 from "picocolors";
|
|
1615
1700
|
|
|
1616
1701
|
// src/core/doctor.ts
|
|
1617
|
-
import { existsSync as
|
|
1618
|
-
import { dirname as dirname6, join as
|
|
1702
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
1703
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
1619
1704
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1620
1705
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
1621
1706
|
var TIMEOUT_MS = 2e3;
|
|
@@ -1664,19 +1749,21 @@ function getProviderAgentInfo(provider) {
|
|
|
1664
1749
|
return { agentsDir: ".opencode/agents", ext: ".md" };
|
|
1665
1750
|
case "codex-cli":
|
|
1666
1751
|
return { agentsDir: ".codex/agents", ext: ".toml" };
|
|
1752
|
+
case "grok-cli":
|
|
1753
|
+
return { agentsDir: ".grok/agents", ext: ".md" };
|
|
1667
1754
|
default:
|
|
1668
1755
|
return { agentsDir: ".claude/agents", ext: ".md" };
|
|
1669
1756
|
}
|
|
1670
1757
|
}
|
|
1671
1758
|
function checkAgentFilesAtRoot(agentsRoot, ext) {
|
|
1672
1759
|
return AGENT_NAMES.map((name) => {
|
|
1673
|
-
const filePath =
|
|
1674
|
-
return { name, status:
|
|
1760
|
+
const filePath = join13(agentsRoot, `${name}${ext}`);
|
|
1761
|
+
return { name, status: existsSync11(filePath) ? "ok" : "missing" };
|
|
1675
1762
|
});
|
|
1676
1763
|
}
|
|
1677
1764
|
function checkAgentFiles(cwd2, provider) {
|
|
1678
1765
|
const { agentsDir, ext } = getProviderAgentInfo(provider);
|
|
1679
|
-
return checkAgentFilesAtRoot(
|
|
1766
|
+
return checkAgentFilesAtRoot(join13(cwd2, agentsDir), ext);
|
|
1680
1767
|
}
|
|
1681
1768
|
function getProviderSkillsDir(provider) {
|
|
1682
1769
|
switch (provider) {
|
|
@@ -1686,16 +1773,18 @@ function getProviderSkillsDir(provider) {
|
|
|
1686
1773
|
return ".opencode/skills";
|
|
1687
1774
|
case "codex-cli":
|
|
1688
1775
|
return ".agents/skills";
|
|
1776
|
+
case "grok-cli":
|
|
1777
|
+
return ".grok/skills";
|
|
1689
1778
|
default:
|
|
1690
1779
|
return ".claude/skills";
|
|
1691
1780
|
}
|
|
1692
1781
|
}
|
|
1693
1782
|
function checkSkillsAtRoot(skillsRoot) {
|
|
1694
|
-
const skillSourceBase =
|
|
1783
|
+
const skillSourceBase = join13(__dirname4, "skills");
|
|
1695
1784
|
return SKILL_NAMES.map((name) => {
|
|
1696
|
-
const livePath =
|
|
1697
|
-
const sourcePath =
|
|
1698
|
-
if (!
|
|
1785
|
+
const livePath = join13(skillsRoot, name, "SKILL.md");
|
|
1786
|
+
const sourcePath = join13(skillSourceBase, name, "SKILL.md");
|
|
1787
|
+
if (!existsSync11(livePath)) {
|
|
1699
1788
|
return { name, status: "missing" };
|
|
1700
1789
|
}
|
|
1701
1790
|
try {
|
|
@@ -1709,7 +1798,7 @@ function checkSkillsAtRoot(skillsRoot) {
|
|
|
1709
1798
|
}
|
|
1710
1799
|
function checkSkills(cwd2, provider) {
|
|
1711
1800
|
const skillsDir = getProviderSkillsDir(provider);
|
|
1712
|
-
return checkSkillsAtRoot(
|
|
1801
|
+
return checkSkillsAtRoot(join13(cwd2, skillsDir));
|
|
1713
1802
|
}
|
|
1714
1803
|
async function getDoctorStatus(cwd2) {
|
|
1715
1804
|
const lib = await checkLibVersion();
|
|
@@ -1810,7 +1899,7 @@ async function runDoctor(cwd2) {
|
|
|
1810
1899
|
}
|
|
1811
1900
|
|
|
1812
1901
|
// src/commands/export.ts
|
|
1813
|
-
import { writeFileSync as
|
|
1902
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
1814
1903
|
import pc5 from "picocolors";
|
|
1815
1904
|
async function runExport(cwd2, opts) {
|
|
1816
1905
|
if (!opts.sql && !opts.json) {
|
|
@@ -1824,7 +1913,7 @@ async function runExport(cwd2, opts) {
|
|
|
1824
1913
|
const data = await db.exportJson();
|
|
1825
1914
|
const out = JSON.stringify(data, null, 2) + "\n";
|
|
1826
1915
|
if (opts.output) {
|
|
1827
|
-
|
|
1916
|
+
writeFileSync7(opts.output, out, "utf8");
|
|
1828
1917
|
console.log(pc5.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
|
|
1829
1918
|
} else {
|
|
1830
1919
|
process.stdout.write(out);
|
|
@@ -1841,9 +1930,9 @@ async function runExport(cwd2, opts) {
|
|
|
1841
1930
|
|
|
1842
1931
|
// src/commands/health.ts
|
|
1843
1932
|
import { spawnSync } from "child_process";
|
|
1844
|
-
import { existsSync as
|
|
1933
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1845
1934
|
import { homedir as homedir2 } from "os";
|
|
1846
|
-
import { join as
|
|
1935
|
+
import { join as join14, resolve as resolve6 } from "path";
|
|
1847
1936
|
import pc6 from "picocolors";
|
|
1848
1937
|
function checkLine(label, ok3, message, indent = 0) {
|
|
1849
1938
|
const prefix = label ? pc6.cyan(`[${label}] `) : " ".repeat(indent);
|
|
@@ -1862,7 +1951,7 @@ async function runHealth(cwd2) {
|
|
|
1862
1951
|
let dbOk;
|
|
1863
1952
|
if (config.database.type === "sqlite") {
|
|
1864
1953
|
const dbPath = resolveSqlitePath(config, cwd2, homedir2());
|
|
1865
|
-
dbOk =
|
|
1954
|
+
dbOk = existsSync12(dbPath);
|
|
1866
1955
|
checkLine("checking DB", dbOk, `${dbPath} reachable`);
|
|
1867
1956
|
} else {
|
|
1868
1957
|
dbOk = true;
|
|
@@ -1875,8 +1964,8 @@ async function runHealth(cwd2) {
|
|
|
1875
1964
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1876
1965
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1877
1966
|
const name = agentNames[i];
|
|
1878
|
-
const agentPath =
|
|
1879
|
-
const ok3 =
|
|
1967
|
+
const agentPath = join14(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
|
|
1968
|
+
const ok3 = existsSync12(agentPath);
|
|
1880
1969
|
checkLine(
|
|
1881
1970
|
i === 0 ? "checking agents" : null,
|
|
1882
1971
|
ok3,
|
|
@@ -1887,8 +1976,8 @@ async function runHealth(cwd2) {
|
|
|
1887
1976
|
}
|
|
1888
1977
|
if (config.tools.mcp.enabled) {
|
|
1889
1978
|
const mcpFile = providerFiles.mcpFile;
|
|
1890
|
-
const mcpPath =
|
|
1891
|
-
const mcpOk =
|
|
1979
|
+
const mcpPath = resolve6(cwd2, mcpFile);
|
|
1980
|
+
const mcpOk = existsSync12(mcpPath);
|
|
1892
1981
|
checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
|
|
1893
1982
|
if (!mcpOk) allOk = false;
|
|
1894
1983
|
}
|
|
@@ -1897,8 +1986,8 @@ async function runHealth(cwd2) {
|
|
|
1897
1986
|
console.error(pc6.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
|
|
1898
1987
|
process.exit(1);
|
|
1899
1988
|
}
|
|
1900
|
-
const scriptPath =
|
|
1901
|
-
if (!
|
|
1989
|
+
const scriptPath = resolve6(cwd2, config.health.scriptPath);
|
|
1990
|
+
if (!existsSync12(scriptPath)) {
|
|
1902
1991
|
console.error(pc6.red(`\u2717 health.sh not found: ${scriptPath}`));
|
|
1903
1992
|
console.error(" Run ahk init first.");
|
|
1904
1993
|
process.exit(1);
|
|
@@ -1928,14 +2017,16 @@ function getProviderHealthFiles(provider) {
|
|
|
1928
2017
|
return { agentsDir: ".opencode/agents", agentExtension: ".md", mcpFile: "opencode.json" };
|
|
1929
2018
|
case "codex-cli":
|
|
1930
2019
|
return { agentsDir: ".codex/agents", agentExtension: ".toml", mcpFile: ".codex/config.toml" };
|
|
2020
|
+
case "grok-cli":
|
|
2021
|
+
return { agentsDir: ".grok/agents", agentExtension: ".md", mcpFile: ".grok/config.toml" };
|
|
1931
2022
|
default:
|
|
1932
2023
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1933
2024
|
}
|
|
1934
2025
|
}
|
|
1935
2026
|
|
|
1936
2027
|
// src/commands/init.ts
|
|
1937
|
-
import { existsSync as
|
|
1938
|
-
import { join as
|
|
2028
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2029
|
+
import { join as join16 } from "path";
|
|
1939
2030
|
import * as p3 from "@clack/prompts";
|
|
1940
2031
|
import pc8 from "picocolors";
|
|
1941
2032
|
|
|
@@ -1990,13 +2081,13 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1990
2081
|
|
|
1991
2082
|
// src/commands/init-helpers.ts
|
|
1992
2083
|
import { randomUUID } from "crypto";
|
|
1993
|
-
import { existsSync as
|
|
1994
|
-
import { join as
|
|
2084
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
2085
|
+
import { join as join15 } from "path";
|
|
1995
2086
|
import pc7 from "picocolors";
|
|
1996
2087
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1997
2088
|
try {
|
|
1998
|
-
const pkgPath2 =
|
|
1999
|
-
if (!
|
|
2089
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2090
|
+
if (!existsSync13(pkgPath2)) return null;
|
|
2000
2091
|
const content = readFileSync8(pkgPath2, "utf8");
|
|
2001
2092
|
const pkg2 = JSON.parse(content);
|
|
2002
2093
|
const name = pkg2?.name;
|
|
@@ -2009,9 +2100,9 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
2009
2100
|
function detectConfigExtension(cwd2) {
|
|
2010
2101
|
if (!isLocalInstallSatisfied(cwd2)) return "json";
|
|
2011
2102
|
try {
|
|
2012
|
-
if (
|
|
2013
|
-
const pkgPath2 =
|
|
2014
|
-
if (!
|
|
2103
|
+
if (existsSync13(join15(cwd2, "tsconfig.json"))) return "ts";
|
|
2104
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2105
|
+
if (!existsSync13(pkgPath2)) return "mjs";
|
|
2015
2106
|
const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
|
|
2016
2107
|
if (pkg2?.type === "module") return "mjs";
|
|
2017
2108
|
} catch {
|
|
@@ -2092,10 +2183,10 @@ function printWelcomeMessage(projectName) {
|
|
|
2092
2183
|
|
|
2093
2184
|
// src/commands/init.ts
|
|
2094
2185
|
async function reconcileFeatureList(db, installDir, storageDir, firstTask) {
|
|
2095
|
-
const featureListPath =
|
|
2186
|
+
const featureListPath = join16(installDir, storageDir, "feature_list.json");
|
|
2096
2187
|
let existingSeeds = [];
|
|
2097
2188
|
let parseFailed = false;
|
|
2098
|
-
if (
|
|
2189
|
+
if (existsSync14(featureListPath)) {
|
|
2099
2190
|
try {
|
|
2100
2191
|
const parsed = JSON.parse(readFileSync9(featureListPath, "utf8"));
|
|
2101
2192
|
if (!Array.isArray(parsed)) throw new Error("feature_list.json is not a JSON array");
|
|
@@ -2169,7 +2260,7 @@ async function runInit(cwd2, flags) {
|
|
|
2169
2260
|
return val;
|
|
2170
2261
|
}, initDescriptionSchema);
|
|
2171
2262
|
let provider;
|
|
2172
|
-
if (flags.provider && ["claude-code", "opencode"].includes(flags.provider)) {
|
|
2263
|
+
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
2173
2264
|
provider = flags.provider;
|
|
2174
2265
|
} else {
|
|
2175
2266
|
const val = await p3.select({
|
|
@@ -2177,7 +2268,8 @@ async function runInit(cwd2, flags) {
|
|
|
2177
2268
|
options: [
|
|
2178
2269
|
{ value: "opencode", label: "OpenCode" },
|
|
2179
2270
|
{ value: "claude-code", label: "Claude Code" },
|
|
2180
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2271
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2272
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
2181
2273
|
]
|
|
2182
2274
|
});
|
|
2183
2275
|
if (p3.isCancel(val)) {
|
|
@@ -2332,14 +2424,14 @@ async function runInit(cwd2, flags) {
|
|
|
2332
2424
|
scope: config.storage.scope,
|
|
2333
2425
|
projectId: config.storage.projectId
|
|
2334
2426
|
});
|
|
2335
|
-
|
|
2336
|
-
|
|
2427
|
+
writeFileSync8(join16(installDir, configFileName), configContent, "utf8");
|
|
2428
|
+
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2337
2429
|
const db = await openDB(config, installDir);
|
|
2338
2430
|
await db.writeStorageState(installDir);
|
|
2339
2431
|
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2340
2432
|
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2341
2433
|
if (parseFailed) {
|
|
2342
|
-
featureListParseFailedPath =
|
|
2434
|
+
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
2343
2435
|
}
|
|
2344
2436
|
await db.close();
|
|
2345
2437
|
spinner6.stop("");
|
|
@@ -2354,8 +2446,13 @@ async function runInit(cwd2, flags) {
|
|
|
2354
2446
|
);
|
|
2355
2447
|
}
|
|
2356
2448
|
console.log(pc8.green("\u2713 Scaffolded harness in current directory"));
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2449
|
+
const PROVIDER_SUMMARY_INFO = {
|
|
2450
|
+
"claude-code": { agentsDir: ".claude/agents/", mcpFile: ".mcp.json" },
|
|
2451
|
+
opencode: { agentsDir: ".opencode/agents/", mcpFile: "./opencode.json" },
|
|
2452
|
+
"codex-cli": { agentsDir: ".codex/agents/", mcpFile: ".codex/config.toml" },
|
|
2453
|
+
"grok-cli": { agentsDir: ".grok/agents/", mcpFile: ".grok/config.toml" }
|
|
2454
|
+
};
|
|
2455
|
+
const { agentsDir, mcpFile } = PROVIDER_SUMMARY_INFO[provider];
|
|
2359
2456
|
console.log("");
|
|
2360
2457
|
console.log(pc8.green(`\u2713 agent-harness-kit.config.${configExt}`));
|
|
2361
2458
|
console.log(pc8.green("\u2713 AGENTS.md"));
|
|
@@ -2397,7 +2494,7 @@ import pc9 from "picocolors";
|
|
|
2397
2494
|
async function runMigrate(cwd2, opts) {
|
|
2398
2495
|
const config = await loadConfig(cwd2);
|
|
2399
2496
|
let target;
|
|
2400
|
-
if (opts.to && ["claude-code", "opencode", "codex-cli"].includes(opts.to)) {
|
|
2497
|
+
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2401
2498
|
target = opts.to;
|
|
2402
2499
|
} else {
|
|
2403
2500
|
const val = await p4.select({
|
|
@@ -2405,7 +2502,8 @@ async function runMigrate(cwd2, opts) {
|
|
|
2405
2502
|
options: [
|
|
2406
2503
|
{ value: "claude-code", label: "Claude Code" },
|
|
2407
2504
|
{ value: "opencode", label: "OpenCode" },
|
|
2408
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2505
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2506
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
2409
2507
|
]
|
|
2410
2508
|
});
|
|
2411
2509
|
if (p4.isCancel(val)) {
|
|
@@ -2434,9 +2532,9 @@ async function runMigrate(cwd2, opts) {
|
|
|
2434
2532
|
}
|
|
2435
2533
|
|
|
2436
2534
|
// src/commands/migrate-storage.ts
|
|
2437
|
-
import { copyFileSync, existsSync as
|
|
2535
|
+
import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
|
|
2438
2536
|
import { homedir as homedir3 } from "os";
|
|
2439
|
-
import { dirname as dirname7, join as
|
|
2537
|
+
import { dirname as dirname7, join as join17, resolve as resolve7 } from "path";
|
|
2440
2538
|
import pc10 from "picocolors";
|
|
2441
2539
|
function log5(msg) {
|
|
2442
2540
|
console.log(msg);
|
|
@@ -2448,17 +2546,17 @@ function defaultMarkdownPathForConfig(config) {
|
|
|
2448
2546
|
return config.storage.scope === "local" ? config.storage.markdownFallback.path : DEFAULT_MARKDOWN_PATH;
|
|
2449
2547
|
}
|
|
2450
2548
|
function currentMdPathForScope(scope, config, cwd2, homeDir) {
|
|
2451
|
-
return scope === "global" ?
|
|
2549
|
+
return scope === "global" ? join17(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve7(cwd2, defaultMarkdownPathForConfig(config));
|
|
2452
2550
|
}
|
|
2453
2551
|
function defaultSqlitePathForConfig(config) {
|
|
2454
2552
|
return config.storage.scope === "local" && config.database.type === "sqlite" ? config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH : DEFAULT_SQLITE_PATH;
|
|
2455
2553
|
}
|
|
2456
2554
|
async function backupDestination(cwd2, storageDir, data) {
|
|
2457
|
-
const backupsDir =
|
|
2458
|
-
const path =
|
|
2555
|
+
const backupsDir = resolve7(cwd2, storageDir, "backups");
|
|
2556
|
+
const path = join17(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
2459
2557
|
try {
|
|
2460
|
-
|
|
2461
|
-
|
|
2558
|
+
mkdirSync8(backupsDir, { recursive: true });
|
|
2559
|
+
writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
2462
2560
|
} catch (err) {
|
|
2463
2561
|
throw new Error(
|
|
2464
2562
|
`Could not write destination backup to ${path} (${err instanceof Error ? err.message : String(err)}). Aborting migration WITHOUT touching the destination \u2014 nothing was overwritten.`
|
|
@@ -2467,14 +2565,14 @@ async function backupDestination(cwd2, storageDir, data) {
|
|
|
2467
2565
|
return path;
|
|
2468
2566
|
}
|
|
2469
2567
|
function copySqliteFile(srcPath, destPath) {
|
|
2470
|
-
|
|
2568
|
+
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
2471
2569
|
copyFileSync(srcPath, destPath);
|
|
2472
2570
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2473
|
-
if (
|
|
2571
|
+
if (existsSync15(`${srcPath}${suffix}`)) {
|
|
2474
2572
|
copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
|
|
2475
2573
|
}
|
|
2476
2574
|
}
|
|
2477
|
-
if (!
|
|
2575
|
+
if (!existsSync15(destPath)) {
|
|
2478
2576
|
throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
|
|
2479
2577
|
}
|
|
2480
2578
|
}
|
|
@@ -2533,7 +2631,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2533
2631
|
return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
|
|
2534
2632
|
}
|
|
2535
2633
|
async function probeTaskCount(dbPath) {
|
|
2536
|
-
if (!
|
|
2634
|
+
if (!existsSync15(dbPath)) return 0;
|
|
2537
2635
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2538
2636
|
const driver = new SQLiteDriver(dbPath);
|
|
2539
2637
|
try {
|
|
@@ -2550,10 +2648,10 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2550
2648
|
const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
|
|
2551
2649
|
const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
|
|
2552
2650
|
const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
|
|
2553
|
-
if (!
|
|
2651
|
+
if (!existsSync15(srcDb)) {
|
|
2554
2652
|
fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
|
|
2555
2653
|
}
|
|
2556
|
-
const destExists =
|
|
2654
|
+
const destExists = existsSync15(destDb);
|
|
2557
2655
|
if (destExists) {
|
|
2558
2656
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2559
2657
|
const destDriver = new SQLiteDriver(destDb);
|
|
@@ -2591,15 +2689,15 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2591
2689
|
}
|
|
2592
2690
|
copySqliteFile(srcDb, destDb);
|
|
2593
2691
|
log5(pc10.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2692
|
+
if (existsSync15(srcMd)) {
|
|
2693
|
+
mkdirSync8(dirname7(destMd), { recursive: true });
|
|
2596
2694
|
copyFileSync(srcMd, destMd);
|
|
2597
2695
|
log5(pc10.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
|
|
2598
2696
|
}
|
|
2599
2697
|
rmSync(srcDb, { force: true });
|
|
2600
2698
|
rmSync(`${srcDb}-wal`, { force: true });
|
|
2601
2699
|
rmSync(`${srcDb}-shm`, { force: true });
|
|
2602
|
-
if (
|
|
2700
|
+
if (existsSync15(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
|
|
2603
2701
|
const db = await openDB(config, cwd2, homeDir);
|
|
2604
2702
|
try {
|
|
2605
2703
|
await db.writeStorageState(cwd2);
|
|
@@ -2611,7 +2709,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2611
2709
|
async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
2612
2710
|
const sqlitePath = defaultSqlitePathForConfig(config);
|
|
2613
2711
|
const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
|
|
2614
|
-
if (!
|
|
2712
|
+
if (!existsSync15(srcPath)) {
|
|
2615
2713
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2616
2714
|
}
|
|
2617
2715
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
@@ -2674,16 +2772,29 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2674
2772
|
}
|
|
2675
2773
|
|
|
2676
2774
|
// src/commands/reset.ts
|
|
2677
|
-
import { existsSync as
|
|
2775
|
+
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2678
2776
|
import { homedir as homedir4 } from "os";
|
|
2679
|
-
import { join as
|
|
2777
|
+
import { join as join18, resolve as resolve8 } from "path";
|
|
2680
2778
|
import * as p5 from "@clack/prompts";
|
|
2681
2779
|
import pc11 from "picocolors";
|
|
2682
2780
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2781
|
+
var PROVIDER_AGENT_DIRS = {
|
|
2782
|
+
"claude-code": ".claude/agents",
|
|
2783
|
+
opencode: ".opencode/agents",
|
|
2784
|
+
"codex-cli": ".codex/agents",
|
|
2785
|
+
"grok-cli": ".grok/agents"
|
|
2786
|
+
};
|
|
2787
|
+
var PROVIDER_AGENT_EXT = {
|
|
2788
|
+
"claude-code": ".md",
|
|
2789
|
+
opencode: ".md",
|
|
2790
|
+
"codex-cli": ".toml",
|
|
2791
|
+
"grok-cli": ".md"
|
|
2792
|
+
};
|
|
2683
2793
|
async function resetAgentMds(cwd2, provider) {
|
|
2684
|
-
const agentDir = provider
|
|
2685
|
-
const agentDirPath =
|
|
2686
|
-
|
|
2794
|
+
const agentDir = PROVIDER_AGENT_DIRS[provider];
|
|
2795
|
+
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2796
|
+
const agentExt = PROVIDER_AGENT_EXT[provider];
|
|
2797
|
+
if (!existsSync16(agentDirPath)) {
|
|
2687
2798
|
console.log(pc11.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2688
2799
|
return;
|
|
2689
2800
|
}
|
|
@@ -2691,7 +2802,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2691
2802
|
try {
|
|
2692
2803
|
const files = readdirSync(agentDirPath);
|
|
2693
2804
|
for (const f of files) {
|
|
2694
|
-
if (f.endsWith(
|
|
2805
|
+
if (f.endsWith(agentExt) && AGENT_MD_FILES.includes(f.replace(agentExt, ""))) {
|
|
2695
2806
|
existingFiles.push(f);
|
|
2696
2807
|
}
|
|
2697
2808
|
}
|
|
@@ -2714,7 +2825,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2714
2825
|
}
|
|
2715
2826
|
if (confirm3) {
|
|
2716
2827
|
try {
|
|
2717
|
-
const filePath =
|
|
2828
|
+
const filePath = join18(agentDirPath, file);
|
|
2718
2829
|
rmSync2(filePath, { force: true });
|
|
2719
2830
|
console.log(pc11.green(` Removed ${file}`));
|
|
2720
2831
|
} catch {
|
|
@@ -2735,11 +2846,11 @@ async function runReset(cwd2, opts) {
|
|
|
2735
2846
|
}
|
|
2736
2847
|
const storageDir = config.storage.dir || ".harness";
|
|
2737
2848
|
const dbPath = config.database.type === "sqlite" ? resolveSqlitePath(config, cwd2, homedir4()) : null;
|
|
2738
|
-
const featureListPath =
|
|
2849
|
+
const featureListPath = resolve8(cwd2, storageDir, "feature_list.json");
|
|
2739
2850
|
let resetDb = false;
|
|
2740
2851
|
let resetFeatureList = false;
|
|
2741
2852
|
let resetAgentMdsFlag = false;
|
|
2742
|
-
if (dbPath &&
|
|
2853
|
+
if (dbPath && existsSync16(dbPath)) {
|
|
2743
2854
|
if (opts.force) {
|
|
2744
2855
|
resetDb = true;
|
|
2745
2856
|
} else {
|
|
@@ -2761,7 +2872,7 @@ async function runReset(cwd2, opts) {
|
|
|
2761
2872
|
} else if (!dbPath) {
|
|
2762
2873
|
console.log(pc11.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2763
2874
|
}
|
|
2764
|
-
if (
|
|
2875
|
+
if (existsSync16(featureListPath)) {
|
|
2765
2876
|
if (opts.force) {
|
|
2766
2877
|
resetFeatureList = true;
|
|
2767
2878
|
} else {
|
|
@@ -2810,8 +2921,8 @@ async function runReset(cwd2, opts) {
|
|
|
2810
2921
|
}
|
|
2811
2922
|
|
|
2812
2923
|
// src/core/mcp-server.ts
|
|
2813
|
-
import { existsSync as
|
|
2814
|
-
import { join as
|
|
2924
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
2925
|
+
import { join as join20, resolve as resolve9 } from "path";
|
|
2815
2926
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2816
2927
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2817
2928
|
import {
|
|
@@ -2820,8 +2931,8 @@ import {
|
|
|
2820
2931
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2821
2932
|
|
|
2822
2933
|
// src/core/permissions-check.ts
|
|
2823
|
-
import { existsSync as
|
|
2824
|
-
import { join as
|
|
2934
|
+
import { existsSync as existsSync17 } from "fs";
|
|
2935
|
+
import { join as join19 } from "path";
|
|
2825
2936
|
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2826
2937
|
function checkPermissionsSync(cwd2, config) {
|
|
2827
2938
|
if (config.provider !== "claude-code") {
|
|
@@ -2830,8 +2941,8 @@ function checkPermissionsSync(cwd2, config) {
|
|
|
2830
2941
|
const agents = {};
|
|
2831
2942
|
let in_sync = true;
|
|
2832
2943
|
for (const agent of AGENTS) {
|
|
2833
|
-
const filePath =
|
|
2834
|
-
const exists =
|
|
2944
|
+
const filePath = join19(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2945
|
+
const exists = existsSync17(filePath);
|
|
2835
2946
|
if (!exists) in_sync = false;
|
|
2836
2947
|
agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
|
|
2837
2948
|
}
|
|
@@ -3129,7 +3240,7 @@ var TOOLS = [
|
|
|
3129
3240
|
];
|
|
3130
3241
|
async function startMcpServer(config, cwd2) {
|
|
3131
3242
|
const db = await openDB(config, cwd2);
|
|
3132
|
-
const docsPath =
|
|
3243
|
+
const docsPath = resolve9(cwd2, config.project.docsPath);
|
|
3133
3244
|
const server = new Server(
|
|
3134
3245
|
{ name: "agent-harness-kit", version: VERSION },
|
|
3135
3246
|
{ capabilities: { tools: {} } }
|
|
@@ -3292,8 +3403,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3292
3403
|
return ok2(JSON.stringify(result));
|
|
3293
3404
|
}
|
|
3294
3405
|
case "deps.snapshot": {
|
|
3295
|
-
const pkgPath2 =
|
|
3296
|
-
if (!
|
|
3406
|
+
const pkgPath2 = join20(cwd2, "package.json");
|
|
3407
|
+
if (!existsSync18(pkgPath2)) {
|
|
3297
3408
|
return ok2("package.json not found in project root", true);
|
|
3298
3409
|
}
|
|
3299
3410
|
const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
|
|
@@ -3302,9 +3413,9 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3302
3413
|
dependencies: pkg2.dependencies ?? {},
|
|
3303
3414
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3304
3415
|
};
|
|
3305
|
-
const harnessDir =
|
|
3306
|
-
|
|
3307
|
-
|
|
3416
|
+
const harnessDir = join20(cwd2, ".harness");
|
|
3417
|
+
mkdirSync9(harnessDir, { recursive: true });
|
|
3418
|
+
writeFileSync10(join20(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3308
3419
|
return ok2(
|
|
3309
3420
|
JSON.stringify({
|
|
3310
3421
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3313,12 +3424,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3313
3424
|
);
|
|
3314
3425
|
}
|
|
3315
3426
|
case "deps.check": {
|
|
3316
|
-
const pkgPath2 =
|
|
3317
|
-
const lockPath =
|
|
3318
|
-
if (!
|
|
3427
|
+
const pkgPath2 = join20(cwd2, "package.json");
|
|
3428
|
+
const lockPath = join20(cwd2, ".harness", "deps-lock.json");
|
|
3429
|
+
if (!existsSync18(pkgPath2)) {
|
|
3319
3430
|
return ok2("package.json not found in project root", true);
|
|
3320
3431
|
}
|
|
3321
|
-
if (!
|
|
3432
|
+
if (!existsSync18(lockPath)) {
|
|
3322
3433
|
return ok2(
|
|
3323
3434
|
JSON.stringify({
|
|
3324
3435
|
status: "no-snapshot",
|
|
@@ -3420,7 +3531,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3420
3531
|
const files = [];
|
|
3421
3532
|
try {
|
|
3422
3533
|
for (const entry of readdirSync2(dir)) {
|
|
3423
|
-
const full =
|
|
3534
|
+
const full = join20(dir, entry);
|
|
3424
3535
|
const stat = statSync(full);
|
|
3425
3536
|
if (stat.isDirectory()) {
|
|
3426
3537
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3550,13 +3661,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3550
3661
|
}
|
|
3551
3662
|
|
|
3552
3663
|
// src/commands/sync.ts
|
|
3553
|
-
import { existsSync as
|
|
3554
|
-
import { join as
|
|
3664
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
3665
|
+
import { join as join21, resolve as resolve10 } from "path";
|
|
3555
3666
|
import pc13 from "picocolors";
|
|
3556
3667
|
async function runSync(cwd2, opts) {
|
|
3557
3668
|
const config = await loadConfig(cwd2);
|
|
3558
3669
|
const direction = opts.direction ?? "both";
|
|
3559
|
-
const featureListPath =
|
|
3670
|
+
const featureListPath = resolve10(join21(cwd2, config.storage.dir, "feature_list.json"));
|
|
3560
3671
|
const db = await openDB(config, cwd2);
|
|
3561
3672
|
try {
|
|
3562
3673
|
if (direction === "in" || direction === "both") {
|
|
@@ -3570,7 +3681,7 @@ async function runSync(cwd2, opts) {
|
|
|
3570
3681
|
}
|
|
3571
3682
|
}
|
|
3572
3683
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3573
|
-
if (!
|
|
3684
|
+
if (!existsSync19(featureListPath)) {
|
|
3574
3685
|
console.log(pc13.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3575
3686
|
return;
|
|
3576
3687
|
}
|
|
@@ -3661,14 +3772,14 @@ async function runTaskAdd(cwd2) {
|
|
|
3661
3772
|
|
|
3662
3773
|
// src/commands/task/done.ts
|
|
3663
3774
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3664
|
-
import { existsSync as
|
|
3665
|
-
import { resolve as
|
|
3775
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3776
|
+
import { resolve as resolve11 } from "path";
|
|
3666
3777
|
import pc15 from "picocolors";
|
|
3667
3778
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3668
3779
|
const config = await loadConfig(cwd2);
|
|
3669
3780
|
if (config.health.required) {
|
|
3670
|
-
const scriptPath =
|
|
3671
|
-
if (
|
|
3781
|
+
const scriptPath = resolve11(cwd2, config.health.scriptPath);
|
|
3782
|
+
if (existsSync20(scriptPath)) {
|
|
3672
3783
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3673
3784
|
if (result.status !== 0) {
|
|
3674
3785
|
console.error(pc15.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
@@ -3841,7 +3952,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3841
3952
|
|
|
3842
3953
|
// src/core/path-probe.ts
|
|
3843
3954
|
import { accessSync, constants, readdirSync as readdirSync3 } from "fs";
|
|
3844
|
-
import { join as
|
|
3955
|
+
import { join as join22 } from "path";
|
|
3845
3956
|
import pc18 from "picocolors";
|
|
3846
3957
|
var DEFAULT_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
3847
3958
|
function defaultIsExecutable(filePath) {
|
|
@@ -3886,7 +3997,7 @@ function resolveOnPath(name, options = {}) {
|
|
|
3886
3997
|
}
|
|
3887
3998
|
for (const dir of dirs) {
|
|
3888
3999
|
try {
|
|
3889
|
-
if (isExecutable(
|
|
4000
|
+
if (isExecutable(join22(dir, name))) return true;
|
|
3890
4001
|
} catch {
|
|
3891
4002
|
continue;
|
|
3892
4003
|
}
|
|
@@ -3910,15 +4021,15 @@ import pc19 from "picocolors";
|
|
|
3910
4021
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
3911
4022
|
var TIMEOUT_MS2 = 2500;
|
|
3912
4023
|
function checkForUpdate(currentVersion) {
|
|
3913
|
-
return new Promise((
|
|
3914
|
-
const timer = setTimeout(() =>
|
|
4024
|
+
return new Promise((resolve12) => {
|
|
4025
|
+
const timer = setTimeout(() => resolve12(null), TIMEOUT_MS2);
|
|
3915
4026
|
fetch(REGISTRY_URL2).then((res) => res.json()).then((data) => {
|
|
3916
4027
|
clearTimeout(timer);
|
|
3917
4028
|
const latest = data.version;
|
|
3918
|
-
|
|
4029
|
+
resolve12(isNewer2(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
3919
4030
|
}).catch(() => {
|
|
3920
4031
|
clearTimeout(timer);
|
|
3921
|
-
|
|
4032
|
+
resolve12(null);
|
|
3922
4033
|
});
|
|
3923
4034
|
});
|
|
3924
4035
|
}
|
|
@@ -3955,7 +4066,7 @@ function parsePort(raw) {
|
|
|
3955
4066
|
var updateCheck = checkForUpdate(pkg.version);
|
|
3956
4067
|
var program = new Command();
|
|
3957
4068
|
program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for multi-agent harness systems").version(pkg.version, "-v, --version");
|
|
3958
|
-
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
4069
|
+
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode | codex-cli | grok-cli (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
3959
4070
|
await runInit(cwd, opts);
|
|
3960
4071
|
});
|
|
3961
4072
|
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").option(
|
|
@@ -3993,7 +4104,7 @@ program.command("dashboard").description("Open web dashboard to visualize harnes
|
|
|
3993
4104
|
await runDashboard(cwd, { port: opts.port, open: opts.open });
|
|
3994
4105
|
});
|
|
3995
4106
|
var migrate = program.command("migrate").description("Migrate provider files to a different provider, or migrate harness storage (see subcommands)");
|
|
3996
|
-
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli").action(async (opts) => {
|
|
4107
|
+
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli | grok-cli").action(async (opts) => {
|
|
3997
4108
|
await runMigrate(cwd, opts);
|
|
3998
4109
|
});
|
|
3999
4110
|
migrate.command("storage").description(
|
|
@@ -4009,7 +4120,7 @@ migrate.command("storage").description(
|
|
|
4009
4120
|
program.command("export").description("Export the database").option("--sql", "SQL dump").option("--json", "JSON export of tasks and actions").option("--output <path>", "Output file path (default: stdout)").action(async (opts) => {
|
|
4010
4121
|
await runExport(cwd, opts);
|
|
4011
4122
|
});
|
|
4012
|
-
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) => {
|
|
4123
|
+
program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode|codex-cli|grok-cli>", "Reset agent MD files for specified provider").action(async (opts) => {
|
|
4013
4124
|
await runReset(cwd, opts);
|
|
4014
4125
|
});
|
|
4015
4126
|
program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
|