@cardor/agent-harness-kit 0.16.8 → 0.17.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.
@@ -38,30 +38,32 @@ These three calls are **not optional**. The dashboard cannot display what you do
38
38
 
39
39
  ### 1. Log every tool call you make
40
40
 
41
- After **each** tool invocation (Read, Edit, Write, Bash), immediately call:
41
+ After **each** tool invocation (Read, Edit, Write, Bash), call **both**:
42
42
 
43
43
  ```
44
- actions.write(actionId, 'tools_used', '<ToolName>: <args-summary> why')
44
+ actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
45
45
  ```
46
46
 
47
47
  Examples:
48
- - `Read: src/auth/middleware.ts understand existing JWT pattern`
49
- - `Bash: npm test -- --testPathPattern=auth verify auth tests pass`
50
- - `Edit: src/auth/middleware.ts:45-78 add refresh token validation`
48
+ - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'understand existing JWT pattern')`
49
+ - `actions.record_tool(actionId, 'Bash', 'npm test --testPathPattern=auth', 'verify auth tests pass')`
50
+ - `actions.record_tool(actionId, 'Edit', 'src/auth/middleware.ts:45-78', 'add refresh token validation')`
51
51
 
52
52
  ### 2. Log every file you touch
53
53
 
54
- After **each** file modification (Edit, Write), immediately call:
54
+ After **each** file modification (Edit, Write), call:
55
55
 
56
56
  ```
57
- actions.write(actionId, 'files_modified', '<file-path> what changed and why')
57
+ actions.record_file(actionId, '<file-path>', '<operation>', '<what changed and why>')
58
58
  ```
59
59
 
60
- Example: `src/auth/middleware.ts added refresh token expiry check in validateToken()`
60
+ Operations: `created` | `modified` | `deleted`
61
+
62
+ Example: `actions.record_file(actionId, 'src/auth/middleware.ts', 'modified', 'added refresh token expiry check in validateToken()')`
61
63
 
62
64
  ### 3. Do not complete your action without both logs being up to date
63
65
 
64
- If you touched 5 files and made 12 tool calls, there must be 5 `files_modified` entries and 12 `tools_used` entries before you call `actions.complete`.
66
+ If you touched 5 files and made 12 tool calls, there must be 5 `actions.record_file` calls and 12 `actions.record_tool` calls before you call `actions.complete`.
65
67
 
66
68
  ---
67
69
 
@@ -125,8 +127,8 @@ actions.complete(actionId, 'Implementation done — N files modified, tests pass
125
127
 
126
128
  - **Read the plan and analysis first.** Never implement cold.
127
129
  - **Only write to `{{writablePaths}}`.** No exceptions.
128
- - **Log every file you touch.** No silent modifications.
129
- - **Log every tool call.** Use `actions.write(actionId, 'tools_used', ...)` after each Read, Edit, Write, Bash invocation.
130
+ - **Log every file you touch.** Call `actions.record_file(actionId, path, operation, notes)` after each Edit/Write.
131
+ - **Log every tool call.** Call `actions.record_tool(actionId, toolName, args, summary)` after each Read, Edit, Write, Bash invocation.
130
132
  - **Leave tests green.** If tests fail after your changes, fix them before completing.
131
133
  - **Do not refactor beyond the task scope.** Implement what was asked, nothing more.
132
134
  - **If blocked, say so.** Do not invent workarounds for unclear requirements.
@@ -37,18 +37,18 @@ These calls are **not optional**. The dashboard cannot display what you do not r
37
37
 
38
38
  ### Log every tool call you make
39
39
 
40
- After **each** tool invocation (Read, Bash, grep, docs.search), immediately call:
40
+ After **each** tool invocation (Read, Bash, grep, docs.search), call:
41
41
 
42
42
  ```
43
- actions.write(actionId, 'tools_used', '<ToolName>: <args-summary> why')
43
+ actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
44
44
  ```
45
45
 
46
46
  Examples:
47
- - `Read: src/auth/middleware.ts find existing JWT pattern`
48
- - `Bash: grep -r "refreshToken" src/ locate all refresh token usages`
49
- - `docs.search: "authentication middleware" check project docs for auth guidance`
47
+ - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'find existing JWT pattern')`
48
+ - `actions.record_tool(actionId, 'Bash', 'grep -r "refreshToken" src/', 'locate all refresh token usages')`
49
+ - `actions.record_tool(actionId, 'docs.search', 'authentication middleware', 'check project docs for auth guidance')`
50
50
 
