@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),
|
|
41
|
+
After **each** tool invocation (Read, Edit, Write, Bash), call **both**:
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
actions.
|
|
44
|
+
actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Examples:
|
|
48
|
-
- `Read
|
|
49
|
-
- `Bash
|
|
50
|
-
- `Edit
|
|
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),
|
|
54
|
+
After **each** file modification (Edit, Write), call:
|
|
55
55
|
|
|
56
56
|
```
|
|
57
|
-
actions.
|
|
57
|
+
actions.record_file(actionId, '<file-path>', '<operation>', '<what changed and why>')
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
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 `
|
|
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.**
|
|
129
|
-
- **Log every tool call.**
|
|
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),
|
|
40
|
+
After **each** tool invocation (Read, Bash, grep, docs.search), call:
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
actions.
|
|
43
|
+
actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Examples:
|
|
47
|
-
- `Read
|
|
48
|
-
- `Bash
|
|
49
|
-
- `docs.search
|
|
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
|
|
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),
|
|
35
|
+
After **each** tool invocation (Bash, tasks.get, tasks.claim, actions.get), call:
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
actions.
|
|
38
|
+
actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Examples:
|
|
42
|
-
- `Bash
|
|
43
|
-
- `tasks.get
|
|
44
|
-
- `actions.get
|
|
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),
|
|
35
|
+
After **each** tool invocation (Read, Bash), call:
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
actions.
|
|
38
|
+
actions.record_tool(actionId, '<ToolName>', '<args-summary>', '<why>')
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Examples:
|
|
42
|
-
- `Read
|
|
43
|
-
- `Bash
|
|
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
|
|
47
|
+
For **each** criterion, call this immediately after you evaluate it using its `id` from `tasks.get`:
|
|
48
48
|
|
|
49
49
|
```
|
|
50
|
-
tasks.
|
|
50
|
+
tasks.acceptance.update(criterionId)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
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.
|
|
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
|
|
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
|
|
@@ -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
|
|
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
|
|
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 =
|
|
1396
|
+
installDir = join10(homedir(), ".claude");
|
|
1369
1397
|
} else {
|
|
1370
|
-
installDir =
|
|
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(
|
|
1382
|
-
mkdirSync6(
|
|
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
|
|
1465
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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
|
|
1838
|
-
import { join as
|
|
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(
|
|
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 (!
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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((
|
|
2031
|
-
const timer = setTimeout(() =>
|
|
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
|
-
|
|
2088
|
+
resolve11(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
2036
2089
|
}).catch(() => {
|
|
2037
2090
|
clearTimeout(timer);
|
|
2038
|
-
|
|
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);
|