@cardor/agent-harness-kit 1.6.5 → 1.6.8
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 +2 -4
- package/dist/cli.js +25 -31
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Everything is stored locally in a SQLite database (`.harness/harness.db`). No cl
|
|
|
107
107
|
## Features
|
|
108
108
|
|
|
109
109
|
- **Provider-agnostic** — works with Claude Code, OpenCode, Codex CLI, or any MCP-compatible AI tool. Switch providers without losing your task history or reconfiguring your workflow.
|
|
110
|
-
- **Structured
|
|
110
|
+
- **Structured 5-agent workflow** — Lead, Explorer, Consultant, Builder, and Reviewer each have defined responsibilities and can only act within their role.
|
|
111
111
|
- **Atomic task claiming** — agents use `tasks.claim()` which uses a SQLite transaction to prevent two agents from picking up the same task at the same time.
|
|
112
112
|
- **Full audit trail** — every action, file touched, tool used, and section written is stored in SQLite and queryable.
|
|
113
113
|
- **Health gate** — agents must run `health.sh` and get a green exit before starting or closing any task. You define what "healthy" means.
|
|
@@ -151,7 +151,7 @@ ahk init
|
|
|
151
151
|
|
|
152
152
|
### `ahk init`
|
|
153
153
|
|
|
154
|
-
Interactive scaffold. Asks for your project name, description, AI provider,
|
|
154
|
+
Interactive scaffold. Asks for your project name, description, AI provider, docs path, task adapter, and an optional first task. Creates all harness files in the current directory.
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
157
|
ahk init
|
|
@@ -163,8 +163,6 @@ ahk init --name "my-app" --provider codex-cli --docs ./docs --tasks local
|
|
|
163
163
|
|
|
164
164
|
Run this once per project. Safe to re-run — it will not overwrite files you've customized.
|
|
165
165
|
|
|
166
|
-
**Global installation** — if you answer yes to "Install globally?", files go to `~/.claude` (Claude Code), `~/.config/opencode` (OpenCode), or `~/.codex` (Codex CLI). This lets you share one harness config across all your projects.
|
|
167
|
-
|
|
168
166
|
---
|
|
169
167
|
|
|
170
168
|
### `ahk build`
|
package/dist/cli.js
CHANGED
|
@@ -334,7 +334,7 @@ docs.search query \u2192 search ${docs
|
|
|
334
334
|
- tasks.get('in_progress') \u2192 resume if something is in progress
|
|
335
335
|
- tasks.get('pending') \u2192 pick lowest id
|
|
336
336
|
|
|
337
|
-
2. WORK (lead \u2192 explorer \u2192 builder \u2192 reviewer)
|
|
337
|
+
2. WORK (lead \u2192 explorer \u2192 consultant \u2192 builder \u2192 reviewer)
|
|
338
338
|
- Each agent calls actions.start(taskId, agentName) \u2192 actionId
|
|
339
339
|
- After EVERY tool call: actions.record_tool(actionId, toolName, args, summary)
|
|
340
340
|
- After EVERY file change: actions.record_file(actionId, filePath, operation, notes)
|
|
@@ -351,6 +351,7 @@ docs.search query \u2192 search ${docs
|
|
|
351
351
|
|-------|---------------|
|
|
352
352
|
| lead | Decomposes the task into a plan, assigns sub-agents |
|
|
353
353
|
| explorer | Reads and maps relevant code, never writes |
|
|
354
|
+
| consultant | Technical advisor, runs after explorer, before builder. Never writes code. |
|
|
354
355
|
| builder | Implements the plan, writes files |
|
|
355
356
|
| reviewer | Verifies acceptance criteria, approves or blocks |
|
|
356
357
|
|
|
@@ -418,7 +419,7 @@ docs.search query \u2192 search ${docs
|
|
|
418
419
|
- tasks.get('pending') \u2192 pick lowest id
|
|
419
420
|
- No pending tasks? \u2192 ask user, infer fields, call tasks.add, then tasks.claim
|
|
420
421
|
|
|
421
|
-
2. WORK (lead \u2192 explorer \u2192 builder \u2192 reviewer)
|
|
422
|
+
2. WORK (lead \u2192 explorer \u2192 consultant \u2192 builder \u2192 reviewer)
|
|
422
423
|
- Each agent calls actions.start(taskId, agentName) \u2192 actionId
|
|
423
424
|
- After EVERY tool call: actions.record_tool(actionId, toolName, args, summary)
|
|
424
425
|
- After EVERY file change: actions.record_file(actionId, filePath, operation, notes)
|
|
@@ -435,6 +436,7 @@ docs.search query \u2192 search ${docs
|
|
|
435
436
|
|-------|---------------|
|
|
436
437
|
| lead | Decomposes the task into a plan, assigns sub-agents |
|
|
437
438
|
| explorer | Reads and maps relevant code, never writes |
|
|
439
|
+
| consultant | Technical advisor, runs after explorer, before builder. Never writes code. |
|
|
438
440
|
| builder | Implements the plan, writes files |
|
|
439
441
|
| reviewer | Verifies acceptance criteria, approves or blocks |
|
|
440
442
|
|
|
@@ -564,6 +566,10 @@ function agentReviewerToml(vars) {
|
|
|
564
566
|
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
565
567
|
return toCodexToml("reviewer", description, body, "read-only");
|
|
566
568
|
}
|
|
569
|
+
function agentConsultantToml(vars) {
|
|
570
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("consultant", vars));
|
|
571
|
+
return toCodexToml("consultant", description, body, "read-only");
|
|
572
|
+
}
|
|
567
573
|
function translateFrontmatterForClaudeCode(md, agentName) {
|
|
568
574
|
const permissionsMap = {
|
|
569
575
|
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
@@ -629,10 +635,15 @@ async function syncAgentPermissions(cwd2) {
|
|
|
629
635
|
continue;
|
|
630
636
|
}
|
|
631
637
|
const content = readFileSync3(filePath, "utf-8");
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
638
|
+
const updated = content.replace(
|
|
639
|
+
/(tools:\n)((?: - [^\n]+\n)*)/m,
|
|
640
|
+
(_match, header, toolsSection) => {
|
|
641
|
+
const nativeLines = toolsSection.split("\n").filter((line) => line.trim() && !line.includes("mcp__"));
|
|
642
|
+
const nativeSection = nativeLines.length ? nativeLines.join("\n") + "\n" : "";
|
|
643
|
+
const mcpSection = tools.map((t) => ` - ${t}`).join("\n") + "\n";
|
|
644
|
+
return header + nativeSection + mcpSection;
|
|
645
|
+
}
|
|
646
|
+
);
|
|
636
647
|
if (updated === content) {
|
|
637
648
|
console.log(` ${agent}.md already in sync`);
|
|
638
649
|
} else {
|
|
@@ -738,6 +749,7 @@ No tasks in progress.
|
|
|
738
749
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
739
750
|
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
|
|
740
751
|
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
|
|
752
|
+
writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName }));
|
|
741
753
|
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
|
|
742
754
|
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
|
|
743
755
|
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
|
|
@@ -756,6 +768,7 @@ No tasks in progress.
|
|
|
756
768
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
757
769
|
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
|
|
758
770
|
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
|
|
771
|
+
writeAgentFile(cwd2, ".codex/agents/consultant.toml", agentConsultantToml({ projectName }));
|
|
759
772
|
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
|
|
760
773
|
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
|
|
761
774
|
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
|
|
@@ -800,6 +813,7 @@ No tasks in progress.
|
|
|
800
813
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
801
814
|
writeAgentFile(cwd2, ".opencode/agents/lead.md", translateFrontmatterForOpenCode(agentLead({ projectName })));
|
|
802
815
|
writeAgentFile(cwd2, ".opencode/agents/explorer.md", translateFrontmatterForOpenCode(agentExplorer({ projectName, allowedPaths })));
|
|
816
|
+
writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
|
|
803
817
|
writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
|
|
804
818
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
805
819
|
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
@@ -817,6 +831,7 @@ No tasks in progress.
|
|
|
817
831
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
818
832
|
writeAgentFile(cwd2, ".opencode/agents/lead.md", translateFrontmatterForOpenCode(agentLead({ projectName })));
|
|
819
833
|
writeAgentFile(cwd2, ".opencode/agents/explorer.md", translateFrontmatterForOpenCode(agentExplorer({ projectName, allowedPaths })));
|
|
834
|
+
writeAgentFile(cwd2, ".opencode/agents/consultant.md", translateFrontmatterForOpenCode(agentConsultant({ projectName })));
|
|
820
835
|
writeAgentFile(cwd2, ".opencode/agents/builder.md", translateFrontmatterForOpenCode(agentBuilder({ projectName, writablePaths })));
|
|
821
836
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", translateFrontmatterForOpenCode(agentReviewer({ projectName })));
|
|
822
837
|
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
@@ -1792,7 +1807,7 @@ async function runHealth(cwd2) {
|
|
|
1792
1807
|
if (!dbOk) allOk = false;
|
|
1793
1808
|
const providerFiles = getProviderHealthFiles(config.provider);
|
|
1794
1809
|
const agentsDir = providerFiles.agentsDir;
|
|
1795
|
-
const agentNames = ["lead", "explorer", "builder", "reviewer"];
|
|
1810
|
+
const agentNames = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
1796
1811
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1797
1812
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1798
1813
|
const name = agentNames[i];
|
|
@@ -1856,7 +1871,6 @@ function getProviderHealthFiles(provider) {
|
|
|
1856
1871
|
|
|
1857
1872
|
// src/commands/init.ts
|
|
1858
1873
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
1859
|
-
import { homedir } from "os";
|
|
1860
1874
|
import { join as join12 } from "path";
|
|
1861
1875
|
import * as p3 from "@clack/prompts";
|
|
1862
1876
|
import pc6 from "picocolors";
|
|
@@ -2051,18 +2065,6 @@ async function runInit(cwd2, flags) {
|
|
|
2051
2065
|
}
|
|
2052
2066
|
provider = val;
|
|
2053
2067
|
}
|
|
2054
|
-
let globalInstallation = false;
|
|
2055
|
-
const globalVal = await p3.confirm({
|
|
2056
|
-
message: "Install globally (to home directory)?",
|
|
2057
|
-
initialValue: false
|
|
2058
|
-
});
|
|
2059
|
-
if (p3.isCancel(globalVal)) {
|
|
2060
|
-
p3.cancel("Cancelled.");
|
|
2061
|
-
process.exit(0);
|
|
2062
|
-
}
|
|
2063
|
-
if (globalVal) {
|
|
2064
|
-
globalInstallation = true;
|
|
2065
|
-
}
|
|
2066
2068
|
let docsPath;
|
|
2067
2069
|
if (flags.docs) {
|
|
2068
2070
|
docsPath = flags.docs;
|
|
@@ -2137,14 +2139,7 @@ async function runInit(cwd2, flags) {
|
|
|
2137
2139
|
try {
|
|
2138
2140
|
const config = applyConfigDefaults({ name, description, provider, docsPath, tasksAdapter });
|
|
2139
2141
|
const materializer = getMaterializer(provider);
|
|
2140
|
-
|
|
2141
|
-
if (globalInstallation) {
|
|
2142
|
-
if (provider === "claude-code") {
|
|
2143
|
-
installDir = join12(homedir(), ".claude");
|
|
2144
|
-
} else {
|
|
2145
|
-
installDir = join12(homedir(), ".config", "opencode");
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2142
|
+
const installDir = cwd2;
|
|
2148
2143
|
const configContent = configTs({
|
|
2149
2144
|
name,
|
|
2150
2145
|
description,
|
|
@@ -2173,8 +2168,7 @@ async function runInit(cwd2, flags) {
|
|
|
2173
2168
|
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
2174
2169
|
throw err;
|
|
2175
2170
|
}
|
|
2176
|
-
|
|
2177
|
-
console.log(pc6.green(`\u2713 Scaffolded harness in ${agentHarnessKitDir}`));
|
|
2171
|
+
console.log(pc6.green("\u2713 Scaffolded harness in current directory"));
|
|
2178
2172
|
const agentsDir = provider === "claude-code" ? ".claude/agents/" : ".opencode/agents/";
|
|
2179
2173
|
const mcpFile = provider === "claude-code" ? ".claude/mcp.json" : "./opencode.json";
|
|
2180
2174
|
console.log("");
|
|
@@ -2250,7 +2244,7 @@ import { existsSync as existsSync9, readdirSync, rmSync } from "fs";
|
|
|
2250
2244
|
import { join as join13, resolve as resolve9 } from "path";
|
|
2251
2245
|
import * as p5 from "@clack/prompts";
|
|
2252
2246
|
import pc8 from "picocolors";
|
|
2253
|
-
var AGENT_MD_FILES = ["lead", "explorer", "builder", "reviewer"];
|
|
2247
|
+
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2254
2248
|
async function resetAgentMds(cwd2, provider) {
|
|
2255
2249
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
2256
2250
|
const agentDirPath = resolve9(cwd2, agentDir);
|