51
- **Every single tool call must be logged.** No silent reads. The audit trail in the dashboard is built entirely from these entries.
51
+ **Every single tool call must be logged.** No silent reads. The Tools dashboard is built entirely from these `actions.record_tool` calls.
52
52
 
53
53
  ---
54
54
 
@@ -32,16 +32,16 @@ These calls are **not optional**. The dashboard cannot display what you do not r
32
32
 
33
33
  ### Log every tool call you make
34
34
 
35
- After **each** tool invocation (Bash, tasks.get, tasks.claim, actions.get), immediately call:
35
+ After **each** tool invocation (Bash, tasks.get, tasks.claim, actions.get), call:
36
36
 
37
37
  ```
38
- actions.write(actionId, 'tools_used', '<ToolName>: <args-summary> why')
38
+ actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
39
39
  ```
40
40
 
41
41
  Examples:
42
- - `Bash: bash health.sh verify codebase health before starting`
43
- - `tasks.get: pending find next task to claim`
44
- - `actions.get: taskId=abc123 read action history to resume in-progress task`
42
+ - `actions.record_tool(actionId, 'Bash', 'bash health.sh', 'verify codebase health before starting')`
43
+ - `actions.record_tool(actionId, 'tasks.get', 'pending', 'find next task to claim')`
44
+ - `actions.record_tool(actionId, 'actions.get', 'taskId=abc123', 'read action history to resume in-progress task')`
45
45
 
46
46
  **Log every call.** This applies from the moment you have an `actionId` (after step 3 below).
47
47
 
@@ -32,28 +32,25 @@ These calls are **not optional**. The dashboard cannot display what you do not r
32
32
 
33
33
  ### 1. Log every tool call you make
34
34
 
35
- After **each** tool invocation (Read, Bash), immediately call:
35
+ After **each** tool invocation (Read, Bash), call:
36
36
 
37
37
  ```
38
- actions.write(actionId, 'tools_used', '<ToolName>: <args-summary> why')
38
+ actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
39
39
  ```
40
40
 
41
41
  Examples:
42
- - `Read: src/auth/middleware.ts verify refresh token logic matches criterion 2`
43
- - `Bash: npm test -- --testPathPattern=auth confirm all auth tests pass`
42
+ - `actions.record_tool(actionId, 'Read', 'src/auth/middleware.ts', 'verify refresh token logic matches criterion 2')`
43
+ - `actions.record_tool(actionId, 'Bash', 'npm test --testPathPattern=auth', 'confirm all auth tests pass')`
44
44
 
45
45
  ### 2. Mark every acceptance criterion as you verify it
46
46
 
47
- For **each** criterion (0-based index), call this immediately after you evaluate it:
47
+ For **each** criterion, call this immediately after you evaluate it using its `id` from `tasks.get`:
48
48
 
49
49
  ```
50
- tasks.acceptance_update(taskId, criterionIndex, true|false)
50
+ tasks.acceptance.update(criterionId)
51
51
  ```
52
52
 
53
- - `true` = criterion is fully met
54
- - `false` = criterion is not met
55
-
56
- If the task has 3 criteria, you must make exactly 3 `tasks.acceptance_update` calls — one per criterion. Skipping any of them leaves the dashboard showing criteria as unverified.
53
+ If the task has 3 criteria, you must make exactly 3 `tasks.acceptance.update` calls — one per criterion. Skipping any of them leaves the dashboard showing criteria as unverified.
57
54
 
58
55
  ---
59
56
 
@@ -124,7 +121,7 @@ Then notify lead so the builder can be re-assigned.
124
121
 
125
122
  - **Run health.sh before approving.** No exceptions.
126
123
  - **Check every acceptance criterion.** Not just the obvious ones.
127
- - **Call `tasks.acceptance_update()` for each criterion.** Both met and unmet — never skip this step.
124
+ - **Call `tasks.acceptance.update()` for each criterion.** Never skip this step.
128
125
  - **Never self-approve partial work.** All criteria must be met, not most.
129
126
  - **Be specific when blocking.** The builder must know exactly what to fix.
