@cardor/agent-harness-kit 1.1.6 → 1.2.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/README.md +168 -88
- package/dist/agent-templates/builder.md +1 -0
- package/dist/agent-templates/explorer.md +1 -0
- package/dist/agent-templates/reviewer.md +1 -0
- package/dist/cli.js +365 -220
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -11,23 +11,25 @@ import * as p from "@clack/prompts";
|
|
|
11
11
|
import pc from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/core/materializer/claude-code.ts
|
|
14
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
15
|
-
import { join as
|
|
14
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
15
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
16
|
+
|
|
17
|
+
// src/utils/file.ts
|
|
18
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
19
|
+
import { join, resolve } from "path";
|
|
20
|
+
var write = (cwd2, relPath, content, mode) => {
|
|
21
|
+
const abs = join(cwd2, relPath);
|
|
22
|
+
mkdirSync(resolve(abs, ".."), { recursive: true });
|
|
23
|
+
writeFileSync(abs, content, { encoding: "utf8", mode });
|
|
24
|
+
};
|
|
16
25
|
|
|
17
26
|
// src/core/materializer/mcp-merge.ts
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
27
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
19
28
|
import { dirname } from "path";
|
|
20
|
-
var compactionConfig = {
|
|
21
|
-
compaction: {
|
|
22
|
-
auto: true,
|
|
23
|
-
prune: true,
|
|
24
|
-
reserved: 1e4
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
29
|
function mergeClaudeMcpJson(filePath, port) {
|
|
28
30
|
const folderPath = dirname(filePath);
|
|
29
31
|
if (!existsSync(folderPath)) {
|
|
30
|
-
|
|
32
|
+
mkdirSync2(folderPath, { recursive: true });
|
|
31
33
|
}
|
|
32
34
|
let existing = {};
|
|
33
35
|
if (existsSync(filePath)) {
|
|
@@ -36,27 +38,39 @@ function mergeClaudeMcpJson(filePath, port) {
|
|
|
36
38
|
} catch {
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
const agentHarnessKitConfig = {
|
|
40
|
-
type: "stdio",
|
|
41
|
-
command: "npx",
|
|
42
|
-
args: ["ahk", "serve", "--port", String(port)]
|
|
43
|
-
};
|
|
44
41
|
const merged = {
|
|
45
42
|
...existing,
|
|
46
|
-
compaction: existing.compaction ?? compactionConfig.compaction,
|
|
47
|
-
default_agent: "lead",
|
|
48
43
|
mcpServers: {
|
|
49
44
|
...existing.mcpServers ?? {},
|
|
50
|
-
"agent-harness-kit":
|
|
45
|
+
"agent-harness-kit": {
|
|
46
|
+
type: "stdio",
|
|
47
|
+
command: "npx",
|
|
48
|
+
args: ["ahk", "serve", "--port", String(port)]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
mkdirSync2(dirname(filePath), { recursive: true });
|
|
53
|
+
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
54
|
+
}
|
|
55
|
+
function mergeClaudeSettingsJson(filePath) {
|
|
56
|
+
mkdirSync2(dirname(filePath), { recursive: true });
|
|
57
|
+
let existing = {};
|
|
58
|
+
if (existsSync(filePath)) {
|
|
59
|
+
try {
|
|
60
|
+
existing = JSON.parse(readFileSync(filePath, "utf8"));
|
|
61
|
+
} catch {
|
|
51
62
|
}
|
|
63
|
+
}
|
|
64
|
+
const merged = {
|
|
65
|
+
...existing,
|
|
66
|
+
agent: "lead"
|
|
52
67
|
};
|
|
53
|
-
|
|
54
|
-
writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
68
|
+
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
55
69
|
}
|
|
56
70
|
function mergeOpencodeJson(filePath, port) {
|
|
57
71
|
const folderPath = dirname(filePath);
|
|
58
72
|
if (!existsSync(folderPath)) {
|
|
59
|
-
|
|
73
|
+
mkdirSync2(folderPath, { recursive: true });
|
|
60
74
|
}
|
|
61
75
|
let existing = {};
|
|
62
76
|
if (existsSync(filePath)) {
|
|
@@ -66,35 +80,73 @@ function mergeOpencodeJson(filePath, port) {
|
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
const existingMcp = existing.mcp ?? {};
|
|
69
|
-
const agentHarnessKitConfig = {
|
|
70
|
-
enabled: true,
|
|
71
|
-
type: "local",
|
|
72
|
-
command: ["npx", "ahk", "serve", "--port", String(port)]
|
|
73
|
-
};
|
|
74
83
|
const merged = {
|
|
75
84
|
...existing,
|
|
76
|
-
compaction: existing.compaction ?? compactionConfig.compaction,
|
|
77
85
|
default_agent: "lead",
|
|
86
|
+
compaction: existing.compaction ?? { auto: true, prune: true, reserved: 1e4 },
|
|
87
|
+
permission: existing.permission ?? { write: "ask" },
|
|
78
88
|
mcp: {
|
|
79
89
|
...existingMcp,
|
|
80
|
-
"agent-harness-kit":
|
|
90
|
+
"agent-harness-kit": {
|
|
91
|
+
enabled: true,
|
|
92
|
+
type: "local",
|
|
93
|
+
command: ["npx", "ahk", "serve", "--port", String(port)]
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
};
|
|
83
|
-
|
|
97
|
+
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
98
|
+
}
|
|
99
|
+
function mergeTomlSection(content, sectionName, sectionBody) {
|
|
100
|
+
const lines = content.split("\n");
|
|
101
|
+
const header = `[${sectionName}]`;
|
|
102
|
+
const startIdx = lines.findIndex((l) => l.trim() === header);
|
|
103
|
+
if (startIdx === -1) {
|
|
104
|
+
const trimmed = content.trimEnd();
|
|
105
|
+
return trimmed + (trimmed ? "\n\n" : "") + header + "\n" + sectionBody.trimEnd() + "\n";
|
|
106
|
+
}
|
|
107
|
+
let endIdx = lines.length;
|
|
108
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
109
|
+
if (/^\[/.test(lines[i])) {
|
|
110
|
+
endIdx = i;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const newLines = [
|
|
115
|
+
...lines.slice(0, startIdx),
|
|
116
|
+
header,
|
|
117
|
+
...sectionBody.trimEnd().split("\n"),
|
|
118
|
+
"",
|
|
119
|
+
...lines.slice(endIdx)
|
|
120
|
+
];
|
|
121
|
+
return newLines.join("\n");
|
|
122
|
+
}
|
|
123
|
+
function mergeCodexConfigToml(filePath, port) {
|
|
124
|
+
mkdirSync2(dirname(filePath), { recursive: true });
|
|
125
|
+
let content = "";
|
|
126
|
+
if (existsSync(filePath)) {
|
|
127
|
+
content = readFileSync(filePath, "utf8");
|
|
128
|
+
}
|
|
129
|
+
const sectionBody = [
|
|
130
|
+
'command = "npx"',
|
|
131
|
+
`args = ["ahk", "serve", "--port", "${port}"]`,
|
|
132
|
+
'default_tools_approval_mode = "auto"'
|
|
133
|
+
].join("\n");
|
|
134
|
+
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
135
|
+
writeFileSync2(filePath, content, "utf8");
|
|
84
136
|
}
|
|
85
137
|
|
|
86
138
|
// src/core/materializer/scaffold-utils.ts
|
|
87
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
88
|
-
import { join as
|
|
139
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
140
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
89
141
|
|
|
90
142
|
// src/core/materializer/templates.ts
|
|
91
143
|
import { readFileSync as readFileSync2 } from "fs";
|
|
92
|
-
import { dirname as dirname2, join } from "path";
|
|
144
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
93
145
|
import { fileURLToPath } from "url";
|
|
94
146
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
95
|
-
var TEMPLATES_DIR =
|
|
147
|
+
var TEMPLATES_DIR = join2(__dirname, "agent-templates");
|
|
96
148
|
function loadAgentTemplate(name, vars = {}) {
|
|
97
|
-
const raw = readFileSync2(
|
|
149
|
+
const raw = readFileSync2(join2(TEMPLATES_DIR, `${name}.md`), "utf8");
|
|
98
150
|
return raw.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
99
151
|
}
|
|
100
152
|
var HEALTH_SH = `#!/usr/bin/env bash
|
|
@@ -349,6 +401,55 @@ function agentReviewer(vars) {
|
|
|
349
401
|
function featureListJson(tasks) {
|
|
350
402
|
return JSON.stringify(tasks, null, 2) + "\n";
|
|
351
403
|
}
|
|
404
|
+
function stripFrontmatter(md) {
|
|
405
|
+
const parts = md.split(/^---\s*$/m);
|
|
406
|
+
if (parts.length < 3) return { description: "", body: md };
|
|
407
|
+
const frontmatter = parts[1];
|
|
408
|
+
const body = parts.slice(2).join("---").replace(/^\n/, "");
|
|
409
|
+
let description = "";
|
|
410
|
+
const foldedMatch = frontmatter.match(/^description:\s*[>|]\s*\n((?:[ \t]+[^\n]*\n?)*)/m);
|
|
411
|
+
if (foldedMatch) {
|
|
412
|
+
description = foldedMatch[1].split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
|
|
413
|
+
} else {
|
|
414
|
+
const inlineMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
415
|
+
if (inlineMatch) description = inlineMatch[1].trim();
|
|
416
|
+
}
|
|
417
|
+
return { description, body };
|
|
418
|
+
}
|
|
419
|
+
function toCodexToml(name, description, body, sandboxMode) {
|
|
420
|
+
const safe = (s) => s.replace(/"""/g, '""\\u0022');
|
|
421
|
+
return `name = "${name}"
|
|
422
|
+
sandbox_mode = "${sandboxMode}"
|
|
423
|
+
|
|
424
|
+
description = """
|
|
425
|
+
${safe(description)}
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
developer_instructions = """
|
|
429
|
+
${safe(body.trimEnd())}
|
|
430
|
+
"""
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
function agentLeadToml(vars) {
|
|
434
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
435
|
+
return toCodexToml("lead", description, body, "read-only");
|
|
436
|
+
}
|
|
437
|
+
function agentLeadAsDefaultToml(vars) {
|
|
438
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
439
|
+
return toCodexToml("default", description, body, "read-only");
|
|
440
|
+
}
|
|
441
|
+
function agentExplorerToml(vars) {
|
|
442
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("explorer", vars));
|
|
443
|
+
return toCodexToml("explorer", description, body, "read-only");
|
|
444
|
+
}
|
|
445
|
+
function agentBuilderToml(vars) {
|
|
446
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("builder", vars));
|
|
447
|
+
return toCodexToml("builder", description, body, "workspace-write");
|
|
448
|
+
}
|
|
449
|
+
function agentReviewerToml(vars) {
|
|
450
|
+
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
451
|
+
return toCodexToml("reviewer", description, body, "read-only");
|
|
452
|
+
}
|
|
352
453
|
var GITIGNORE_ENTRIES = `
|
|
353
454
|
# agent-harness-kit
|
|
354
455
|
.harness/harness.db
|
|
@@ -359,17 +460,17 @@ var GITIGNORE_ENTRIES = `
|
|
|
359
460
|
|
|
360
461
|
// src/core/materializer/scaffold-utils.ts
|
|
361
462
|
function writeAgentFile(cwd2, relPath, content) {
|
|
362
|
-
const abs =
|
|
463
|
+
const abs = join3(cwd2, relPath);
|
|
363
464
|
if (existsSync2(abs)) return;
|
|
364
|
-
|
|
365
|
-
|
|
465
|
+
mkdirSync3(resolve2(abs, ".."), { recursive: true });
|
|
466
|
+
writeFileSync3(abs, content, "utf8");
|
|
366
467
|
}
|
|
367
468
|
function appendGitignore(cwd2) {
|
|
368
|
-
const giPath =
|
|
469
|
+
const giPath = join3(cwd2, ".gitignore");
|
|
369
470
|
const existing = existsSync2(giPath) ? readFileSync3(giPath, "utf8") : "";
|
|
370
471
|
const toAdd = GITIGNORE_ENTRIES.split("\n").filter((line) => line && !existing.includes(line)).join("\n");
|
|
371
472
|
if (toAdd.trim()) {
|
|
372
|
-
|
|
473
|
+
writeFileSync3(giPath, existing + (existing.endsWith("\n") ? "" : "\n") + toAdd + "\n", "utf8");
|
|
373
474
|
}
|
|
374
475
|
}
|
|
375
476
|
function slugify(title) {
|
|
@@ -380,20 +481,16 @@ function slugify(title) {
|
|
|
380
481
|
var ClaudeCodeMaterializer = class {
|
|
381
482
|
async scaffold(config, opts) {
|
|
382
483
|
const { cwd: cwd2 } = opts;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
};
|
|
388
|
-
write("AGENTS.md", agentsMd(config));
|
|
389
|
-
write("CLAUDE.md", claudeMd(config));
|
|
390
|
-
if (!existsSync3(join3(cwd2, "health.sh"))) {
|
|
391
|
-
write("health.sh", HEALTH_SH, 493);
|
|
484
|
+
write(cwd2, "AGENTS.md", agentsMd(config));
|
|
485
|
+
write(cwd2, "CLAUDE.md", claudeMd(config));
|
|
486
|
+
if (!existsSync3(join4(cwd2, "health.sh"))) {
|
|
487
|
+
write(cwd2, "health.sh", HEALTH_SH, 493);
|
|
392
488
|
}
|
|
393
489
|
const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
|
|
394
|
-
write(
|
|
395
|
-
if (!existsSync3(
|
|
490
|
+
write(cwd2, "feature_list.json", featureListJson(tasks));
|
|
491
|
+
if (!existsSync3(join4(cwd2, config.storage.markdownFallback.path))) {
|
|
396
492
|
write(
|
|
493
|
+
cwd2,
|
|
397
494
|
config.storage.markdownFallback.path,
|
|
398
495
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
399
496
|
<!-- Run ahk status to refresh -->
|
|
@@ -411,17 +508,18 @@ No tasks in progress.
|
|
|
411
508
|
writeAgentFile(cwd2, ".claude/agents/explorer.md", agentExplorer({ projectName, allowedPaths }));
|
|
412
509
|
writeAgentFile(cwd2, ".claude/agents/builder.md", agentBuilder({ projectName, writablePaths }));
|
|
413
510
|
writeAgentFile(cwd2, ".claude/agents/reviewer.md", agentReviewer({ projectName }));
|
|
414
|
-
mergeClaudeMcpJson(
|
|
511
|
+
mergeClaudeMcpJson(join4(cwd2, ".claude/mcp.json"), config.tools.mcp.port);
|
|
512
|
+
mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
|
|
415
513
|
appendGitignore(cwd2);
|
|
416
514
|
}
|
|
417
515
|
async build(config, cwd2) {
|
|
418
|
-
const
|
|
419
|
-
const abs =
|
|
420
|
-
|
|
421
|
-
|
|
516
|
+
const write2 = (relPath, content) => {
|
|
517
|
+
const abs = join4(cwd2, relPath);
|
|
518
|
+
mkdirSync4(resolve3(abs, ".."), { recursive: true });
|
|
519
|
+
writeFileSync4(abs, content, "utf8");
|
|
422
520
|
};
|
|
423
|
-
|
|
424
|
-
|
|
521
|
+
write2("AGENTS.md", agentsMd(config));
|
|
522
|
+
write2("CLAUDE.md", claudeMd(config));
|
|
425
523
|
const projectName = config.project.name;
|
|
426
524
|
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
427
525
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
@@ -429,7 +527,70 @@ No tasks in progress.
|
|
|
429
527
|
writeAgentFile(cwd2, ".claude/agents/explorer.md", agentExplorer({ projectName, allowedPaths }));
|
|
430
528
|
writeAgentFile(cwd2, ".claude/agents/builder.md", agentBuilder({ projectName, writablePaths }));
|
|
431
529
|
writeAgentFile(cwd2, ".claude/agents/reviewer.md", agentReviewer({ projectName }));
|
|
432
|
-
mergeClaudeMcpJson(
|
|
530
|
+
mergeClaudeMcpJson(join4(cwd2, ".claude/mcp.json"), config.tools.mcp.port);
|
|
531
|
+
mergeClaudeSettingsJson(join4(cwd2, ".claude/settings.json"));
|
|
532
|
+
}
|
|
533
|
+
async migrate(config, _to, _cwd) {
|
|
534
|
+
void config;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// src/core/materializer/codex-cli.ts
|
|
539
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
540
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
541
|
+
var CodexCliMaterializer = class {
|
|
542
|
+
async scaffold(config, opts) {
|
|
543
|
+
const { cwd: cwd2 } = opts;
|
|
544
|
+
const write2 = (relPath, content, mode) => {
|
|
545
|
+
const abs = join5(cwd2, relPath);
|
|
546
|
+
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
547
|
+
writeFileSync5(abs, content, { encoding: "utf8", mode });
|
|
548
|
+
};
|
|
549
|
+
write2("AGENTS.md", agentsMd(config));
|
|
550
|
+
if (!existsSync4(join5(cwd2, "health.sh"))) {
|
|
551
|
+
write2("health.sh", HEALTH_SH, 493);
|
|
552
|
+
}
|
|
553
|
+
const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
|
|
554
|
+
write2(join5(config.storage.dir, "feature_list.json"), featureListJson(tasks));
|
|
555
|
+
if (!existsSync4(join5(cwd2, config.storage.markdownFallback.path))) {
|
|
556
|
+
write2(
|
|
557
|
+
config.storage.markdownFallback.path,
|
|
558
|
+
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
559
|
+
<!-- Run ahk status to refresh -->
|
|
560
|
+
|
|
561
|
+
# Current Session
|
|
562
|
+
|
|
563
|
+
No tasks in progress.
|
|
564
|
+
`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const projectName = config.project.name;
|
|
568
|
+
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
569
|
+
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
570
|
+
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
|
|
571
|
+
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
|
|
572
|
+
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
|
|
573
|
+
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
|
|
574
|
+
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
|
|
575
|
+
mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
|
|
576
|
+
appendGitignore(cwd2);
|
|
577
|
+
}
|
|
578
|
+
async build(config, cwd2) {
|
|
579
|
+
const write2 = (relPath, content) => {
|
|
580
|
+
const abs = join5(cwd2, relPath);
|
|
581
|
+
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
582
|
+
writeFileSync5(abs, content, "utf8");
|
|
583
|
+
};
|
|
584
|
+
write2("AGENTS.md", agentsMd(config));
|
|
585
|
+
const projectName = config.project.name;
|
|
586
|
+
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
587
|
+
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
588
|
+
writeAgentFile(cwd2, ".codex/agents/lead.toml", agentLeadToml({ projectName }));
|
|
589
|
+
writeAgentFile(cwd2, ".codex/agents/explorer.toml", agentExplorerToml({ projectName, allowedPaths }));
|
|
590
|
+
writeAgentFile(cwd2, ".codex/agents/builder.toml", agentBuilderToml({ projectName, writablePaths }));
|
|
591
|
+
writeAgentFile(cwd2, ".codex/agents/reviewer.toml", agentReviewerToml({ projectName }));
|
|
592
|
+
writeAgentFile(cwd2, ".codex/agents/default.toml", agentLeadAsDefaultToml({ projectName }));
|
|
593
|
+
mergeCodexConfigToml(join5(cwd2, ".codex/config.toml"), config.tools.mcp.port);
|
|
433
594
|
}
|
|
434
595
|
async migrate(config, _to, _cwd) {
|
|
435
596
|
void config;
|
|
@@ -437,24 +598,24 @@ No tasks in progress.
|
|
|
437
598
|
};
|
|
438
599
|
|
|
439
600
|
// src/core/materializer/opencode.ts
|
|
440
|
-
import { existsSync as
|
|
441
|
-
import { join as
|
|
601
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
602
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
442
603
|
var OpenCodeMaterializer = class {
|
|
443
604
|
async scaffold(config, opts) {
|
|
444
605
|
const { cwd: cwd2 } = opts;
|
|
445
|
-
const
|
|
446
|
-
const abs =
|
|
447
|
-
|
|
448
|
-
|
|
606
|
+
const write2 = (relPath, content, mode) => {
|
|
607
|
+
const abs = join6(cwd2, relPath);
|
|
608
|
+
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
609
|
+
writeFileSync6(abs, content, { encoding: "utf8", mode });
|
|
449
610
|
};
|
|
450
|
-
|
|
451
|
-
if (!
|
|
452
|
-
|
|
611
|
+
write2("AGENTS.md", agentsMd(config));
|
|
612
|
+
if (!existsSync5(join6(cwd2, "health.sh"))) {
|
|
613
|
+
write2("health.sh", HEALTH_SH, 493);
|
|
453
614
|
}
|
|
454
615
|
const tasks = opts.firstTask ? [{ slug: slugify(opts.firstTask.title), ...opts.firstTask }] : [];
|
|
455
|
-
|
|
456
|
-
if (!
|
|
457
|
-
|
|
616
|
+
write2(join6(config.storage.dir, "feature_list.json"), featureListJson(tasks));
|
|
617
|
+
if (!existsSync5(join6(cwd2, config.storage.markdownFallback.path))) {
|
|
618
|
+
write2(
|
|
458
619
|
config.storage.markdownFallback.path,
|
|
459
620
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
460
621
|
<!-- Run ahk status to refresh -->
|
|
@@ -472,16 +633,16 @@ No tasks in progress.
|
|
|
472
633
|
writeAgentFile(cwd2, ".opencode/agents/explorer.md", agentExplorer({ projectName, allowedPaths }));
|
|
473
634
|
writeAgentFile(cwd2, ".opencode/agents/builder.md", agentBuilder({ projectName, writablePaths }));
|
|
474
635
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", agentReviewer({ projectName }));
|
|
475
|
-
mergeOpencodeJson(
|
|
636
|
+
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
476
637
|
appendGitignore(cwd2);
|
|
477
638
|
}
|
|
478
639
|
async build(config, cwd2) {
|
|
479
|
-
const
|
|
480
|
-
const abs =
|
|
481
|
-
|
|
482
|
-
|
|
640
|
+
const write2 = (relPath, content) => {
|
|
641
|
+
const abs = join6(cwd2, relPath);
|
|
642
|
+
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
643
|
+
writeFileSync6(abs, content, "utf8");
|
|
483
644
|
};
|
|
484
|
-
|
|
645
|
+
write2("AGENTS.md", agentsMd(config));
|
|
485
646
|
const projectName = config.project.name;
|
|
486
647
|
const allowedPaths = (config.agents.explorer.allowedPaths ?? []).join(", ");
|
|
487
648
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
@@ -489,7 +650,7 @@ No tasks in progress.
|
|
|
489
650
|
writeAgentFile(cwd2, ".opencode/agents/explorer.md", agentExplorer({ projectName, allowedPaths }));
|
|
490
651
|
writeAgentFile(cwd2, ".opencode/agents/builder.md", agentBuilder({ projectName, writablePaths }));
|
|
491
652
|
writeAgentFile(cwd2, ".opencode/agents/reviewer.md", agentReviewer({ projectName }));
|
|
492
|
-
mergeOpencodeJson(
|
|
653
|
+
mergeOpencodeJson(join6(cwd2, "opencode.json"), config.tools.mcp.port);
|
|
493
654
|
}
|
|
494
655
|
async migrate(config, _to, _cwd) {
|
|
495
656
|
void config;
|
|
@@ -503,6 +664,8 @@ function getMaterializer(provider) {
|
|
|
503
664
|
return new ClaudeCodeMaterializer();
|
|
504
665
|
case "opencode":
|
|
505
666
|
return new OpenCodeMaterializer();
|
|
667
|
+
case "codex-cli":
|
|
668
|
+
return new CodexCliMaterializer();
|
|
506
669
|
default:
|
|
507
670
|
throw new Error(`Unknown provider: ${provider}`);
|
|
508
671
|
}
|
|
@@ -543,14 +706,14 @@ async function buildOnce(cwd2) {
|
|
|
543
706
|
}
|
|
544
707
|
|
|
545
708
|
// src/commands/dashboard.ts
|
|
546
|
-
import { dirname as dirname4, join as
|
|
709
|
+
import { dirname as dirname4, join as join9, resolve as resolve7 } from "path";
|
|
547
710
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
548
711
|
import pc2 from "picocolors";
|
|
549
712
|
|
|
550
713
|
// src/core/dashboard-server.ts
|
|
551
714
|
import { watch as watch2 } from "fs";
|
|
552
|
-
import { existsSync as
|
|
553
|
-
import { extname, join as
|
|
715
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
716
|
+
import { extname, join as join7 } from "path";
|
|
554
717
|
import { serve } from "@hono/node-server";
|
|
555
718
|
import { Hono } from "hono";
|
|
556
719
|
import { WebSocketServer } from "ws";
|
|
@@ -628,15 +791,15 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
628
791
|
app.get("/*", (c) => {
|
|
629
792
|
const urlPath = c.req.path;
|
|
630
793
|
if (urlPath !== "/") {
|
|
631
|
-
const candidate =
|
|
632
|
-
if (
|
|
794
|
+
const candidate = join7(staticPath, urlPath);
|
|
795
|
+
if (existsSync6(candidate)) {
|
|
633
796
|
try {
|
|
634
797
|
return fileResponse(candidate);
|
|
635
798
|
} catch {
|
|
636
799
|
}
|
|
637
800
|
}
|
|
638
801
|
}
|
|
639
|
-
return fileResponse(
|
|
802
|
+
return fileResponse(join7(staticPath, "index.html"));
|
|
640
803
|
});
|
|
641
804
|
const httpServer = serve({ fetch: app.fetch, port });
|
|
642
805
|
const wss = new WebSocketServer({ noServer: true });
|
|
@@ -663,7 +826,7 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
663
826
|
let watcher = null;
|
|
664
827
|
if (dbPath) {
|
|
665
828
|
const walPath = `${dbPath}-wal`;
|
|
666
|
-
const watchTarget =
|
|
829
|
+
const watchTarget = existsSync6(walPath) ? walPath : dbPath;
|
|
667
830
|
watcher = watch2(watchTarget, broadcast);
|
|
668
831
|
}
|
|
669
832
|
return {
|
|
@@ -679,8 +842,8 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
679
842
|
|
|
680
843
|
// src/core/db.ts
|
|
681
844
|
import { randomUUID } from "crypto";
|
|
682
|
-
import { mkdirSync as
|
|
683
|
-
import { dirname as dirname3, join as
|
|
845
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
846
|
+
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
684
847
|
|
|
685
848
|
// src/core/repositories/ActionRepository.ts
|
|
686
849
|
var ActionRepository = class {
|
|
@@ -1081,8 +1244,8 @@ var HarnessDB = class {
|
|
|
1081
1244
|
// ─── current.md fallback ──────────────────────────────────────────────────
|
|
1082
1245
|
async regenerateCurrentMd() {
|
|
1083
1246
|
if (!this.config.storage.markdownFallback.enabled) return;
|
|
1084
|
-
const mdPath =
|
|
1085
|
-
|
|
1247
|
+
const mdPath = resolve6(this.config.storage.markdownFallback.path);
|
|
1248
|
+
mkdirSync7(dirname3(mdPath), { recursive: true });
|
|
1086
1249
|
const inProgress = await this.tasks.getAll("in_progress");
|
|
1087
1250
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1088
1251
|
let md = `<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -1149,7 +1312,7 @@ var HarnessDB = class {
|
|
|
1149
1312
|
}
|
|
1150
1313
|
}
|
|
1151
1314
|
}
|
|
1152
|
-
|
|
1315
|
+
writeFileSync7(mdPath, md, "utf8");
|
|
1153
1316
|
}
|
|
1154
1317
|
// ─── Raw query escape hatch ───────────────────────────────────────────────
|
|
1155
1318
|
async queryRaw(sql, ...params) {
|
|
@@ -1191,9 +1354,9 @@ var HarnessDB = class {
|
|
|
1191
1354
|
status: t.status
|
|
1192
1355
|
}))
|
|
1193
1356
|
);
|
|
1194
|
-
const path =
|
|
1195
|
-
|
|
1196
|
-
|
|
1357
|
+
const path = join8(resolve6(cwd2), this.config.storage.dir, "feature_list.json");
|
|
1358
|
+
mkdirSync7(dirname3(path), { recursive: true });
|
|
1359
|
+
writeFileSync7(path, JSON.stringify(list, null, 2) + "\n", "utf8");
|
|
1197
1360
|
}
|
|
1198
1361
|
};
|
|
1199
1362
|
async function openDB(config, cwd2) {
|
|
@@ -1210,7 +1373,7 @@ async function openDB(config, cwd2) {
|
|
|
1210
1373
|
if (dbConfig.type !== "sqlite") {
|
|
1211
1374
|
throw new Error("Invalid database type");
|
|
1212
1375
|
}
|
|
1213
|
-
driver = new SQLiteDriver(
|
|
1376
|
+
driver = new SQLiteDriver(resolve6(cwd2, dbConfig.path));
|
|
1214
1377
|
}
|
|
1215
1378
|
await driver.ensureSchema();
|
|
1216
1379
|
return new HarnessDB(driver, config);
|
|
@@ -1221,8 +1384,8 @@ var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
|
1221
1384
|
async function runDashboard(cwd2, opts) {
|
|
1222
1385
|
const config = await loadConfig(cwd2);
|
|
1223
1386
|
const db = await openDB(config, cwd2);
|
|
1224
|
-
const dbPath = config.database.type === "sqlite" ?
|
|
1225
|
-
const staticPath =
|
|
1387
|
+
const dbPath = config.database.type === "sqlite" ? resolve7(cwd2, config.database.path) : null;
|
|
1388
|
+
const staticPath = join9(__dirname2, "dashboard-dist");
|
|
1226
1389
|
const { url } = startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1227
1390
|
console.log(pc2.green(`\u2713`) + ` Dashboard running at ${pc2.bold(pc2.cyan(url))}`);
|
|
1228
1391
|
console.log(pc2.dim(` WebSocket live updates enabled`));
|
|
@@ -1239,7 +1402,7 @@ async function runDashboard(cwd2, opts) {
|
|
|
1239
1402
|
}
|
|
1240
1403
|
|
|
1241
1404
|
// src/commands/export.ts
|
|
1242
|
-
import { writeFileSync as
|
|
1405
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
1243
1406
|
import pc3 from "picocolors";
|
|
1244
1407
|
async function runExport(cwd2, opts) {
|
|
1245
1408
|
if (!opts.sql && !opts.json) {
|
|
@@ -1253,7 +1416,7 @@ async function runExport(cwd2, opts) {
|
|
|
1253
1416
|
const data = await db.exportJson();
|
|
1254
1417
|
const out = JSON.stringify(data, null, 2) + "\n";
|
|
1255
1418
|
if (opts.output) {
|
|
1256
|
-
|
|
1419
|
+
writeFileSync8(opts.output, out, "utf8");
|
|
1257
1420
|
console.log(pc3.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
|
|
1258
1421
|
} else {
|
|
1259
1422
|
process.stdout.write(out);
|
|
@@ -1270,8 +1433,8 @@ async function runExport(cwd2, opts) {
|
|
|
1270
1433
|
|
|
1271
1434
|
// src/commands/health.ts
|
|
1272
1435
|
import { spawnSync } from "child_process";
|
|
1273
|
-
import { existsSync as
|
|
1274
|
-
import { join as
|
|
1436
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1437
|
+
import { join as join10, resolve as resolve8 } from "path";
|
|
1275
1438
|
import pc4 from "picocolors";
|
|
1276
1439
|
function checkLine(label, ok2, message, indent = 0) {
|
|
1277
1440
|
const prefix = label ? pc4.cyan(`[${label}] `) : " ".repeat(indent);
|
|
@@ -1289,8 +1452,8 @@ async function runHealth(cwd2) {
|
|
|
1289
1452
|
let allOk = true;
|
|
1290
1453
|
let dbOk;
|
|
1291
1454
|
if (config.database.type === "sqlite") {
|
|
1292
|
-
const dbPath =
|
|
1293
|
-
dbOk =
|
|
1455
|
+
const dbPath = resolve8(cwd2, config.database.path);
|
|
1456
|
+
dbOk = existsSync7(dbPath);
|
|
1294
1457
|
checkLine("checking DB", dbOk, `${config.database.path} reachable`);
|
|
1295
1458
|
} else {
|
|
1296
1459
|
dbOk = true;
|
|
@@ -1302,15 +1465,15 @@ async function runHealth(cwd2) {
|
|
|
1302
1465
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1303
1466
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1304
1467
|
const name = agentNames[i];
|
|
1305
|
-
const agentPath =
|
|
1306
|
-
const ok2 =
|
|
1468
|
+
const agentPath = join10(cwd2, agentsDir, `${name}.md`);
|
|
1469
|
+
const ok2 = existsSync7(agentPath);
|
|
1307
1470
|
checkLine(i === 0 ? "checking agents" : null, ok2, `${name}.md present`, agentsLabelWidth);
|
|
1308
1471
|
if (!ok2) allOk = false;
|
|
1309
1472
|
}
|
|
1310
1473
|
if (config.tools.mcp.enabled) {
|
|
1311
1474
|
const mcpFile = config.provider === "claude-code" ? ".claude/mcp.json" : "opencode.json";
|
|
1312
|
-
const mcpPath =
|
|
1313
|
-
const mcpOk =
|
|
1475
|
+
const mcpPath = resolve8(cwd2, mcpFile);
|
|
1476
|
+
const mcpOk = existsSync7(mcpPath);
|
|
1314
1477
|
checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
|
|
1315
1478
|
if (!mcpOk) allOk = false;
|
|
1316
1479
|
}
|
|
@@ -1319,8 +1482,8 @@ async function runHealth(cwd2) {
|
|
|
1319
1482
|
console.error(pc4.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
|
|
1320
1483
|
process.exit(1);
|
|
1321
1484
|
}
|
|
1322
|
-
const scriptPath =
|
|
1323
|
-
if (!
|
|
1485
|
+
const scriptPath = resolve8(cwd2, config.health.scriptPath);
|
|
1486
|
+
if (!existsSync7(scriptPath)) {
|
|
1324
1487
|
console.error(pc4.red(`\u2717 health.sh not found: ${scriptPath}`));
|
|
1325
1488
|
console.error(" Run ahk init first.");
|
|
1326
1489
|
process.exit(1);
|
|
@@ -1344,9 +1507,9 @@ async function runHealth(cwd2) {
|
|
|
1344
1507
|
}
|
|
1345
1508
|
|
|
1346
1509
|
// src/commands/init.ts
|
|
1347
|
-
import { mkdirSync as
|
|
1510
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
1348
1511
|
import { homedir } from "os";
|
|
1349
|
-
import { join as
|
|
1512
|
+
import { join as join12 } from "path";
|
|
1350
1513
|
import * as p3 from "@clack/prompts";
|
|
1351
1514
|
import pc6 from "picocolors";
|
|
1352
1515
|
|
|
@@ -1401,13 +1564,13 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1401
1564
|
};
|
|
1402
1565
|
|
|
1403
1566
|
// src/commands/init-helpers.ts
|
|
1404
|
-
import { existsSync as
|
|
1405
|
-
import { join as
|
|
1567
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
1568
|
+
import { join as join11 } from "path";
|
|
1406
1569
|
import pc5 from "picocolors";
|
|
1407
1570
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1408
1571
|
try {
|
|
1409
|
-
const pkgPath2 =
|
|
1410
|
-
if (!
|
|
1572
|
+
const pkgPath2 = join11(cwd2, "package.json");
|
|
1573
|
+
if (!existsSync8(pkgPath2)) return null;
|
|
1411
1574
|
const content = readFileSync5(pkgPath2, "utf8");
|
|
1412
1575
|
const pkg2 = JSON.parse(content);
|
|
1413
1576
|
const name = pkg2?.name;
|
|
@@ -1498,36 +1661,30 @@ async function runInit(cwd2, flags) {
|
|
|
1498
1661
|
if (flags.name) {
|
|
1499
1662
|
name = flags.name;
|
|
1500
1663
|
} else {
|
|
1501
|
-
name = await cliFormWithRetry(
|
|
1502
|
-
async () => {
|
|
1503
|
-
const val = await p3.text({
|
|
1504
|
-
message: "Project name",
|
|
1505
|
-
placeholder: "my-app",
|
|
1506
|
-
...detectedName && { initialValue: detectedName }
|
|
1507
|
-
});
|
|
1508
|
-
if (p3.isCancel(val)) {
|
|
1509
|
-
p3.cancel("Cancelled.");
|
|
1510
|
-
process.exit(0);
|
|
1511
|
-
}
|
|
1512
|
-
return val;
|
|
1513
|
-
},
|
|
1514
|
-
initNameSchema
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
1517
|
-
const description = await cliFormWithRetry(
|
|
1518
|
-
async () => {
|
|
1664
|
+
name = await cliFormWithRetry(async () => {
|
|
1519
1665
|
const val = await p3.text({
|
|
1520
|
-
message: "
|
|
1521
|
-
placeholder: "
|
|
1666
|
+
message: "Project name",
|
|
1667
|
+
placeholder: "my-app",
|
|
1668
|
+
...detectedName && { initialValue: detectedName }
|
|
1522
1669
|
});
|
|
1523
1670
|
if (p3.isCancel(val)) {
|
|
1524
1671
|
p3.cancel("Cancelled.");
|
|
1525
1672
|
process.exit(0);
|
|
1526
1673
|
}
|
|
1527
1674
|
return val;
|
|
1528
|
-
},
|
|
1529
|
-
|
|
1530
|
-
)
|
|
1675
|
+
}, initNameSchema);
|
|
1676
|
+
}
|
|
1677
|
+
const description = await cliFormWithRetry(async () => {
|
|
1678
|
+
const val = await p3.text({
|
|
1679
|
+
message: "Short description (shown to agents as context)",
|
|
1680
|
+
placeholder: "A REST API for managing notes"
|
|
1681
|
+
});
|
|
1682
|
+
if (p3.isCancel(val)) {
|
|
1683
|
+
p3.cancel("Cancelled.");
|
|
1684
|
+
process.exit(0);
|
|
1685
|
+
}
|
|
1686
|
+
return val;
|
|
1687
|
+
}, initDescriptionSchema);
|
|
1531
1688
|
let provider;
|
|
1532
1689
|
if (flags.provider && ["claude-code", "opencode"].includes(flags.provider)) {
|
|
1533
1690
|
provider = flags.provider;
|
|
@@ -1535,8 +1692,9 @@ async function runInit(cwd2, flags) {
|
|
|
1535
1692
|
const val = await p3.select({
|
|
1536
1693
|
message: "AI provider",
|
|
1537
1694
|
options: [
|
|
1695
|
+
{ value: "opencode", label: "OpenCode" },
|
|
1538
1696
|
{ value: "claude-code", label: "Claude Code" },
|
|
1539
|
-
{ value: "
|
|
1697
|
+
{ value: "codex-cli", label: "Codex CLI" }
|
|
1540
1698
|
]
|
|
1541
1699
|
});
|
|
1542
1700
|
if (p3.isCancel(val)) {
|
|
@@ -1561,20 +1719,17 @@ async function runInit(cwd2, flags) {
|
|
|
1561
1719
|
if (flags.docs) {
|
|
1562
1720
|
docsPath = flags.docs;
|
|
1563
1721
|
} else {
|
|
1564
|
-
docsPath = await cliFormWithRetry(
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
},
|
|
1576
|
-
initDocsSchema
|
|
1577
|
-
);
|
|
1722
|
+
docsPath = await cliFormWithRetry(async () => {
|
|
1723
|
+
const val = await p3.text({
|
|
1724
|
+
message: "Docs folder path (agents will search here)",
|
|
1725
|
+
initialValue: "./docs"
|
|
1726
|
+
});
|
|
1727
|
+
if (p3.isCancel(val)) {
|
|
1728
|
+
p3.cancel("Cancelled.");
|
|
1729
|
+
process.exit(0);
|
|
1730
|
+
}
|
|
1731
|
+
return val;
|
|
1732
|
+
}, initDocsSchema);
|
|
1578
1733
|
}
|
|
1579
1734
|
let tasksAdapter;
|
|
1580
1735
|
if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
|
|
@@ -1594,35 +1749,29 @@ async function runInit(cwd2, flags) {
|
|
|
1594
1749
|
}
|
|
1595
1750
|
tasksAdapter = val;
|
|
1596
1751
|
}
|
|
1597
|
-
const addFirstTask = await p3.confirm({ message: "Add your first task now?", initialValue:
|
|
1752
|
+
const addFirstTask = await p3.confirm({ message: "Add your first task now?", initialValue: false });
|
|
1598
1753
|
if (p3.isCancel(addFirstTask)) {
|
|
1599
1754
|
p3.cancel("Cancelled");
|
|
1600
1755
|
process.exit(0);
|
|
1601
1756
|
}
|
|
1602
1757
|
let firstTask;
|
|
1603
1758
|
if (addFirstTask) {
|
|
1604
|
-
const taskTitle = await cliFormWithRetry(
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
process.exit(0);
|
|
1621
|
-
}
|
|
1622
|
-
return val.trim();
|
|
1623
|
-
},
|
|
1624
|
-
taskDescriptionSchema
|
|
1625
|
-
);
|
|
1759
|
+
const taskTitle = await cliFormWithRetry(async () => {
|
|
1760
|
+
const val = await p3.text({ message: "Task title" });
|
|
1761
|
+
if (p3.isCancel(val)) {
|
|
1762
|
+
p3.cancel("Cancelled");
|
|
1763
|
+
process.exit(0);
|
|
1764
|
+
}
|
|
1765
|
+
return val.trim();
|
|
1766
|
+
}, taskTitleSchema);
|
|
1767
|
+
const taskDesc = await cliFormWithRetry(async () => {
|
|
1768
|
+
const val = await p3.text({ message: "Task description", placeholder: "What and why" });
|
|
1769
|
+
if (p3.isCancel(val)) {
|
|
1770
|
+
p3.cancel("Cancelled");
|
|
1771
|
+
process.exit(0);
|
|
1772
|
+
}
|
|
1773
|
+
return val.trim();
|
|
1774
|
+
}, taskDescriptionSchema);
|
|
1626
1775
|
const acceptance = [];
|
|
1627
1776
|
p3.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
1628
1777
|
while (true) {
|
|
@@ -1643,9 +1792,9 @@ async function runInit(cwd2, flags) {
|
|
|
1643
1792
|
let installDir = cwd2;
|
|
1644
1793
|
if (globalInstallation) {
|
|
1645
1794
|
if (provider === "claude-code") {
|
|
1646
|
-
installDir =
|
|
1795
|
+
installDir = join12(homedir(), ".claude");
|
|
1647
1796
|
} else {
|
|
1648
|
-
installDir =
|
|
1797
|
+
installDir = join12(homedir(), ".config", "opencode");
|
|
1649
1798
|
}
|
|
1650
1799
|
}
|
|
1651
1800
|
const configContent = configTs({
|
|
@@ -1656,8 +1805,8 @@ async function runInit(cwd2, flags) {
|
|
|
1656
1805
|
tasksAdapter,
|
|
1657
1806
|
port: config.tools.mcp.port
|
|
1658
1807
|
});
|
|
1659
|
-
|
|
1660
|
-
|
|
1808
|
+
writeFileSync9(join12(installDir, "agent-harness-kit.config.ts"), configContent, "utf8");
|
|
1809
|
+
mkdirSync8(join12(installDir, config.storage.dir), { recursive: true });
|
|
1661
1810
|
const db = await openDB(config, installDir);
|
|
1662
1811
|
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
1663
1812
|
if (firstTask) {
|
|
@@ -1695,7 +1844,15 @@ async function runInit(cwd2, flags) {
|
|
|
1695
1844
|
console.log("");
|
|
1696
1845
|
console.log(pc6.cyan("\u2192") + ` Edit ${pc6.cyan("health.sh")} with your project checks`);
|
|
1697
1846
|
console.log(pc6.cyan("\u2192") + ` ${pc6.cyan("ahk task add")} to queue work for agents`);
|
|
1698
|
-
console.log(
|
|
1847
|
+
console.log(
|
|
1848
|
+
pc6.cyan("\u2192") + ` Enrich your docs with knowledge graphs: ${pc6.cyan("https://github.com/safishamsi/graphify")}`
|
|
1849
|
+
);
|
|
1850
|
+
const recommendations = [
|
|
1851
|
+
` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
|
|
1852
|
+
` Learn more: ${pc6.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
|
|
1853
|
+
];
|
|
1854
|
+
console.log("");
|
|
1855
|
+
drawBox(recommendations);
|
|
1699
1856
|
}
|
|
1700
1857
|
|
|
1701
1858
|
// src/commands/migrate.ts
|
|
@@ -1740,15 +1897,15 @@ async function runMigrate(cwd2, opts) {
|
|
|
1740
1897
|
}
|
|
1741
1898
|
|
|
1742
1899
|
// src/commands/reset.ts
|
|
1743
|
-
import { existsSync as
|
|
1744
|
-
import { join as
|
|
1900
|
+
import { existsSync as existsSync9, readdirSync, rmSync } from "fs";
|
|
1901
|
+
import { join as join13, resolve as resolve9 } from "path";
|
|
1745
1902
|
import * as p5 from "@clack/prompts";
|
|
1746
1903
|
import pc8 from "picocolors";
|
|
1747
1904
|
var AGENT_MD_FILES = ["lead", "explorer", "builder", "reviewer"];
|
|
1748
1905
|
async function resetAgentMds(cwd2, provider) {
|
|
1749
1906
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
1750
|
-
const agentDirPath =
|
|
1751
|
-
if (!
|
|
1907
|
+
const agentDirPath = resolve9(cwd2, agentDir);
|
|
1908
|
+
if (!existsSync9(agentDirPath)) {
|
|
1752
1909
|
console.log(pc8.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
1753
1910
|
return;
|
|
1754
1911
|
}
|
|
@@ -1779,7 +1936,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
1779
1936
|
}
|
|
1780
1937
|
if (confirm3) {
|
|
1781
1938
|
try {
|
|
1782
|
-
const filePath =
|
|
1939
|
+
const filePath = join13(agentDirPath, file);
|
|
1783
1940
|
rmSync(filePath, { force: true });
|
|
1784
1941
|
console.log(pc8.green(` Removed ${file}`));
|
|
1785
1942
|
} catch {
|
|
@@ -1799,12 +1956,12 @@ async function runReset(cwd2, opts) {
|
|
|
1799
1956
|
process.exit(1);
|
|
1800
1957
|
}
|
|
1801
1958
|
const storageDir = config.storage.dir || ".harness";
|
|
1802
|
-
const dbPath = config.database.type === "sqlite" ?
|
|
1803
|
-
const featureListPath =
|
|
1959
|
+
const dbPath = config.database.type === "sqlite" ? resolve9(cwd2, config.database.path) : null;
|
|
1960
|
+
const featureListPath = resolve9(cwd2, storageDir, "feature_list.json");
|
|
1804
1961
|
let resetDb = false;
|
|
1805
1962
|
let resetFeatureList = false;
|
|
1806
1963
|
let resetAgentMdsFlag = false;
|
|
1807
|
-
if (dbPath &&
|
|
1964
|
+
if (dbPath && existsSync9(dbPath)) {
|
|
1808
1965
|
if (opts.force) {
|
|
1809
1966
|
resetDb = true;
|
|
1810
1967
|
} else {
|
|
@@ -1826,7 +1983,7 @@ async function runReset(cwd2, opts) {
|
|
|
1826
1983
|
} else if (!dbPath) {
|
|
1827
1984
|
console.log(pc8.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
1828
1985
|
}
|
|
1829
|
-
if (
|
|
1986
|
+
if (existsSync9(featureListPath)) {
|
|
1830
1987
|
if (opts.force) {
|
|
1831
1988
|
resetFeatureList = true;
|
|
1832
1989
|
} else {
|
|
@@ -1876,7 +2033,7 @@ async function runReset(cwd2, opts) {
|
|
|
1876
2033
|
|
|
1877
2034
|
// src/core/mcp-server.ts
|
|
1878
2035
|
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "fs";
|
|
1879
|
-
import { join as
|
|
2036
|
+
import { join as join14, resolve as resolve10 } from "path";
|
|
1880
2037
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
1881
2038
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1882
2039
|
import {
|
|
@@ -2055,7 +2212,7 @@ var TOOLS = [
|
|
|
2055
2212
|
];
|
|
2056
2213
|
async function startMcpServer(config, cwd2) {
|
|
2057
2214
|
const db = await openDB(config, cwd2);
|
|
2058
|
-
const docsPath =
|
|
2215
|
+
const docsPath = resolve10(cwd2, config.project.docsPath);
|
|
2059
2216
|
const server = new Server(
|
|
2060
2217
|
{ name: "agent-harness-kit", version: VERSION },
|
|
2061
2218
|
{ capabilities: { tools: {} } }
|
|
@@ -2196,7 +2353,7 @@ function collectMarkdownFiles(dir) {
|
|
|
2196
2353
|
const files = [];
|
|
2197
2354
|
try {
|
|
2198
2355
|
for (const entry of readdirSync2(dir)) {
|
|
2199
|
-
const full =
|
|
2356
|
+
const full = join14(dir, entry);
|
|
2200
2357
|
const stat = statSync(full);
|
|
2201
2358
|
if (stat.isDirectory()) {
|
|
2202
2359
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -2303,13 +2460,13 @@ async function runStatus(cwd2, opts) {
|
|
|
2303
2460
|
}
|
|
2304
2461
|
|
|
2305
2462
|
// src/commands/sync.ts
|
|
2306
|
-
import { existsSync as
|
|
2307
|
-
import { join as
|
|
2463
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
2464
|
+
import { join as join15, resolve as resolve11 } from "path";
|
|
2308
2465
|
import pc10 from "picocolors";
|
|
2309
2466
|
async function runSync(cwd2, opts) {
|
|
2310
2467
|
const config = await loadConfig(cwd2);
|
|
2311
2468
|
const direction = opts.direction ?? "both";
|
|
2312
|
-
const featureListPath =
|
|
2469
|
+
const featureListPath = resolve11(join15(cwd2, config.storage.dir, "feature_list.json"));
|
|
2313
2470
|
const db = await openDB(config, cwd2);
|
|
2314
2471
|
try {
|
|
2315
2472
|
if (direction === "in" || direction === "both") {
|
|
@@ -2323,7 +2480,7 @@ async function runSync(cwd2, opts) {
|
|
|
2323
2480
|
}
|
|
2324
2481
|
}
|
|
2325
2482
|
async function syncIn(featureListPath, db, dryRun) {
|
|
2326
|
-
if (!
|
|
2483
|
+
if (!existsSync10(featureListPath)) {
|
|
2327
2484
|
console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
2328
2485
|
return;
|
|
2329
2486
|
}
|
|
@@ -2414,14 +2571,14 @@ async function runTaskAdd(cwd2) {
|
|
|
2414
2571
|
|
|
2415
2572
|
// src/commands/task/done.ts
|
|
2416
2573
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2417
|
-
import { existsSync as
|
|
2418
|
-
import { resolve as
|
|
2574
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2575
|
+
import { resolve as resolve12 } from "path";
|
|
2419
2576
|
import pc12 from "picocolors";
|
|
2420
2577
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
2421
2578
|
const config = await loadConfig(cwd2);
|
|
2422
2579
|
if (config.health.required) {
|
|
2423
|
-
const scriptPath =
|
|
2424
|
-
if (
|
|
2580
|
+
const scriptPath = resolve12(cwd2, config.health.scriptPath);
|
|
2581
|
+
if (existsSync11(scriptPath)) {
|
|
2425
2582
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
2426
2583
|
if (result.status !== 0) {
|
|
2427
2584
|
console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
@@ -2492,10 +2649,10 @@ async function runTaskList(cwd2, opts) {
|
|
|
2492
2649
|
|
|
2493
2650
|
// src/core/package-data.ts
|
|
2494
2651
|
import { createRequire } from "module";
|
|
2495
|
-
import { dirname as dirname5, join as
|
|
2652
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
2496
2653
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2497
2654
|
var require2 = createRequire(import.meta.url);
|
|
2498
|
-
var pkgPath =
|
|
2655
|
+
var pkgPath = join16(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
2499
2656
|
var pkg = require2(pkgPath);
|
|
2500
2657
|
|
|
2501
2658
|
// src/core/update-check.ts
|
|
@@ -2503,33 +2660,24 @@ import pc14 from "picocolors";
|
|
|
2503
2660
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
2504
2661
|
var TIMEOUT_MS = 2500;
|
|
2505
2662
|
function checkForUpdate(currentVersion) {
|
|
2506
|
-
return new Promise((
|
|
2507
|
-
const timer = setTimeout(() =>
|
|
2663
|
+
return new Promise((resolve13) => {
|
|
2664
|
+
const timer = setTimeout(() => resolve13(null), TIMEOUT_MS);
|
|
2508
2665
|
fetch(REGISTRY_URL).then((res) => res.json()).then((data) => {
|
|
2509
2666
|
clearTimeout(timer);
|
|
2510
2667
|
const latest = data.version;
|
|
2511
|
-
|
|
2668
|
+
resolve13(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
2512
2669
|
}).catch(() => {
|
|
2513
2670
|
clearTimeout(timer);
|
|
2514
|
-
|
|
2671
|
+
resolve13(null);
|
|
2515
2672
|
});
|
|
2516
2673
|
});
|
|
2517
2674
|
}
|
|
2518
2675
|
function printUpdateMessage({ current, latest }) {
|
|
2519
2676
|
const lines = [
|
|
2520
2677
|
` Update available ${pc14.dim(current)} \u2192 ${pc14.green(latest)} `,
|
|
2521
|
-
` Run: ${pc14.cyan(`
|
|
2678
|
+
` Run: ${pc14.cyan(`pnpm i ${pkg.name}@${latest}`)} `
|
|
2522
2679
|
];
|
|
2523
|
-
|
|
2524
|
-
const border = "\u2500".repeat(width);
|
|
2525
|
-
console.log();
|
|
2526
|
-
console.log(pc14.yellow(`\u250C${border}\u2510`));
|
|
2527
|
-
for (const line of lines) {
|
|
2528
|
-
const pad = width - stripAnsi2(line).length;
|
|
2529
|
-
console.log(pc14.yellow("\u2502") + line + " ".repeat(pad) + pc14.yellow("\u2502"));
|
|
2530
|
-
}
|
|
2531
|
-
console.log(pc14.yellow(`\u2514${border}\u2518`));
|
|
2532
|
-
console.log();
|
|
2680
|
+
drawBox(lines);
|
|
2533
2681
|
}
|
|
2534
2682
|
function isNewer(latest, current) {
|
|
2535
2683
|
const toNum = (v4) => v4.split(".").map(Number);
|
|
@@ -2539,9 +2687,6 @@ function isNewer(latest, current) {
|
|
|
2539
2687
|
if (lMin !== cMin) return lMin > cMin;
|
|
2540
2688
|
return lPat > cPat;
|
|
2541
2689
|
}
|
|
2542
|
-
function stripAnsi2(str2) {
|
|
2543
|
-
return str2.replace(/\x1B\[[0-9;]*m/g, "");
|
|
2544
|
-
}
|
|
2545
2690
|
|
|
2546
2691
|
// src/cli.ts
|
|
2547
2692
|
var cwd = process.cwd();
|