@cardor/agent-harness-kit 0.16.10 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-templates/builder.md +13 -11
- package/dist/agent-templates/explorer.md +6 -6
- package/dist/agent-templates/lead.md +5 -5
- package/dist/agent-templates/reviewer.md +8 -11
- package/dist/cli.js +234 -213
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -149,14 +149,17 @@ If it exits non-zero, stop and report the issue. Do not proceed with tasks until
|
|
|
149
149
|
The harness exposes tools via MCP server on port ${port}. Use these instead of reading files directly.
|
|
150
150
|
|
|
151
151
|
\`\`\`
|
|
152
|
-
actions.start
|
|
153
|
-
actions.write
|
|
154
|
-
actions.
|
|
155
|
-
actions.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
tasks.
|
|
159
|
-
|
|
152
|
+
actions.start taskId agent \u2192 start an action, returns actionId
|
|
153
|
+
actions.write actionId section text \u2192 record a section (result, blockers, ...)
|
|
154
|
+
actions.record_tool actionId toolName [argsJson] [summary] \u2192 log a tool call to the Tools dashboard
|
|
155
|
+
actions.record_file actionId filePath operation [notes] \u2192 log a file touch to the Files dashboard
|
|
156
|
+
actions.complete actionId summary \u2192 close the action
|
|
157
|
+
actions.get taskId \u2192 full action history for a task
|
|
158
|
+
tasks.get [status] \u2192 list tasks (pending | in_progress | done | blocked)
|
|
159
|
+
tasks.claim id \u2192 atomically claim a pending task
|
|
160
|
+
tasks.update id status \u2192 change task status
|
|
161
|
+
tasks.acceptance.update criterionId \u2192 mark an acceptance criterion as met
|
|
162
|
+
docs.search query \u2192 search ${docsPath} for relevant content
|
|
160
163
|
\`\`\`
|
|
161
164
|
|
|
162
165
|
## Workflow
|
|
@@ -169,7 +172,8 @@ docs.search query \u2192 search ${docsPath} for relevant c
|
|
|
169
172
|
|
|
170
173
|
2. WORK (lead \u2192 explorer \u2192 builder \u2192 reviewer)
|
|
171
174
|
- Each agent calls actions.start(taskId, agentName) \u2192 actionId
|
|
172
|
-
-
|
|
175
|
+
- After EVERY tool call: actions.record_tool(actionId, toolName, args, summary)
|
|
176
|
+
- After EVERY file change: actions.record_file(actionId, filePath, operation, notes)
|
|
173
177
|
- Closes with actions.complete(actionId, summary)
|
|
174
178
|
|
|
175
179
|
3. CLOSE
|
|
@@ -1161,12 +1165,27 @@ async function runHealth(cwd2) {
|
|
|
1161
1165
|
// src/commands/init.ts
|
|
1162
1166
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
1163
1167
|
import { homedir } from "os";
|
|
1164
|
-
import { join as
|
|
1168
|
+
import { join as join10 } from "path";
|
|
1165
1169
|
import * as p2 from "@clack/prompts";
|
|
1166
1170
|
import pc6 from "picocolors";
|
|
1167
1171
|
|
|
1168
1172
|
// src/commands/init-helpers.ts
|
|
1173
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
1174
|
+
import { join as join9 } from "path";
|
|
1169
1175
|
import pc5 from "picocolors";
|
|
1176
|
+
function readProjectNameFromPackageJson(cwd2) {
|
|
1177
|
+
try {
|
|
1178
|
+
const pkgPath2 = join9(cwd2, "package.json");
|
|
1179
|
+
if (!existsSync7(pkgPath2)) return null;
|
|
1180
|
+
const content = readFileSync5(pkgPath2, "utf8");
|
|
1181
|
+
const pkg2 = JSON.parse(content);
|
|
1182
|
+
const name = pkg2?.name;
|
|
1183
|
+
if (typeof name === "string" && name.trim()) return name.trim();
|
|
1184
|
+
return null;
|
|
1185
|
+
} catch {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1170
1189
|
function applyConfigDefaults(params) {
|
|
1171
1190
|
return {
|
|
1172
1191
|
provider: params.provider,
|
|
@@ -1241,7 +1260,8 @@ function printWelcomeMessage(projectName) {
|
|
|
1241
1260
|
|
|
1242
1261
|
// src/commands/init.ts
|
|
1243
1262
|
async function runInit(cwd2, flags) {
|
|
1244
|
-
const
|
|
1263
|
+
const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
|
|
1264
|
+
const projectName = detectedName || "my-project";
|
|
1245
1265
|
printWelcomeMessage(projectName);
|
|
1246
1266
|
let name;
|
|
1247
1267
|
if (flags.name) {
|
|
@@ -1250,6 +1270,7 @@ async function runInit(cwd2, flags) {
|
|
|
1250
1270
|
const val = await p2.text({
|
|
1251
1271
|
message: "Project name",
|
|
1252
1272
|
placeholder: "my-app",
|
|
1273
|
+
...detectedName && { initialValue: detectedName },
|
|
1253
1274
|
validate: (v) => v.trim() ? void 0 : "Project name is required"
|
|
1254
1275
|
});
|
|
1255
1276
|
if (p2.isCancel(val)) {
|
|
@@ -1373,9 +1394,9 @@ async function runInit(cwd2, flags) {
|
|
|
1373
1394
|
let installDir = cwd2;
|
|
1374
1395
|
if (globalInstallation) {
|
|
1375
1396
|
if (provider === "claude-code") {
|
|
1376
|
-
installDir =
|
|
1397
|
+
installDir = join10(homedir(), ".claude");
|
|
1377
1398
|
} else {
|
|
1378
|
-
installDir =
|
|
1399
|
+
installDir = join10(homedir(), ".config", "opencode");
|
|
1379
1400
|
}
|
|
1380
1401
|
}
|
|
1381
1402
|
const configContent = configTs({
|
|
@@ -1386,8 +1407,8 @@ async function runInit(cwd2, flags) {
|
|
|
1386
1407
|
tasksAdapter,
|
|
1387
1408
|
port: config.tools.mcp.port
|
|
1388
1409
|
});
|
|
1389
|
-
writeFileSync7(
|
|
1390
|
-
mkdirSync6(
|
|
1410
|
+
writeFileSync7(join10(installDir, "agent-harness-kit.config.ts"), configContent, "utf8");
|
|
1411
|
+
mkdirSync6(join10(installDir, config.storage.dir), { recursive: true });
|
|
1391
1412
|
const db = openDB(config, installDir);
|
|
1392
1413
|
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
1393
1414
|
if (firstTask) {
|
|
@@ -1468,9 +1489,137 @@ async function runMigrate(cwd2, opts) {
|
|
|
1468
1489
|
}
|
|
1469
1490
|
}
|
|
1470
1491
|
|
|
1492
|
+
// src/commands/reset.ts
|
|
1493
|
+
import { existsSync as existsSync8, readdirSync, rmSync } from "fs";
|
|
1494
|
+
import { join as join11, resolve as resolve7 } from "path";
|
|
1495
|
+
import * as p4 from "@clack/prompts";
|
|
1496
|
+
import pc8 from "picocolors";
|
|
1497
|
+
async function resetAgentMds(cwd2, provider) {
|
|
1498
|
+
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
1499
|
+
const agentDirPath = resolve7(cwd2, agentDir);
|
|
1500
|
+
if (!existsSync8(agentDirPath)) {
|
|
1501
|
+
console.log(pc8.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const existingFiles = [];
|
|
1505
|
+
try {
|
|
1506
|
+
const files = readdirSync(agentDirPath);
|
|
1507
|
+
for (const f of files) {
|
|
1508
|
+
if (f.endsWith(".md")) {
|
|
1509
|
+
existingFiles.push(f);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
} catch {
|
|
1513
|
+
console.log(pc8.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
if (existingFiles.length === 0) {
|
|
1517
|
+
console.log(pc8.yellow(` No agent MD files found in ${agentDir}/`));
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
for (const file of existingFiles) {
|
|
1521
|
+
const confirm3 = await p4.confirm({
|
|
1522
|
+
message: `Remove ${file}?`,
|
|
1523
|
+
initialValue: true
|
|
1524
|
+
});
|
|
1525
|
+
if (p4.isCancel(confirm3)) {
|
|
1526
|
+
console.log(pc8.red(" Cancelled by user."));
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (confirm3) {
|
|
1530
|
+
try {
|
|
1531
|
+
const filePath = join11(agentDirPath, file);
|
|
1532
|
+
rmSync(filePath, { force: true });
|
|
1533
|
+
console.log(pc8.green(` Removed ${file}`));
|
|
1534
|
+
} catch {
|
|
1535
|
+
console.error(pc8.red(` Failed to remove ${file}`));
|
|
1536
|
+
}
|
|
1537
|
+
} else {
|
|
1538
|
+
console.log(pc8.cyan(` Skipped ${file}`));
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
async function runReset(cwd2, opts) {
|
|
1543
|
+
let config;
|
|
1544
|
+
try {
|
|
1545
|
+
config = await loadConfig(cwd2);
|
|
1546
|
+
} catch {
|
|
1547
|
+
console.error(pc8.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
1548
|
+
process.exit(1);
|
|
1549
|
+
}
|
|
1550
|
+
const storageDir = config.storage.dir || ".harness";
|
|
1551
|
+
const dbPath = resolve7(cwd2, storageDir, "harness.db");
|
|
1552
|
+
const featureListPath = resolve7(cwd2, storageDir, "feature_list.json");
|
|
1553
|
+
let resetDb = false;
|
|
1554
|
+
let resetFeatureList = false;
|
|
1555
|
+
let resetAgentMdsFlag = false;
|
|
1556
|
+
if (existsSync8(dbPath)) {
|
|
1557
|
+
if (opts.force) {
|
|
1558
|
+
resetDb = true;
|
|
1559
|
+
} else {
|
|
1560
|
+
const confirm3 = await p4.confirm({
|
|
1561
|
+
message: `Delete database (${storageDir}/harness.db)?`,
|
|
1562
|
+
initialValue: true
|
|
1563
|
+
});
|
|
1564
|
+
if (p4.isCancel(confirm3)) {
|
|
1565
|
+
console.log(pc8.red(" Cancelled by user."));
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
resetDb = confirm3;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (existsSync8(featureListPath)) {
|
|
1572
|
+
if (opts.force) {
|
|
1573
|
+
resetFeatureList = true;
|
|
1574
|
+
} else {
|
|
1575
|
+
const confirm3 = await p4.confirm({
|
|
1576
|
+
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
1577
|
+
initialValue: true
|
|
1578
|
+
});
|
|
1579
|
+
if (p4.isCancel(confirm3)) {
|
|
1580
|
+
console.log(pc8.red(" Cancelled by user."));
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
resetFeatureList = confirm3;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (opts.provider) {
|
|
1587
|
+
resetAgentMdsFlag = true;
|
|
1588
|
+
}
|
|
1589
|
+
let changed = false;
|
|
1590
|
+
if (resetDb) {
|
|
1591
|
+
try {
|
|
1592
|
+
rmSync(dbPath, { force: true });
|
|
1593
|
+
console.log(pc8.green(` \u2713 Removed ${storageDir}/harness.db`));
|
|
1594
|
+
changed = true;
|
|
1595
|
+
} catch {
|
|
1596
|
+
console.error(pc8.red(` \u2717 Failed to remove ${dbPath}`));
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (resetFeatureList) {
|
|
1600
|
+
try {
|
|
1601
|
+
rmSync(featureListPath, { force: true });
|
|
1602
|
+
console.log(pc8.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
1603
|
+
changed = true;
|
|
1604
|
+
} catch {
|
|
1605
|
+
console.error(pc8.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (resetAgentMdsFlag) {
|
|
1609
|
+
console.log("");
|
|
1610
|
+
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
1611
|
+
}
|
|
1612
|
+
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
1613
|
+
console.log(pc8.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
console.log("");
|
|
1617
|
+
console.log(pc8.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1471
1620
|
// src/core/mcp-server.ts
|
|
1472
|
-
import { readdirSync, readFileSync as
|
|
1473
|
-
import { join as
|
|
1621
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "fs";
|
|
1622
|
+
import { join as join12, resolve as resolve8 } from "path";
|
|
1474
1623
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
1475
1624
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1476
1625
|
import {
|
|
@@ -1631,7 +1780,7 @@ var TOOLS = [
|
|
|
1631
1780
|
];
|
|
1632
1781
|
async function startMcpServer(config, cwd2) {
|
|
1633
1782
|
const db = openDB(config, cwd2);
|
|
1634
|
-
const docsPath =
|
|
1783
|
+
const docsPath = resolve8(cwd2, config.project.docsPath);
|
|
1635
1784
|
const server = new Server(
|
|
1636
1785
|
{ name: "agent-harness-kit", version: VERSION },
|
|
1637
1786
|
{ capabilities: { tools: {} } }
|
|
@@ -1741,7 +1890,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
1741
1890
|
for (const file of files) {
|
|
1742
1891
|
if (results.length >= maxResults) break;
|
|
1743
1892
|
try {
|
|
1744
|
-
const content =
|
|
1893
|
+
const content = readFileSync6(file, "utf8");
|
|
1745
1894
|
const lines = content.split("\n");
|
|
1746
1895
|
for (let i = 0; i < lines.length; i++) {
|
|
1747
1896
|
const lower = lines[i].toLowerCase();
|
|
@@ -1761,8 +1910,8 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
1761
1910
|
function collectMarkdownFiles(dir) {
|
|
1762
1911
|
const files = [];
|
|
1763
1912
|
try {
|
|
1764
|
-
for (const entry of
|
|
1765
|
-
const full =
|
|
1913
|
+
for (const entry of readdirSync2(dir)) {
|
|
1914
|
+
const full = join12(dir, entry);
|
|
1766
1915
|
const stat = statSync(full);
|
|
1767
1916
|
if (stat.isDirectory()) {
|
|
1768
1917
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -1801,12 +1950,12 @@ async function runServe(cwd2, opts) {
|
|
|
1801
1950
|
|
|
1802
1951
|
// src/commands/status.ts
|
|
1803
1952
|
import Table from "cli-table3";
|
|
1804
|
-
import
|
|
1953
|
+
import pc9 from "picocolors";
|
|
1805
1954
|
var STATUS_COLOR = {
|
|
1806
|
-
pending: (s) =>
|
|
1807
|
-
in_progress: (s) =>
|
|
1808
|
-
done: (s) =>
|
|
1809
|
-
blocked: (s) =>
|
|
1955
|
+
pending: (s) => pc9.dim(s),
|
|
1956
|
+
in_progress: (s) => pc9.cyan(s),
|
|
1957
|
+
done: (s) => pc9.green(s),
|
|
1958
|
+
blocked: (s) => pc9.red(s)
|
|
1810
1959
|
};
|
|
1811
1960
|
async function runStatus(cwd2, opts) {
|
|
1812
1961
|
const config = await loadConfig(cwd2);
|
|
@@ -1824,11 +1973,11 @@ async function runStatus(cwd2, opts) {
|
|
|
1824
1973
|
return;
|
|
1825
1974
|
}
|
|
1826
1975
|
if (tasks.length === 0) {
|
|
1827
|
-
console.log(
|
|
1976
|
+
console.log(pc9.dim("No tasks yet. Run: ahk task add"));
|
|
1828
1977
|
return;
|
|
1829
1978
|
}
|
|
1830
1979
|
const table = new Table({
|
|
1831
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
1980
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc9.bold(h)),
|
|
1832
1981
|
style: { head: [], border: [] }
|
|
1833
1982
|
});
|
|
1834
1983
|
for (const t of tasks) {
|
|
@@ -1846,12 +1995,12 @@ async function runStatus(cwd2, opts) {
|
|
|
1846
1995
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
1847
1996
|
if (inProgress.length > 0) {
|
|
1848
1997
|
console.log("");
|
|
1849
|
-
console.log(
|
|
1998
|
+
console.log(pc9.bold("Active actions:"));
|
|
1850
1999
|
for (const t of inProgress) {
|
|
1851
2000
|
const actions = db.getActionsForTask(t.id);
|
|
1852
2001
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
1853
2002
|
for (const a of active) {
|
|
1854
|
-
console.log(` ${
|
|
2003
|
+
console.log(` ${pc9.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
1855
2004
|
}
|
|
1856
2005
|
}
|
|
1857
2006
|
}
|
|
@@ -1860,20 +2009,20 @@ async function runStatus(cwd2, opts) {
|
|
|
1860
2009
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
1861
2010
|
return `${fn(s.status)}: ${s.total}`;
|
|
1862
2011
|
});
|
|
1863
|
-
console.log(
|
|
2012
|
+
console.log(pc9.dim("Tasks \u2014 ") + parts.join(pc9.dim(" | ")));
|
|
1864
2013
|
} finally {
|
|
1865
2014
|
db.close();
|
|
1866
2015
|
}
|
|
1867
2016
|
}
|
|
1868
2017
|
|
|
1869
2018
|
// src/commands/sync.ts
|
|
1870
|
-
import { existsSync as
|
|
1871
|
-
import { join as
|
|
1872
|
-
import
|
|
2019
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
2020
|
+
import { join as join13, resolve as resolve9 } from "path";
|
|
2021
|
+
import pc10 from "picocolors";
|
|
1873
2022
|
async function runSync(cwd2, opts) {
|
|
1874
2023
|
const config = await loadConfig(cwd2);
|
|
1875
2024
|
const direction = opts.direction ?? "both";
|
|
1876
|
-
const featureListPath =
|
|
2025
|
+
const featureListPath = resolve9(join13(cwd2, config.storage.dir, "feature_list.json"));
|
|
1877
2026
|
const db = openDB(config, cwd2);
|
|
1878
2027
|
try {
|
|
1879
2028
|
if (direction === "in" || direction === "both") {
|
|
@@ -1887,70 +2036,70 @@ async function runSync(cwd2, opts) {
|
|
|
1887
2036
|
}
|
|
1888
2037
|
}
|
|
1889
2038
|
async function syncIn(featureListPath, db, dryRun) {
|
|
1890
|
-
if (!
|
|
1891
|
-
console.log(
|
|
2039
|
+
if (!existsSync9(featureListPath)) {
|
|
2040
|
+
console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
1892
2041
|
return;
|
|
1893
2042
|
}
|
|
1894
2043
|
let seeds;
|
|
1895
2044
|
try {
|
|
1896
|
-
seeds = JSON.parse(
|
|
2045
|
+
seeds = JSON.parse(readFileSync7(featureListPath, "utf8"));
|
|
1897
2046
|
} catch (err) {
|
|
1898
|
-
console.error(
|
|
2047
|
+
console.error(pc10.red(`Failed to parse feature_list.json: ${err}`));
|
|
1899
2048
|
process.exit(1);
|
|
1900
2049
|
}
|
|
1901
2050
|
if (dryRun) {
|
|
1902
|
-
console.log(
|
|
2051
|
+
console.log(pc10.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
1903
2052
|
for (const t of seeds) {
|
|
1904
2053
|
const existing = db.getTaskBySlug(t.slug);
|
|
1905
|
-
console.log(` ${existing ?
|
|
2054
|
+
console.log(` ${existing ? pc10.dim("skip") : pc10.green("add ")} ${t.slug}`);
|
|
1906
2055
|
}
|
|
1907
2056
|
return;
|
|
1908
2057
|
}
|
|
1909
2058
|
const result = db.syncFromFeatureList(seeds);
|
|
1910
|
-
console.log(
|
|
2059
|
+
console.log(pc10.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
1911
2060
|
}
|
|
1912
2061
|
function syncOut(db, cwd2, dryRun) {
|
|
1913
2062
|
if (dryRun) {
|
|
1914
2063
|
const tasks = db.getTasks();
|
|
1915
|
-
console.log(
|
|
2064
|
+
console.log(pc10.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
1916
2065
|
console.log(` ${tasks.length} tasks would be written`);
|
|
1917
2066
|
return;
|
|
1918
2067
|
}
|
|
1919
2068
|
db.writeFeatureList(cwd2);
|
|
1920
|
-
console.log(
|
|
2069
|
+
console.log(pc10.green("\u2713 Out-sync: feature_list.json updated"));
|
|
1921
2070
|
}
|
|
1922
2071
|
|
|
1923
2072
|
// src/commands/task/add.ts
|
|
1924
|
-
import * as
|
|
1925
|
-
import
|
|
2073
|
+
import * as p5 from "@clack/prompts";
|
|
2074
|
+
import pc11 from "picocolors";
|
|
1926
2075
|
async function runTaskAdd(cwd2) {
|
|
1927
|
-
|
|
1928
|
-
const titleVal = await
|
|
2076
|
+
p5.intro(pc11.bold("agent-harness-kit \u2014 add task"));
|
|
2077
|
+
const titleVal = await p5.text({
|
|
1929
2078
|
message: "Task title",
|
|
1930
2079
|
validate: (v) => v.trim() ? void 0 : "Title is required"
|
|
1931
2080
|
});
|
|
1932
|
-
if (
|
|
1933
|
-
|
|
2081
|
+
if (p5.isCancel(titleVal)) {
|
|
2082
|
+
p5.cancel("Cancelled.");
|
|
1934
2083
|
process.exit(0);
|
|
1935
2084
|
}
|
|
1936
2085
|
const title = titleVal.trim();
|
|
1937
|
-
const descVal = await
|
|
2086
|
+
const descVal = await p5.text({
|
|
1938
2087
|
message: "Description (what and why)",
|
|
1939
2088
|
placeholder: "Optional"
|
|
1940
2089
|
});
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
2090
|
+
if (p5.isCancel(descVal)) {
|
|
2091
|
+
p5.cancel("Cancelled.");
|
|
1943
2092
|
process.exit(0);
|
|
1944
2093
|
}
|
|
1945
2094
|
const description = descVal.trim();
|
|
1946
2095
|
const acceptance = [];
|
|
1947
|
-
|
|
2096
|
+
p5.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
1948
2097
|
while (true) {
|
|
1949
|
-
const val = await
|
|
1950
|
-
if (
|
|
2098
|
+
const val = await p5.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
|
|
2099
|
+
if (p5.isCancel(val) || !val || !val.trim()) break;
|
|
1951
2100
|
acceptance.push(val.trim());
|
|
1952
2101
|
}
|
|
1953
|
-
const spinner5 =
|
|
2102
|
+
const spinner5 = p5.spinner();
|
|
1954
2103
|
spinner5.start("Saving...");
|
|
1955
2104
|
try {
|
|
1956
2105
|
const config = await loadConfig(cwd2);
|
|
@@ -1960,28 +2109,28 @@ async function runTaskAdd(cwd2) {
|
|
|
1960
2109
|
db.writeFeatureList(cwd2);
|
|
1961
2110
|
db.close();
|
|
1962
2111
|
spinner5.stop("");
|
|
1963
|
-
console.log(
|
|
1964
|
-
console.log(
|
|
2112
|
+
console.log(pc11.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
|
|
2113
|
+
console.log(pc11.cyan("\u2192") + " " + pc11.cyan("ahk status") + " to see all tasks");
|
|
1965
2114
|
} catch (err) {
|
|
1966
|
-
spinner5.stop(
|
|
1967
|
-
|
|
2115
|
+
spinner5.stop(pc11.red("Failed"));
|
|
2116
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
1968
2117
|
process.exit(1);
|
|
1969
2118
|
}
|
|
1970
2119
|
}
|
|
1971
2120
|
|
|
1972
2121
|
// src/commands/task/done.ts
|
|
1973
2122
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1974
|
-
import { existsSync as
|
|
1975
|
-
import { resolve as
|
|
1976
|
-
import
|
|
2123
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2124
|
+
import { resolve as resolve10 } from "path";
|
|
2125
|
+
import pc12 from "picocolors";
|
|
1977
2126
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
1978
2127
|
const config = await loadConfig(cwd2);
|
|
1979
2128
|
if (config.health.required) {
|
|
1980
|
-
const scriptPath =
|
|
1981
|
-
if (
|
|
2129
|
+
const scriptPath = resolve10(cwd2, config.health.scriptPath);
|
|
2130
|
+
if (existsSync10(scriptPath)) {
|
|
1982
2131
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
1983
2132
|
if (result.status !== 0) {
|
|
1984
|
-
console.error(
|
|
2133
|
+
console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
1985
2134
|
if (result.stdout) console.error(result.stdout);
|
|
1986
2135
|
if (result.stderr) console.error(result.stderr);
|
|
1987
2136
|
process.exit(1);
|
|
@@ -1994,16 +2143,16 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
1994
2143
|
const isId = !isNaN(parsed);
|
|
1995
2144
|
const task2 = isId ? db.getTaskById(parsed) : db.getTaskBySlug(idOrSlug);
|
|
1996
2145
|
if (!task2) {
|
|
1997
|
-
console.error(
|
|
2146
|
+
console.error(pc12.red(`Task not found: ${idOrSlug}`));
|
|
1998
2147
|
process.exit(1);
|
|
1999
2148
|
}
|
|
2000
2149
|
if (task2.status === "done") {
|
|
2001
|
-
console.log(
|
|
2150
|
+
console.log(pc12.dim(`Task #${task2.id} is already done.`));
|
|
2002
2151
|
return;
|
|
2003
2152
|
}
|
|
2004
2153
|
db.updateTaskStatus(task2.id, "done");
|
|
2005
2154
|
db.writeFeatureList(cwd2);
|
|
2006
|
-
console.log(
|
|
2155
|
+
console.log(pc12.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
2007
2156
|
} finally {
|
|
2008
2157
|
db.close();
|
|
2009
2158
|
}
|
|
@@ -2011,12 +2160,12 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
2011
2160
|
|
|
2012
2161
|
// src/commands/task/list.ts
|
|
2013
2162
|
import Table2 from "cli-table3";
|
|
2014
|
-
import
|
|
2163
|
+
import pc13 from "picocolors";
|
|
2015
2164
|
var STATUS_COLOR2 = {
|
|
2016
|
-
pending: (s) =>
|
|
2017
|
-
in_progress: (s) =>
|
|
2018
|
-
done: (s) =>
|
|
2019
|
-
blocked: (s) =>
|
|
2165
|
+
pending: (s) => pc13.dim(s),
|
|
2166
|
+
in_progress: (s) => pc13.cyan(s),
|
|
2167
|
+
done: (s) => pc13.green(s),
|
|
2168
|
+
blocked: (s) => pc13.red(s)
|
|
2020
2169
|
};
|
|
2021
2170
|
async function runTaskList(cwd2, opts) {
|
|
2022
2171
|
const config = await loadConfig(cwd2);
|
|
@@ -2030,11 +2179,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
2030
2179
|
return;
|
|
2031
2180
|
}
|
|
2032
2181
|
if (tasks.length === 0) {
|
|
2033
|
-
console.log(
|
|
2182
|
+
console.log(pc13.dim("No tasks" + (filterStatus ? ` with status: ${filterStatus}` : "") + "."));
|
|
2034
2183
|
return;
|
|
2035
2184
|
}
|
|
2036
2185
|
const table = new Table2({
|
|
2037
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
2186
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc13.bold(h)),
|
|
2038
2187
|
style: { head: [], border: [] }
|
|
2039
2188
|
});
|
|
2040
2189
|
for (const t of tasks) {
|
|
@@ -2049,14 +2198,14 @@ async function runTaskList(cwd2, opts) {
|
|
|
2049
2198
|
|
|
2050
2199
|
// src/core/package-data.ts
|
|
2051
2200
|
import { createRequire as createRequire2 } from "module";
|
|
2052
|
-
import { dirname as dirname5, join as
|
|
2201
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
2053
2202
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2054
2203
|
var require2 = createRequire2(import.meta.url);
|
|
2055
|
-
var pkgPath =
|
|
2204
|
+
var pkgPath = join14(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
2056
2205
|
var pkg = require2(pkgPath);
|
|
2057
2206
|
|
|
2058
2207
|
// src/core/update-check.ts
|
|
2059
|
-
import
|
|
2208
|
+
import pc14 from "picocolors";
|
|
2060
2209
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
2061
2210
|
var TIMEOUT_MS = 2500;
|
|
2062
2211
|
function checkForUpdate(currentVersion) {
|
|
@@ -2074,18 +2223,18 @@ function checkForUpdate(currentVersion) {
|
|
|
2074
2223
|
}
|
|
2075
2224
|
function printUpdateMessage({ current, latest }) {
|
|
2076
2225
|
const lines = [
|
|
2077
|
-
` Update available ${
|
|
2078
|
-
` Run: ${
|
|
2226
|
+
` Update available ${pc14.dim(current)} \u2192 ${pc14.green(latest)} `,
|
|
2227
|
+
` Run: ${pc14.cyan(`npm i ${pkg.name}@${latest}`)} `
|
|
2079
2228
|
];
|
|
2080
2229
|
const width = Math.max(...lines.map((l) => stripAnsi2(l).length));
|
|
2081
2230
|
const border = "\u2500".repeat(width);
|
|
2082
2231
|
console.log();
|
|
2083
|
-
console.log(
|
|
2232
|
+
console.log(pc14.yellow(`\u250C${border}\u2510`));
|
|
2084
2233
|
for (const line of lines) {
|
|
2085
2234
|
const pad = width - stripAnsi2(line).length;
|
|
2086
|
-
console.log(
|
|
2235
|
+
console.log(pc14.yellow("\u2502") + line + " ".repeat(pad) + pc14.yellow("\u2502"));
|
|
2087
2236
|
}
|
|
2088
|
-
console.log(
|
|
2237
|
+
console.log(pc14.yellow(`\u2514${border}\u2518`));
|
|
2089
2238
|
console.log();
|
|
2090
2239
|
}
|
|
2091
2240
|
function isNewer(latest, current) {
|
|
@@ -2100,134 +2249,6 @@ function stripAnsi2(str2) {
|
|
|
2100
2249
|
return str2.replace(/\x1B\[[0-9;]*m/g, "");
|
|
2101
2250
|
}
|
|
2102
2251
|
|
|
2103
|
-
// src/commands/reset.ts
|
|
2104
|
-
import { existsSync as existsSync9, readdirSync as readdirSync2, rmSync } from "fs";
|
|
2105
|
-
import { join as join13, resolve as resolve10 } from "path";
|
|
2106
|
-
import * as p5 from "@clack/prompts";
|
|
2107
|
-
import pc14 from "picocolors";
|
|
2108
|
-
async function resetAgentMds(cwd2, provider) {
|
|
2109
|
-
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
2110
|
-
const agentDirPath = resolve10(cwd2, agentDir);
|
|
2111
|
-
if (!existsSync9(agentDirPath)) {
|
|
2112
|
-
console.log(pc14.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2113
|
-
return;
|
|
2114
|
-
}
|
|
2115
|
-
const existingFiles = [];
|
|
2116
|
-
try {
|
|
2117
|
-
const files = readdirSync2(agentDirPath);
|
|
2118
|
-
for (const f of files) {
|
|
2119
|
-
if (f.endsWith(".md")) {
|
|
2120
|
-
existingFiles.push(f);
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
} catch {
|
|
2124
|
-
console.log(pc14.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2125
|
-
return;
|
|
2126
|
-
}
|
|
2127
|
-
if (existingFiles.length === 0) {
|
|
2128
|
-
console.log(pc14.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2129
|
-
return;
|
|
2130
|
-
}
|
|
2131
|
-
for (const file of existingFiles) {
|
|
2132
|
-
const confirm3 = await p5.confirm({
|
|
2133
|
-
message: `Remove ${file}?`,
|
|
2134
|
-
initialValue: true
|
|
2135
|
-
});
|
|
2136
|
-
if (p5.isCancel(confirm3)) {
|
|
2137
|
-
console.log(pc14.red(" Cancelled by user."));
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
if (confirm3) {
|
|
2141
|
-
try {
|
|
2142
|
-
const filePath = join13(agentDirPath, file);
|
|
2143
|
-
rmSync(filePath, { force: true });
|
|
2144
|
-
console.log(pc14.green(` Removed ${file}`));
|
|
2145
|
-
} catch {
|
|
2146
|
-
console.error(pc14.red(` Failed to remove ${file}`));
|
|
2147
|
-
}
|
|
2148
|
-
} else {
|
|
2149
|
-
console.log(pc14.cyan(` Skipped ${file}`));
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
async function runReset(cwd2, opts) {
|
|
2154
|
-
let config;
|
|
2155
|
-
try {
|
|
2156
|
-
config = await loadConfig(cwd2);
|
|
2157
|
-
} catch {
|
|
2158
|
-
console.error(pc14.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2159
|
-
process.exit(1);
|
|
2160
|
-
}
|
|
2161
|
-
const storageDir = config.storage.dir || ".harness";
|
|
2162
|
-
const dbPath = resolve10(cwd2, storageDir, "harness.db");
|
|
2163
|
-
const featureListPath = resolve10(cwd2, storageDir, "feature_list.json");
|
|
2164
|
-
let resetDb = false;
|
|
2165
|
-
let resetFeatureList = false;
|
|
2166
|
-
let resetAgentMdsFlag = false;
|
|
2167
|
-
if (existsSync9(dbPath)) {
|
|
2168
|
-
if (opts.force) {
|
|
2169
|
-
resetDb = true;
|
|
2170
|
-
} else {
|
|
2171
|
-
const confirm3 = await p5.confirm({
|
|
2172
|
-
message: `Delete database (${storageDir}/harness.db)?`,
|
|
2173
|
-
initialValue: true
|
|
2174
|
-
});
|
|
2175
|
-
if (p5.isCancel(confirm3)) {
|
|
2176
|
-
console.log(pc14.red(" Cancelled by user."));
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
resetDb = confirm3;
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
if (existsSync9(featureListPath)) {
|
|
2183
|
-
if (opts.force) {
|
|
2184
|
-
resetFeatureList = true;
|
|
2185
|
-
} else {
|
|
2186
|
-
const confirm3 = await p5.confirm({
|
|
2187
|
-
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
2188
|
-
initialValue: true
|
|
2189
|
-
});
|
|
2190
|
-
if (p5.isCancel(confirm3)) {
|
|
2191
|
-
console.log(pc14.red(" Cancelled by user."));
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
resetFeatureList = confirm3;
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
if (opts.provider) {
|
|
2198
|
-
resetAgentMdsFlag = true;
|
|
2199
|
-
}
|
|
2200
|
-
let changed = false;
|
|
2201
|
-
if (resetDb) {
|
|
2202
|
-
try {
|
|
2203
|
-
rmSync(dbPath, { force: true });
|
|
2204
|
-
console.log(pc14.green(` \u2713 Removed ${storageDir}/harness.db`));
|
|
2205
|
-
changed = true;
|
|
2206
|
-
} catch {
|
|
2207
|
-
console.error(pc14.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
if (resetFeatureList) {
|
|
2211
|
-
try {
|
|
2212
|
-
rmSync(featureListPath, { force: true });
|
|
2213
|
-
console.log(pc14.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2214
|
-
changed = true;
|
|
2215
|
-
} catch {
|
|
2216
|
-
console.error(pc14.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
if (resetAgentMdsFlag) {
|
|
2220
|
-
console.log("");
|
|
2221
|
-
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2222
|
-
}
|
|
2223
|
-
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2224
|
-
console.log(pc14.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
console.log("");
|
|
2228
|
-
console.log(pc14.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
2252
|
// src/cli.ts
|
|
2232
2253
|
var cwd = process.cwd();
|
|
2233
2254
|
var updateCheck = checkForUpdate(pkg.version);
|