130
127
  - **Do not fix issues yourself.** Your job is to verify, not to implement.
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 taskId agent \u2192 start an action, returns actionId
153
- actions.write actionId section text \u2192 record a section (result, tools_used, ...)
154
- actions.complete actionId summary \u2192 close the action
155
- actions.get taskId \u2192 full action history for a task
156
- tasks.get [status] \u2192 list tasks (pending | in_progress | done | blocked)
157
- tasks.claim id \u2192 atomically claim a pending task
158
- tasks.update id status \u2192 change task status
159
- docs.search query \u2192 search ${docsPath} for relevant content
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
- - Records work with actions.write(actionId, section, content)
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
@@ -860,6 +864,14 @@ var HarnessDB = class {
860
864
  this.regenerateCurrentMd();
861
865
  return this.getAction(actionId);
862
866
  }
867
+ closeOrphanedActions(taskId) {
868
+ const now = (/* @__PURE__ */ new Date()).toISOString();
869
+ const result = this.db.prepare(
870
+ `UPDATE actions SET status = 'completed', completed_at = ?, summary = 'Auto-closed: task marked done'
871
+ WHERE task_id = ? AND status = 'in_progress'`
872
+ ).run(now, taskId);
873
+ return result.changes;
874
+ }
863
875
  getAction(actionId) {
864
876
  return this.db.prepare(`SELECT * FROM actions WHERE id = ?`).get(actionId) ?? null;
865
877
  }
