@cardor/agent-harness-kit 1.6.0 → 1.6.2
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 +35 -1
- package/dist/agent-templates/builder.md +26 -5
- package/dist/agent-templates/lead.md +5 -0
- package/dist/cli.js +109 -17
- package/dist/cli.js.map +1 -1
- package/dist/{sqlite-XBEJJ5T2.js → sqlite-KWYK4IJW.js} +2 -2
- package/dist/sqlite-KWYK4IJW.js.map +1 -0
- package/package.json +2 -2
- package/dist/sqlite-XBEJJ5T2.js.map +0 -1
package/README.md
CHANGED
|
@@ -172,6 +172,7 @@ Regenerates `AGENTS.md` and provider-specific files from your `agent-harness-kit
|
|
|
172
172
|
```bash
|
|
173
173
|
ahk build
|
|
174
174
|
ahk build --watch # watch mode: rebuilds automatically on config changes
|
|
175
|
+
ahk build --sync # sync tools: frontmatter in .claude/agents/*.md to match current permission constants
|
|
175
176
|
```
|
|
176
177
|
|
|
177
178
|
---
|
|
@@ -186,6 +187,8 @@ ahk dashboard --port 8080 # custom port
|
|
|
186
187
|
ahk dashboard --no-open # start server without opening browser
|
|
187
188
|
```
|
|
188
189
|
|
|
190
|
+
If the requested port (default `4242`) is already in use, `ahk dashboard` automatically tries up to 10 sequential ports (e.g. `4242 → 4243 → … → 4251`). The actual port opened is printed to the console. If all 10 ports are exhausted, the command exits with a clear error message showing which port range was attempted.
|
|
191
|
+
|
|
189
192
|
The dashboard includes:
|
|
190
193
|
|
|
191
194
|
| View | What it shows |
|
|
@@ -409,6 +412,12 @@ your-project/
|
|
|
409
412
|
|
|
410
413
|
---
|
|
411
414
|
|
|
415
|
+
## Tasks schema
|
|
416
|
+
|
|
417
|
+
The `tasks` table includes an `updated_at` timestamp column, set on creation and automatically updated on every status change. On first run after upgrading from an older version, existing rows are backfilled with `COALESCE(completed_at, started_at, created_at)`. Tasks returned by `tasks.get` are ordered by status priority (pending → in_progress → blocked → done) then by `updated_at` descending.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
412
421
|
## What you can customize
|
|
413
422
|
|
|
414
423
|
### `agent-harness-kit.config.ts`
|
|
@@ -574,7 +583,7 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
|
|
|
574
583
|
| `tasks.claim` | `id, agent` | Atomically claim a pending task. Returns `task_already_claimed` if another agent got it first |
|
|
575
584
|
| `tasks.update` | `id, status` | Change task status |
|
|
576
585
|
| `tasks.add` | `title, slug?, description?, acceptance?` | Create a new task directly from MCP (agents can queue work on the fly) |
|
|
577
|
-
| `tasks.acceptance.update` | `criterionId` | Mark an acceptance criterion as met. Criterion IDs come from `tasks.
|
|
586
|
+
| `tasks.acceptance.update` | `criterionId` | Mark an acceptance criterion as met. Criterion IDs come from `tasks.acceptance_get` |
|
|
578
587
|
| `actions.start` | `taskId, agent` | Start a new action, returns `actionId` |
|
|
579
588
|
| `actions.write` | `actionId, sectionType, content` | Record a text section: `result \| tools_used \| blockers \| next_steps`. Does **not** populate the Files dashboard — use `actions.record_file` for that |
|
|
580
589
|
| `actions.complete` | `actionId, summary` | Close an action with a one-line summary |
|
|
@@ -582,6 +591,7 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
|
|
|
582
591
|
| `actions.record_file` | `actionId, filePath, operation, notes?` | Register a file touch. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted` |
|
|
583
592
|
| `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard |
|
|
584
593
|
| `docs.search` | `query` | Search the `docsPath` folder for content matching the query |
|
|
594
|
+
| `tasks.acceptance_get` | `taskId` | Returns all acceptance criteria for a task with their `id`, `task_id`, `criterion` text, and `met` status. Use the returned `id` values with `tasks.acceptance.update` |
|
|
585
595
|
|
|
586
596
|
---
|
|
587
597
|
|
|
@@ -594,6 +604,30 @@ The harness exposes these tools via MCP. Agents use them instead of reading file
|
|
|
594
604
|
| **builder** | Implements the plan. Only writes to `writablePaths`. Records every file modified. |
|
|
595
605
|
| **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
|
|
596
606
|
|
|
607
|
+
### MCP tool permissions by role
|
|
608
|
+
|
|
609
|
+
Each agent role has a scoped set of MCP tools enforced through the agent definition files.
|
|
610
|
+
|
|
611
|
+
| Tool | lead | explorer | builder | reviewer |
|
|
612
|
+
|---|:---:|:---:|:---:|:---:|
|
|
613
|
+
| `tasks.get` | ✅ | ✅ | ✅ | ✅ |
|
|
614
|
+
| `tasks.claim` | ✅ | ✅ | ✅ | ✅ |
|
|
615
|
+
| `tasks.add` | ✅ | ❌ | ✅ | ✅ |
|
|
616
|
+
| `tasks.update` | ✅ | ❌ | ✅ | ✅ |
|
|
617
|
+
| `tasks.edit` | ✅ | ❌ | ✅ | ✅ |
|
|
618
|
+
| `tasks.archive` / `unarchive` | ✅ | ❌ | ✅ | ✅ |
|
|
619
|
+
| `tasks.acceptance_get` | ✅ | ✅ | ✅ | ✅ |
|
|
620
|
+
| `tasks.acceptance.update` | ❌ | ❌ | ❌ | ✅ |
|
|
621
|
+
| `actions.*` (all 6) | ✅ | ✅ | ✅ | ✅ |
|
|
622
|
+
| `docs.search` | ✅ | ✅ | ✅ | ✅ |
|
|
623
|
+
| `permissions.check` | ✅ | ✅ | ✅ | ✅ |
|
|
624
|
+
|
|
625
|
+
**explorer** is read-only for task state — can query but cannot mutate status or mark criteria.
|
|
626
|
+
**reviewer** is the only role that can mark acceptance criteria as met (`tasks.acceptance.update`).
|
|
627
|
+
**lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
|
|
628
|
+
|
|
629
|
+
`permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
|
|
630
|
+
|
|
597
631
|
---
|
|
598
632
|
|
|
599
633
|
## What to commit
|
|
@@ -99,13 +99,34 @@ The explorer identified how this codebase works. Use those patterns. Do not intr
|
|
|
99
99
|
|
|
100
100
|
If tests fail, fix them before completing your action. Do not leave the codebase in a broken state.
|
|
101
101
|
|
|
102
|
-
### 6. Sync README and docs
|
|
102
|
+
### 6. Sync README and docs — MANDATORY
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Before completing your action, you **must** check whether any user-facing behavior changed and update docs accordingly. This step is not optional.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
106
|
+
**Step 1 — Search actively:**
|
|
107
|
+
```bash
|
|
108
|
+
grep -n "your-feature-keyword" README.md docs/**/*.md 2>/dev/null
|
|
109
|
+
```
|
|
110
|
+
Search for keywords related to the files you changed (CLI commands, MCP tool names, config keys, DB columns, agent behavior). Read any matching sections.
|
|
111
|
+
|
|
112
|
+
**Step 2 — Update or justify:**
|
|
113
|
+
- If a matching section exists → update it to reflect the new behavior.
|
|
114
|
+
- If no section exists but the change is user-facing → add one in the appropriate location.
|
|
115
|
+
- If nothing is user-facing (internal refactor, tests only) → explicitly state that in your result section.
|
|
116
|
+
|
|
117
|
+
**What counts as user-facing:**
|
|
118
|
+
- New or changed CLI commands or flags
|
|
119
|
+
- New or changed MCP tools
|
|
120
|
+
- Changes to DB schema visible to users
|
|
121
|
+
- Changes to agent permissions or behavior
|
|
122
|
+
- New config options
|
|
123
|
+
|
|
124
|
+
**Step 3 — Report in your result section:**
|
|
125
|
+
Always end your result with one of:
|
|
126
|
+
- `Docs updated: README.md lines X–Y (description of what changed)`
|
|
127
|
+
- `No docs update needed: this change is internal only ([specific reason])`
|
|
128
|
+
|
|
129
|
+
Never leave this blank or skip it silently.
|
|
109
130
|
|
|
110
131
|
### 7. Record your result
|
|
111
132
|
|
|
@@ -87,6 +87,10 @@ bash health.sh
|
|
|
87
87
|
|
|
88
88
|
If exit code ≠ 0 → **stop immediately**. Report the health failure and do not proceed.
|
|
89
89
|
|
|
90
|
+
Then call `permissions.check` — if `in_sync: false`, inform the user before proceeding:
|
|
91
|
+
> "Your agent permissions are outdated. Run `ahk build --sync` to update, or I can guide you."
|
|
92
|
+
Wait for the user to acknowledge before continuing the session.
|
|
93
|
+
|
|
90
94
|
Then check session state via MCP:
|
|
91
95
|
|
|
92
96
|
```
|
|
@@ -134,6 +138,7 @@ Think through:
|
|
|
134
138
|
- What exactly should the builder implement?
|
|
135
139
|
- What are the acceptance criteria the reviewer will check?
|
|
136
140
|
- If codebase changes are involved: does the builder need to update README or `docs/` files?
|
|
141
|
+
- Does this task touch user-facing behavior (CLI commands, MCP tools, DB schema, config, agent permissions)? If yes, add an acceptance criterion: `README.md and/or docs/ updated to reflect the change`
|
|
137
142
|
|
|
138
143
|
Record it:
|
|
139
144
|
|
package/dist/cli.js
CHANGED
|
@@ -589,6 +589,32 @@ function appendGitignore(cwd2) {
|
|
|
589
589
|
function slugify(title) {
|
|
590
590
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
591
591
|
}
|
|
592
|
+
var AGENT_TOOLS = {
|
|
593
|
+
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
594
|
+
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
595
|
+
builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
|
|
596
|
+
reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
|
|
597
|
+
};
|
|
598
|
+
async function syncAgentPermissions(cwd2) {
|
|
599
|
+
for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
|
|
600
|
+
const filePath = join3(cwd2, ".claude", "agents", `${agent}.md`);
|
|
601
|
+
if (!existsSync2(filePath)) {
|
|
602
|
+
console.log(` ${agent}.md not found \u2014 skipping`);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
606
|
+
const toolsBlock = `tools:
|
|
607
|
+
${tools.map((t) => ` - ${t}`).join("\n")}
|
|
608
|
+
`;
|
|
609
|
+
const updated = content.replace(/tools:\n(?: - [^\n]+\n)*/m, toolsBlock);
|
|
610
|
+
if (updated === content) {
|
|
611
|
+
console.log(` ${agent}.md already in sync`);
|
|
612
|
+
} else {
|
|
613
|
+
writeFileSync3(filePath, updated, "utf-8");
|
|
614
|
+
console.log(` ${agent}.md updated`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
592
618
|
|
|
593
619
|
// src/core/materializer/claude-code.ts
|
|
594
620
|
var ClaudeCodeMaterializer = class {
|
|
@@ -789,6 +815,9 @@ function getMaterializer(provider) {
|
|
|
789
815
|
// src/commands/build.ts
|
|
790
816
|
async function runBuild(cwd2, opts) {
|
|
791
817
|
await buildOnce(cwd2);
|
|
818
|
+
if (opts.sync) {
|
|
819
|
+
await syncAgentPermissions(cwd2);
|
|
820
|
+
}
|
|
792
821
|
if (opts.watch) {
|
|
793
822
|
p.log.info(`Watching agent-harness-kit.config.ts for changes...`);
|
|
794
823
|
watch(cwd2, { recursive: false }, async (_, filename) => {
|
|
@@ -1642,7 +1671,7 @@ async function openDB(config, cwd2) {
|
|
|
1642
1671
|
const { MySQLDriver } = await import("./mysql-THKQOXIS.js");
|
|
1643
1672
|
driver = new MySQLDriver(dbConfig);
|
|
1644
1673
|
} else {
|
|
1645
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
1674
|
+
const { SQLiteDriver } = await import("./sqlite-KWYK4IJW.js");
|
|
1646
1675
|
if (dbConfig.type !== "sqlite") {
|
|
1647
1676
|
throw new Error("Invalid database type");
|
|
1648
1677
|
}
|
|
@@ -2324,14 +2353,56 @@ async function runReset(cwd2, opts) {
|
|
|
2324
2353
|
}
|
|
2325
2354
|
|
|
2326
2355
|
// src/core/mcp-server.ts
|
|
2327
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
2328
|
-
import { join as
|
|
2356
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "fs";
|
|
2357
|
+
import { join as join15, resolve as resolve10 } from "path";
|
|
2329
2358
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2330
2359
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2331
2360
|
import {
|
|
2332
2361
|
CallToolRequestSchema,
|
|
2333
2362
|
ListToolsRequestSchema
|
|
2334
2363
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2364
|
+
|
|
2365
|
+
// src/core/permissions-check.ts
|
|
2366
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
2367
|
+
import { join as join14 } from "path";
|
|
2368
|
+
var CANONICAL = {
|
|
2369
|
+
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
2370
|
+
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
2371
|
+
builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
|
|
2372
|
+
reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
|
|
2373
|
+
};
|
|
2374
|
+
function parseToolsFromFrontmatter(content) {
|
|
2375
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/m);
|
|
2376
|
+
if (!match) return [];
|
|
2377
|
+
const fm = match[1];
|
|
2378
|
+
const toolsMatch = fm.match(/^tools:\n((?: - [^\n]+\n?)*)/m);
|
|
2379
|
+
if (!toolsMatch) return [];
|
|
2380
|
+
return toolsMatch[1].split("\n").map((l) => l.trim().replace(/^- /, "")).filter((l) => l.startsWith("mcp__"));
|
|
2381
|
+
}
|
|
2382
|
+
function checkPermissionsSync(cwd2) {
|
|
2383
|
+
const agents = {};
|
|
2384
|
+
let in_sync = true;
|
|
2385
|
+
for (const agent of ["lead", "explorer", "builder", "reviewer"]) {
|
|
2386
|
+
const filePath = join14(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2387
|
+
if (!existsSync10(filePath)) {
|
|
2388
|
+
const missing2 = CANONICAL[agent];
|
|
2389
|
+
agents[agent] = { ok: false, missing: missing2, extra: [] };
|
|
2390
|
+
in_sync = false;
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2394
|
+
const installed = parseToolsFromFrontmatter(content);
|
|
2395
|
+
const canonical = CANONICAL[agent];
|
|
2396
|
+
const missing = canonical.filter((t) => !installed.includes(t));
|
|
2397
|
+
const extra = installed.filter((t) => !canonical.includes(t));
|
|
2398
|
+
const ok2 = missing.length === 0 && extra.length === 0;
|
|
2399
|
+
if (!ok2) in_sync = false;
|
|
2400
|
+
agents[agent] = { ok: ok2, missing, extra };
|
|
2401
|
+
}
|
|
2402
|
+
return { in_sync, agents };
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/core/mcp-server.ts
|
|
2335
2406
|
var VERSION = "0.1.0";
|
|
2336
2407
|
var TOOLS = [
|
|
2337
2408
|
{
|
|
@@ -2555,6 +2626,11 @@ var TOOLS = [
|
|
|
2555
2626
|
},
|
|
2556
2627
|
required: ["id"]
|
|
2557
2628
|
}
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
name: "permissions.check",
|
|
2632
|
+
description: "Check whether the .claude/agents/*.md tool permission lists are in sync with the current canonical permission constants. Returns per-agent diff with missing and extra tools. Call this at session start to detect outdated agent files after an ahk upgrade.",
|
|
2633
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2558
2634
|
}
|
|
2559
2635
|
];
|
|
2560
2636
|
async function startMcpServer(config, cwd2) {
|
|
@@ -2569,7 +2645,7 @@ async function startMcpServer(config, cwd2) {
|
|
|
2569
2645
|
const { name, arguments: args } = request.params;
|
|
2570
2646
|
const a = args ?? {};
|
|
2571
2647
|
try {
|
|
2572
|
-
const result = await dispatch(name, a, db, docsPath);
|
|
2648
|
+
const result = await dispatch(name, a, db, docsPath, cwd2);
|
|
2573
2649
|
return result;
|
|
2574
2650
|
} catch (err) {
|
|
2575
2651
|
return ok(`Error: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
@@ -2578,7 +2654,7 @@ async function startMcpServer(config, cwd2) {
|
|
|
2578
2654
|
const transport = new StdioServerTransport();
|
|
2579
2655
|
await server.connect(transport);
|
|
2580
2656
|
}
|
|
2581
|
-
async function dispatch(name, args, db, docsPath) {
|
|
2657
|
+
async function dispatch(name, args, db, docsPath, cwd2) {
|
|
2582
2658
|
switch (name) {
|
|
2583
2659
|
case "actions.start": {
|
|
2584
2660
|
const taskId = num(args, "taskId");
|
|
@@ -2697,6 +2773,10 @@ async function dispatch(name, args, db, docsPath) {
|
|
|
2697
2773
|
const task2 = await db.unarchiveTask(id);
|
|
2698
2774
|
return ok(JSON.stringify(task2));
|
|
2699
2775
|
}
|
|
2776
|
+
case "permissions.check": {
|
|
2777
|
+
const result = checkPermissionsSync(cwd2);
|
|
2778
|
+
return ok(JSON.stringify(result, null, 2));
|
|
2779
|
+
}
|
|
2700
2780
|
default:
|
|
2701
2781
|
return ok(`Unknown tool: ${name}`, true);
|
|
2702
2782
|
}
|
|
@@ -2709,7 +2789,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
2709
2789
|
for (const file of files) {
|
|
2710
2790
|
if (results.length >= maxResults) break;
|
|
2711
2791
|
try {
|
|
2712
|
-
const content =
|
|
2792
|
+
const content = readFileSync7(file, "utf8");
|
|
2713
2793
|
const lines = content.split("\n");
|
|
2714
2794
|
for (let i = 0; i < lines.length; i++) {
|
|
2715
2795
|
const lower = lines[i].toLowerCase();
|
|
@@ -2730,7 +2810,7 @@ function collectMarkdownFiles(dir) {
|
|
|
2730
2810
|
const files = [];
|
|
2731
2811
|
try {
|
|
2732
2812
|
for (const entry of readdirSync2(dir)) {
|
|
2733
|
-
const full =
|
|
2813
|
+
const full = join15(dir, entry);
|
|
2734
2814
|
const stat = statSync(full);
|
|
2735
2815
|
if (stat.isDirectory()) {
|
|
2736
2816
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -2764,6 +2844,18 @@ async function runServe(cwd2, opts) {
|
|
|
2764
2844
|
}
|
|
2765
2845
|
process.stderr.write(`[agent-harness-kit] MCP server starting (stdio)
|
|
2766
2846
|
`);
|
|
2847
|
+
const syncResult = checkPermissionsSync(cwd2);
|
|
2848
|
+
if (!syncResult.in_sync) {
|
|
2849
|
+
const affected = Object.entries(syncResult.agents).filter(([, r]) => !r.ok).map(([name, r]) => {
|
|
2850
|
+
const parts = [];
|
|
2851
|
+
if (r.missing.length) parts.push(`missing: ${r.missing.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
2852
|
+
if (r.extra.length) parts.push(`extra: ${r.extra.map((t) => t.replace("mcp__agent-harness-kit__", "")).join(", ")}`);
|
|
2853
|
+
return `${name} (${parts.join("; ")})`;
|
|
2854
|
+
}).join("\n ");
|
|
2855
|
+
process.stderr.write(`[agent-harness-kit] Agent permissions out of sync. Run: ahk build --sync
|
|
2856
|
+
${affected}
|
|
2857
|
+
`);
|
|
2858
|
+
}
|
|
2767
2859
|
await startMcpServer(config, cwd2);
|
|
2768
2860
|
}
|
|
2769
2861
|
|
|
@@ -2842,13 +2934,13 @@ async function runStatus(cwd2, opts) {
|
|
|
2842
2934
|
}
|
|
2843
2935
|
|
|
2844
2936
|
// src/commands/sync.ts
|
|
2845
|
-
import { existsSync as
|
|
2846
|
-
import { join as
|
|
2937
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2938
|
+
import { join as join16, resolve as resolve11 } from "path";
|
|
2847
2939
|
import pc10 from "picocolors";
|
|
2848
2940
|
async function runSync(cwd2, opts) {
|
|
2849
2941
|
const config = await loadConfig(cwd2);
|
|
2850
2942
|
const direction = opts.direction ?? "both";
|
|
2851
|
-
const featureListPath = resolve11(
|
|
2943
|
+
const featureListPath = resolve11(join16(cwd2, config.storage.dir, "feature_list.json"));
|
|
2852
2944
|
const db = await openDB(config, cwd2);
|
|
2853
2945
|
try {
|
|
2854
2946
|
if (direction === "in" || direction === "both") {
|
|
@@ -2862,13 +2954,13 @@ async function runSync(cwd2, opts) {
|
|
|
2862
2954
|
}
|
|
2863
2955
|
}
|
|
2864
2956
|
async function syncIn(featureListPath, db, dryRun) {
|
|
2865
|
-
if (!
|
|
2957
|
+
if (!existsSync11(featureListPath)) {
|
|
2866
2958
|
console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
2867
2959
|
return;
|
|
2868
2960
|
}
|
|
2869
2961
|
let seeds;
|
|
2870
2962
|
try {
|
|
2871
|
-
seeds = JSON.parse(
|
|
2963
|
+
seeds = JSON.parse(readFileSync8(featureListPath, "utf8"));
|
|
2872
2964
|
} catch (err) {
|
|
2873
2965
|
console.error(pc10.red(`Failed to parse feature_list.json: ${err}`));
|
|
2874
2966
|
process.exit(1);
|
|
@@ -2953,14 +3045,14 @@ async function runTaskAdd(cwd2) {
|
|
|
2953
3045
|
|
|
2954
3046
|
// src/commands/task/done.ts
|
|
2955
3047
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2956
|
-
import { existsSync as
|
|
3048
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2957
3049
|
import { resolve as resolve12 } from "path";
|
|
2958
3050
|
import pc12 from "picocolors";
|
|
2959
3051
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
2960
3052
|
const config = await loadConfig(cwd2);
|
|
2961
3053
|
if (config.health.required) {
|
|
2962
3054
|
const scriptPath = resolve12(cwd2, config.health.scriptPath);
|
|
2963
|
-
if (
|
|
3055
|
+
if (existsSync12(scriptPath)) {
|
|
2964
3056
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
2965
3057
|
if (result.status !== 0) {
|
|
2966
3058
|
console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
@@ -3133,10 +3225,10 @@ async function runTaskList(cwd2, opts) {
|
|
|
3133
3225
|
|
|
3134
3226
|
// src/core/package-data.ts
|
|
3135
3227
|
import { createRequire } from "module";
|
|
3136
|
-
import { dirname as dirname5, join as
|
|
3228
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
3137
3229
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3138
3230
|
var require2 = createRequire(import.meta.url);
|
|
3139
|
-
var pkgPath =
|
|
3231
|
+
var pkgPath = join17(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
3140
3232
|
var pkg = require2(pkgPath);
|
|
3141
3233
|
|
|
3142
3234
|
// src/core/update-check.ts
|
|
@@ -3180,7 +3272,7 @@ program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for mu
|
|
|
3180
3272
|
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)").action(async (opts) => {
|
|
3181
3273
|
await runInit(cwd, opts);
|
|
3182
3274
|
});
|
|
3183
|
-
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").action(async (opts) => {
|
|
3275
|
+
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").action(async (opts) => {
|
|
3184
3276
|
await runBuild(cwd, opts);
|
|
3185
3277
|
});
|
|
3186
3278
|
program.command("health").description("Run health.sh and report result").action(async () => {
|