@cardor/agent-harness-kit 1.1.7 → 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 +357 -206
- 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,9 @@ 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
|
+
);
|
|
1699
1850
|
const recommendations = [
|
|
1700
1851
|
` Give a try to Heimdall MCP: Transparent proxy that traces every MCP tool call with OpenTelemetry. `,
|
|
1701
1852
|
` Learn more: ${pc6.cyan("https://github.com/enmanuelmag/heimdall-mcp")} `
|
|
@@ -1746,15 +1897,15 @@ async function runMigrate(cwd2, opts) {
|
|
|
1746
1897
|
}
|
|
1747
1898
|
|
|
1748
1899
|
// src/commands/reset.ts
|
|
1749
|
-
import { existsSync as
|
|
1750
|
-
import { join as
|
|
1900
|
+
import { existsSync as existsSync9, readdirSync, rmSync } from "fs";
|
|
1901
|
+
import { join as join13, resolve as resolve9 } from "path";
|
|
1751
1902
|
import * as p5 from "@clack/prompts";
|
|
1752
1903
|
import pc8 from "picocolors";
|
|
1753
1904
|
var AGENT_MD_FILES = ["lead", "explorer", "builder", "reviewer"];
|
|
1754
1905
|
async function resetAgentMds(cwd2, provider) {
|
|
1755
1906
|
const agentDir = provider === "claude-code" ? ".claude/agents" : ".opencode/agents";
|
|
1756
|
-
const agentDirPath =
|
|
1757
|
-
if (!
|
|
1907
|
+
const agentDirPath = resolve9(cwd2, agentDir);
|
|
1908
|
+
if (!existsSync9(agentDirPath)) {
|
|
1758
1909
|
console.log(pc8.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
1759
1910
|
return;
|
|
1760
1911
|
}
|
|
@@ -1785,7 +1936,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
1785
1936
|
}
|
|
1786
1937
|
if (confirm3) {
|
|
1787
1938
|
try {
|
|
1788
|
-
const filePath =
|
|
1939
|
+
const filePath = join13(agentDirPath, file);
|
|
1789
1940
|
rmSync(filePath, { force: true });
|
|
1790
1941
|
console.log(pc8.green(` Removed ${file}`));
|
|
1791
1942
|
} catch {
|
|
@@ -1805,12 +1956,12 @@ async function runReset(cwd2, opts) {
|
|
|
1805
1956
|
process.exit(1);
|
|
1806
1957
|
}
|
|
1807
1958
|
const storageDir = config.storage.dir || ".harness";
|
|
1808
|
-
const dbPath = config.database.type === "sqlite" ?
|
|
1809
|
-
const featureListPath =
|
|
1959
|
+
const dbPath = config.database.type === "sqlite" ? resolve9(cwd2, config.database.path) : null;
|
|
1960
|
+
const featureListPath = resolve9(cwd2, storageDir, "feature_list.json");
|
|
1810
1961
|
let resetDb = false;
|
|
1811
1962
|
let resetFeatureList = false;
|
|
1812
1963
|
let resetAgentMdsFlag = false;
|
|
1813
|
-
if (dbPath &&
|
|
1964
|
+
if (dbPath && existsSync9(dbPath)) {
|
|
1814
1965
|
if (opts.force) {
|
|
1815
1966
|
resetDb = true;
|
|
1816
1967
|
} else {
|
|
@@ -1832,7 +1983,7 @@ async function runReset(cwd2, opts) {
|
|
|
1832
1983
|
} else if (!dbPath) {
|
|
1833
1984
|
console.log(pc8.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
1834
1985
|
}
|
|
1835
|
-
if (
|
|
1986
|
+
if (existsSync9(featureListPath)) {
|
|
1836
1987
|
if (opts.force) {
|
|
1837
1988
|
resetFeatureList = true;
|
|
1838
1989
|
} else {
|
|
@@ -1882,7 +2033,7 @@ async function runReset(cwd2, opts) {
|
|
|
1882
2033
|
|
|
1883
2034
|
// src/core/mcp-server.ts
|
|
1884
2035
|
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "fs";
|
|
1885
|
-
import { join as
|
|
2036
|
+
import { join as join14, resolve as resolve10 } from "path";
|
|
1886
2037
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
1887
2038
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1888
2039
|
import {
|
|
@@ -2061,7 +2212,7 @@ var TOOLS = [
|
|
|
2061
2212
|
];
|
|
2062
2213
|
async function startMcpServer(config, cwd2) {
|
|
2063
2214
|
const db = await openDB(config, cwd2);
|
|
2064
|
-
const docsPath =
|
|
2215
|
+
const docsPath = resolve10(cwd2, config.project.docsPath);
|
|
2065
2216
|
const server = new Server(
|
|
2066
2217
|
{ name: "agent-harness-kit", version: VERSION },
|
|
2067
2218
|
{ capabilities: { tools: {} } }
|
|
@@ -2202,7 +2353,7 @@ function collectMarkdownFiles(dir) {
|
|
|
2202
2353
|
const files = [];
|
|
2203
2354
|
try {
|
|
2204
2355
|
for (const entry of readdirSync2(dir)) {
|
|
2205
|
-
const full =
|
|
2356
|
+
const full = join14(dir, entry);
|
|
2206
2357
|
const stat = statSync(full);
|
|
2207
2358
|
if (stat.isDirectory()) {
|
|
2208
2359
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -2309,13 +2460,13 @@ async function runStatus(cwd2, opts) {
|
|
|
2309
2460
|
}
|
|
2310
2461
|
|
|
2311
2462
|
// src/commands/sync.ts
|
|
2312
|
-
import { existsSync as
|
|
2313
|
-
import { join as
|
|
2463
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
2464
|
+
import { join as join15, resolve as resolve11 } from "path";
|
|
2314
2465
|
import pc10 from "picocolors";
|
|
2315
2466
|
async function runSync(cwd2, opts) {
|
|
2316
2467
|
const config = await loadConfig(cwd2);
|
|
2317
2468
|
const direction = opts.direction ?? "both";
|
|
2318
|
-
const featureListPath =
|
|
2469
|
+
const featureListPath = resolve11(join15(cwd2, config.storage.dir, "feature_list.json"));
|
|
2319
2470
|
const db = await openDB(config, cwd2);
|
|
2320
2471
|
try {
|
|
2321
2472
|
if (direction === "in" || direction === "both") {
|
|
@@ -2329,7 +2480,7 @@ async function runSync(cwd2, opts) {
|
|
|
2329
2480
|
}
|
|
2330
2481
|
}
|
|
2331
2482
|
async function syncIn(featureListPath, db, dryRun) {
|
|
2332
|
-
if (!
|
|
2483
|
+
if (!existsSync10(featureListPath)) {
|
|
2333
2484
|
console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
2334
2485
|
return;
|
|
2335
2486
|
}
|
|
@@ -2420,14 +2571,14 @@ async function runTaskAdd(cwd2) {
|
|
|
2420
2571
|
|
|
2421
2572
|
// src/commands/task/done.ts
|
|
2422
2573
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2423
|
-
import { existsSync as
|
|
2424
|
-
import { resolve as
|
|
2574
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2575
|
+
import { resolve as resolve12 } from "path";
|
|
2425
2576
|
import pc12 from "picocolors";
|
|
2426
2577
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
2427
2578
|
const config = await loadConfig(cwd2);
|
|
2428
2579
|
if (config.health.required) {
|
|
2429
|
-
const scriptPath =
|
|
2430
|
-
if (
|
|
2580
|
+
const scriptPath = resolve12(cwd2, config.health.scriptPath);
|
|
2581
|
+
if (existsSync11(scriptPath)) {
|
|
2431
2582
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
2432
2583
|
if (result.status !== 0) {
|
|
2433
2584
|
console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
@@ -2498,10 +2649,10 @@ async function runTaskList(cwd2, opts) {
|
|
|
2498
2649
|
|
|
2499
2650
|
// src/core/package-data.ts
|
|
2500
2651
|
import { createRequire } from "module";
|
|
2501
|
-
import { dirname as dirname5, join as
|
|
2652
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
2502
2653
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2503
2654
|
var require2 = createRequire(import.meta.url);
|
|
2504
|
-
var pkgPath =
|
|
2655
|
+
var pkgPath = join16(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
2505
2656
|
var pkg = require2(pkgPath);
|
|
2506
2657
|
|
|
2507
2658
|
// src/core/update-check.ts
|
|
@@ -2509,15 +2660,15 @@ import pc14 from "picocolors";
|
|
|
2509
2660
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
2510
2661
|
var TIMEOUT_MS = 2500;
|
|
2511
2662
|
function checkForUpdate(currentVersion) {
|
|
2512
|
-
return new Promise((
|
|
2513
|
-
const timer = setTimeout(() =>
|
|
2663
|
+
return new Promise((resolve13) => {
|
|
2664
|
+
const timer = setTimeout(() => resolve13(null), TIMEOUT_MS);
|
|
2514
2665
|
fetch(REGISTRY_URL).then((res) => res.json()).then((data) => {
|
|
2515
2666
|
clearTimeout(timer);
|
|
2516
2667
|
const latest = data.version;
|
|
2517
|
-
|
|
2668
|
+
resolve13(isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
2518
2669
|
}).catch(() => {
|
|
2519
2670
|
clearTimeout(timer);
|
|
2520
|
-
|
|
2671
|
+
resolve13(null);
|
|
2521
2672
|
});
|
|
2522
2673
|
});
|
|
2523
2674
|
}
|