@@ -1153,12 +1165,27 @@ async function runHealth(cwd2) {
1153
1165
  // src/commands/init.ts
1154
1166
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
1155
1167
  import { homedir } from "os";
1156
- import { join as join9 } from "path";
1168
+ import { join as join10 } from "path";
1157
1169
  import * as p2 from "@clack/prompts";
1158
1170
  import pc6 from "picocolors";
1159
1171
 
1160
1172
  // src/commands/init-helpers.ts
1173
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
1174
+ import { join as join9 } from "path";
1161
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
+ }
1162
1189
  function applyConfigDefaults(params) {
1163
1190
  return {
1164
1191
  provider: params.provider,
@@ -1233,7 +1260,8 @@ function printWelcomeMessage(projectName) {
1233
1260
 
1234
1261
  // src/commands/init.ts
1235
1262
  async function runInit(cwd2, flags) {
1236
- const projectName = flags.name || "my-project";
1263
+ const detectedName = flags.name ?? readProjectNameFromPackageJson(cwd2);
1264
+ const projectName = detectedName || "my-project";
1237
1265
  printWelcomeMessage(projectName);
1238
1266
  let name;
1239
1267
  if (flags.name) {
@@ -1365,9 +1393,9 @@ async function runInit(cwd2, flags) {
1365
1393
  let installDir = cwd2;
1366
1394
  if (globalInstallation) {
1367
1395
  if (provider === "claude-code") {
1368
- installDir = join9(homedir(), ".claude");
1396
+ installDir = join10(homedir(), ".claude");
1369
1397
  } else {
1370
- installDir = join9(homedir(), ".config", "opencode");
1398
+ installDir = join10(homedir(), ".config", "opencode");
1371
1399
  }
1372
1400
  }
1373
1401
  const configContent = configTs({
@@ -1378,8 +1406,8 @@ async function runInit(cwd2, flags) {
1378
1406
  tasksAdapter,
1379
1407
  port: config.tools.mcp.port
1380
1408
  });
1381
- writeFileSync7(join9(installDir, "agent-harness-kit.config.ts"), configContent, "utf8");
1382
- mkdirSync6(join9(installDir, config.storage.dir), { recursive: true });
1409
+ writeFileSync7(join10(installDir, "agent-harness-kit.config.ts"), configContent, "utf8");
1410
+ mkdirSync6(join10(installDir, config.storage.dir), { recursive: true });
1383
1411
  const db = openDB(config, installDir);
1384
1412
  await materializer.scaffold(config, { cwd: installDir, firstTask });
1385
1413
  if (firstTask) {
@@ -1461,8 +1489,8 @@ async function runMigrate(cwd2, opts) {
1461
1489
  }
1462
1490
 
1463
1491
  // src/core/mcp-server.ts
1464
- import { readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1465
- import { join as join10, resolve as resolve7 } from "path";
1492
+ import { readdirSync, readFileSync as readFileSync6, statSync } from "fs";
1493
+ import { join as join11, resolve as resolve7 } from "path";
1466
1494
  import { Server } from "@modelcontextprotocol/sdk/server";
1467
1495
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1468
1496
  import {
@@ -1605,6 +1633,20 @@ var TOOLS = [
1605
1633
  },
1606
1634
  required: ["criterionId"]
1607
1635
  }
1636
+ },
1637
+ {
1638
+ name: "actions.record_tool",
1639
+ description: "Record a tool call made during an action. This is the only way to populate the Tools dashboard. Call once per tool invocation.",
1640
+ inputSchema: {
1641
+ type: "object",
1642
+ properties: {
1643
+ actionId: { type: "string", description: "UUID returned by actions.start" },
1644
+ toolName: { type: "string", description: "Name of the tool that was called (e.g. Read, Bash, Edit)" },
1645
+ argsJson: { type: "string", description: "Optional JSON string of the arguments passed to the tool" },
1646
+ resultSummary: { type: "string", description: "Optional short summary of the tool result" }
1647
+ },
1648
+ required: ["actionId", "toolName"]
1649
+ }
1608
1650
  }
1609
1651
  ];
1610
1652
  async function startMcpServer(config, cwd2) {
@@ -1675,6 +1717,9 @@ async function dispatch(name, args, db, docsPath) {
1675
1717
  case "tasks.update": {
1676
1718
  const id = num(args, "id");
1677
1719
  const status = str(args, "status");
1720
+ if (status === "done") {
1721
+ db.closeOrphanedActions(id);
1722
+ }
1678
1723
  const task2 = db.updateTaskStatus(id, status);
1679
1724
  return ok(JSON.stringify(task2));
1680
1725
  }
@@ -1696,6 +1741,14 @@ async function dispatch(name, args, db, docsPath) {
1696
1741
  db.markAcceptanceMet(criterionId);
1697
1742
  return ok(JSON.stringify({ criterionId, met: true }));
1698
1743
  }
1744
+ case "actions.record_tool": {
1745
+ const actionId = str(args, "actionId");
1746
+ const toolName = str(args, "toolName");
1747
+ const argsJson = args["argsJson"];
1748
+ const resultSummary = args["resultSummary"];
1749
+ db.recordTool(actionId, toolName, argsJson, resultSummary);
1750
+ return ok(JSON.stringify({ actionId, toolName, recorded: true }));
1751
+ }
1699
1752
  default:
1700
1753
  return ok(`Unknown tool: ${name}`, true);
1701
1754
  }
@@ -1708,7 +1761,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
1708
1761
  for (const file of files) {
1709
1762
  if (results.length >= maxResults) break;
1710
1763
  try {
1711
- const content = readFileSync5(file, "utf8");
1764
+ const content = readFileSync6(file, "utf8");
1712
1765
  const lines = content.split("\n");
1713
1766
  for (let i = 0; i < lines.length; i++) {
1714
1767
  const lower = lines[i].toLowerCase();
@@ -1729,7 +1782,7 @@ function collectMarkdownFiles(dir) {
1729
1782
  const files = [];
1730
1783
  try {
1731
1784
  for (const entry of readdirSync(dir)) {
1732
- const full = join10(dir, entry);
1785
+ const full = join11(dir, entry);
1733
1786
  const stat = statSync(full);
1734
1787
  if (stat.isDirectory()) {
1735
1788
  files.push(...collectMarkdownFiles(full));
@@ -1834,13 +1887,13 @@ async function runStatus(cwd2, opts) {
1834
1887
  }
1835
1888
 
1836
1889
  // src/commands/sync.ts
1837
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
1838
- import { join as join11, resolve as resolve8 } from "path";
1890
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1891
+ import { join as join12, resolve as resolve8 } from "path";
1839
1892
  import pc9 from "picocolors";
1840
1893
  async function runSync(cwd2, opts) {
1841
1894
  const config = await loadConfig(cwd2);
1842
1895
  const direction = opts.direction ?? "both";
1843
- const featureListPath = resolve8(join11(cwd2, config.storage.dir, "feature_list.json"));
1896
+ const featureListPath = resolve8(join12(cwd2, config.storage.dir, "feature_list.json"));
1844
1897
  const db = openDB(config, cwd2);
1845
1898
  try {
1846
1899
  if (direction === "in" || direction === "both") {
@@ -1854,13 +1907,13 @@ async function runSync(cwd2, opts) {
1854
1907
  }
1855
1908
  }
1856
1909
  async function syncIn(featureListPath, db, dryRun) {
1857
- if (!existsSync7(featureListPath)) {
1910
+ if (!existsSync8(featureListPath)) {
1858
1911
  console.log(pc9.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
1859
1912
  return;
1860
1913
  }
1861
1914
  let seeds;
1862
1915
  try {
1863
- seeds = JSON.parse(readFileSync6(featureListPath, "utf8"));
1916
+ seeds = JSON.parse(readFileSync7(featureListPath, "utf8"));
1864
1917
  } catch (err) {
1865
1918
  console.error(pc9.red(`Failed to parse feature_list.json: ${err}`));
1866
1919
  process.exit(1);
@@ -1938,14 +1991,14 @@ async function runTaskAdd(cwd2) {
1938
1991
 
1939
1992
  // src/commands/task/done.ts
1940
1993
  import { spawnSync as spawnSync2 } from "child_process";
1941
- import { existsSync as existsSync8 } from "fs";
1994
+ import { existsSync as existsSync9 } from "fs";
1942
1995
  import { resolve as resolve9 } from "path";
1943
1996
  import pc11 from "picocolors";
1944
1997
  async function runTaskDone(cwd2, idOrSlug) {
1945
1998
  const config = await loadConfig(cwd2);
1946
1999
  if (config.health.required) {
1947
2000
  const scriptPath = resolve9(cwd2, config.health.scriptPath);
1948
- if (existsSync8(scriptPath)) {
2001
+ if (existsSync9(scriptPath)) {
1949
2002
  const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
1950
2003
  if (result.status !== 0) {
1951
2004
  console.error(pc11.red("\u2717 Health check failed \u2014 cannot mark task as done."));
@@ -2016,10 +2069,10 @@ async function runTaskList(cwd2, opts) {
2016
2069
 
2017
2070
  // src/core/package-data.ts
2018
2071
  import { createRequire as createRequire2 } from "module";
2019
- import { dirname as dirname5, join as join12 } from "path";
2072
+ import { dirname as dirname5, join as join13 } from "path";
2020
2073
  import { fileURLToPath as fileURLToPath3 } from "url";
2021
2074
  var require2 = createRequire2(import.meta.url);
2022
- var pkgPath = join12(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
2075
+ var pkgPath = join13(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
2023
2076
  var pkg = require2(pkgPath);
2024
2077
 
2025
2078
  // src/core/update-check.ts
@@ -2027,15 +2080,15 @@ import pc13 from "picocolors";
2027
2080
  var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
2028
2081
  var TIMEOUT_MS = 2500;
2029
2082
  function checkForUpdate(currentVersion) {
2030
- return new Promise((resolve10) => {
2031
- const timer = setTimeout(() => resolve10(null), TIMEOUT_MS);
2083
+ return new Promise((resolve11) => {
2084
+ const timer = setTimeout(() => resolve11(null), TIMEOUT_MS);
2032
2085
  fetch(REGISTRY_URL).then((res) => res.json()).then((data) => {
2033
2086
  clearTimeout(timer);
2034
2087
  const latest = data.version;
2035
- resolve10(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
2088
+ resolve11(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
2036
2089
  }).catch(() => {
2037
2090
  clearTimeout(timer);
2038
- resolve10(null);
2091
+ resolve11(null);
2039
2092
  });
2040
2093
  });
2041
2094
  }
@@ -2067,6 +2120,134 @@ function stripAnsi2(str2) {
2067
2120
  return str2.replace(/\x1B\[[0-9;]*m/g, "");
2068
2121
  }
2069
2122
 
2123
+ // src/commands/reset.ts
2124
+ import { existsSync as existsSync10, readdirSync as readdirSync2, rmSync } from "fs";
2125
+ import { join as join14, resolve as resolve10 } from "path";
2126
+ import * as p5 from "@clack/prompts";
2127
+ import pc14 from "picocolors";
2128
+ async function resetAgentMds(cwd2, provider) {
2129
+ const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
2130
+ const agentDirPath = resolve10(cwd2, agentDir);
2131
+ if (!existsSync10(agentDirPath)) {
2132
+ console.log(pc14.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2133
+ return;
2134
+ }
2135
+ const existingFiles = [];
2136
+ try {
2137
+ const files = readdirSync2(agentDirPath);
2138
+ for (const f of files) {
2139
+ if (f.endsWith(".md")) {
2140
+ existingFiles.push(f);
2141
+ }
2142
+ }
2143
+ } catch {
2144
+ console.log(pc14.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2145
+ return;
2146
+ }
2147
+ if (existingFiles.length === 0) {
2148
+ console.log(pc14.yellow(` No agent MD files found in ${agentDir}/`));
2149
+ return;
2150
+ }
2151
+ for (const file of existingFiles) {
2152
+ const confirm3 = await p5.confirm({
2153
+ message: `Remove ${file}?`,
2154
+ initialValue: true
2155
+ });
2156
+ if (p5.isCancel(confirm3)) {
2157
+ console.log(pc14.red(" Cancelled by user."));
2158
+ return;
2159
+ }
2160
+ if (confirm3) {
2161
+ try {
2162
+ const filePath = join14(agentDirPath, file);
2163
+ rmSync(filePath, { force: true });
2164
+ console.log(pc14.green(` Removed ${file}`));
2165
+ } catch {
2166
+ console.error(pc14.red(` Failed to remove ${file}`));
2167
+ }
2168
+ } else {
2169
+ console.log(pc14.cyan(` Skipped ${file}`));
2170
+ }
2171
+ }
2172
+ }
2173
+ async function runReset(cwd2, opts) {
2174
+ let config;
2175
+ try {
2176
+ config = await loadConfig(cwd2);
2177
+ } catch {
2178
+ console.error(pc14.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2179
+ process.exit(1);
2180
+ }
2181
+ const storageDir = config.storage.dir || ".harness";
2182
+ const dbPath = resolve10(cwd2, storageDir, "harness.db");
2183
+ const featureListPath = resolve10(cwd2, storageDir, "feature_list.json");
2184
+ let resetDb = false;
2185
+ let resetFeatureList = false;
2186
+ let resetAgentMdsFlag = false;
2187
+ if (existsSync10(dbPath)) {
2188
+ if (opts.force) {
2189
+ resetDb = true;
2190
+ } else {
2191
+ const confirm3 = await p5.confirm({
2192
+ message: `Delete database (${storageDir}/harness.db)?`,
2193
+ initialValue: true
2194
+ });
2195
+ if (p5.isCancel(confirm3)) {
2196
+ console.log(pc14.red(" Cancelled by user."));
2197
+ return;
2198
+ }
2199
+ resetDb = confirm3;
2200
+ }
2201
+ }
2202
+ if (existsSync10(featureListPath)) {
2203
+ if (opts.force) {
2204
+ resetFeatureList = true;
2205
+ } else {
2206
+ const confirm3 = await p5.confirm({
2207
+ message: `Delete feature list (${storageDir}/feature_list.json)?`,
2208
+ initialValue: true
2209
+ });
2210
+ if (p5.isCancel(confirm3)) {
2211
+ console.log(pc14.red(" Cancelled by user."));
2212
+ return;
2213
+ }
2214
+ resetFeatureList = confirm3;
2215
+ }
2216
+ }
2217
+ if (opts.provider) {
2218
+ resetAgentMdsFlag = true;
2219
+ }
2220
+ let changed = false;
2221
+ if (resetDb) {
2222
+ try {
2223
+ rmSync(dbPath, { force: true });
2224
+ console.log(pc14.green(` \u2713 Removed ${storageDir}/harness.db`));
2225
+ changed = true;
2226
+ } catch {
2227
+ console.error(pc14.red(` \u2717 Failed to remove ${dbPath}`));
2228
+ }
2229
+ }
2230
+ if (resetFeatureList) {
2231
+ try {
2232
+ rmSync(featureListPath, { force: true });
2233
+ console.log(pc14.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2234
+ changed = true;
2235
+ } catch {
2236
+ console.error(pc14.red(` \u2717 Failed to remove ${featureListPath}`));
2237
+ }
2238
+ }
2239
+ if (resetAgentMdsFlag) {
2240
+ console.log("");
2241
+ await resetAgentMds(cwd2, opts.provider || "claude-code");
2242
+ }
2243
+ if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
2244
+ console.log(pc14.yellow(" Nothing to reset (all items missing or skipped)."));
2245
+ return;
2246
+ }
2247
+ console.log("");
2248
+ console.log(pc14.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2249
+ }
2250
+
2070
2251
  // src/cli.ts
2071
2252
  var cwd = process.cwd();
2072
2253
  var updateCheck = checkForUpdate(pkg.version);
@@ -2109,6 +2290,9 @@ program.command("migrate").description("Migrate provider-specific files to a dif
2109
2290
  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) => {
2110
2291
  await runExport(cwd, opts);
2111
2292
  });
2293
+ 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) => {
2294
+ await runReset(cwd, opts);
2295
+ });
2112
2296
  program.hook("postAction", async () => {
2113
2297
  const update = await updateCheck;
2114
2298
  if (update) printUpdateMessage(update);