@cardor/agent-harness-kit 2.1.0 → 2.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 +31 -8
- package/dist/cli.js +410 -261
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
import { Command, InvalidArgumentError } from "commander";
|
|
19
|
-
import
|
|
19
|
+
import pc21 from "picocolors";
|
|
20
20
|
|
|
21
21
|
// src/commands/build.ts
|
|
22
22
|
import { watch } from "fs";
|
|
23
|
-
import * as
|
|
23
|
+
import * as p2 from "@clack/prompts";
|
|
24
24
|
import pc2 from "picocolors";
|
|
25
25
|
|
|
26
26
|
// src/core/materializer/claude-code.ts
|
|
@@ -53,31 +53,39 @@ import { fileURLToPath } from "url";
|
|
|
53
53
|
var require2 = createRequire(import.meta.url);
|
|
54
54
|
var here = dirname(fileURLToPath(import.meta.url));
|
|
55
55
|
var candidates = [join2(here, "..", "..", "package.json"), join2(here, "..", "package.json")];
|
|
56
|
-
var pkgPath = candidates.find((
|
|
56
|
+
var pkgPath = candidates.find((p10) => existsSync(p10)) ?? candidates[0];
|
|
57
57
|
var pkg = require2(pkgPath);
|
|
58
58
|
|
|
59
59
|
// src/core/local-install-guard.ts
|
|
60
60
|
function isLocalInstallSatisfied(cwd2) {
|
|
61
61
|
const selfPkgPath = join3(cwd2, "package.json");
|
|
62
|
-
let projectPkg = null;
|
|
63
62
|
if (existsSync2(selfPkgPath)) {
|
|
64
63
|
try {
|
|
65
64
|
const selfPkg = JSON.parse(readFileSync(selfPkgPath, "utf8"));
|
|
66
65
|
if (selfPkg?.name === pkg.name) return true;
|
|
67
|
-
projectPkg = selfPkg;
|
|
68
66
|
} catch {
|
|
69
67
|
}
|
|
70
68
|
}
|
|
69
|
+
return hasRealLocalInstall(cwd2);
|
|
70
|
+
}
|
|
71
|
+
function hasRealLocalInstall(cwd2) {
|
|
71
72
|
const [scope, name] = pkg.name.split("/");
|
|
72
73
|
const localPath = pkg.name.startsWith("@") ? join3(cwd2, "node_modules", scope, name) : join3(cwd2, "node_modules", pkg.name);
|
|
73
74
|
if (existsSync2(localPath)) return true;
|
|
74
75
|
const isPnp = existsSync2(join3(cwd2, ".pnp.cjs")) || existsSync2(join3(cwd2, ".pnp.loader.mjs"));
|
|
75
|
-
if (isPnp
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
if (isPnp) {
|
|
77
|
+
const pkgPath2 = join3(cwd2, "package.json");
|
|
78
|
+
if (existsSync2(pkgPath2)) {
|
|
79
|
+
try {
|
|
80
|
+
const projectPkg = JSON.parse(readFileSync(pkgPath2, "utf8"));
|
|
81
|
+
const deps = {
|
|
82
|
+
...projectPkg?.dependencies ?? {},
|
|
83
|
+
...projectPkg?.devDependencies ?? {}
|
|
84
|
+
};
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
90
|
return false;
|
|
83
91
|
}
|
|
@@ -132,7 +140,7 @@ function detectFromPackageManagerField(cwd2) {
|
|
|
132
140
|
}
|
|
133
141
|
function getMcpCommandParts(pm, port, cwd2) {
|
|
134
142
|
const portStr = String(port);
|
|
135
|
-
if (!
|
|
143
|
+
if (!hasRealLocalInstall(cwd2)) {
|
|
136
144
|
return ["ahk", "serve", "--port", portStr];
|
|
137
145
|
}
|
|
138
146
|
switch (pm) {
|
|
@@ -365,6 +373,17 @@ function mergeTomlSection(content, sectionName, sectionBody) {
|
|
|
365
373
|
];
|
|
366
374
|
return newLines.join("\n");
|
|
367
375
|
}
|
|
376
|
+
function ensureTomlTopLevelKey(content, key, value) {
|
|
377
|
+
const lines = content.length > 0 ? content.split("\n") : [];
|
|
378
|
+
const firstSectionIdx = lines.findIndex((l) => /^\s*\[/.test(l));
|
|
379
|
+
const preambleEnd = firstSectionIdx === -1 ? lines.length : firstSectionIdx;
|
|
380
|
+
const keyRe = new RegExp(`^\\s*${key}\\s*=`);
|
|
381
|
+
const alreadyPresent = lines.slice(0, preambleEnd).some((l) => keyRe.test(l));
|
|
382
|
+
if (alreadyPresent) return content;
|
|
383
|
+
const newLines = [...lines];
|
|
384
|
+
newLines.splice(preambleEnd, 0, `${key} = ${JSON.stringify(value)}`);
|
|
385
|
+
return newLines.join("\n");
|
|
386
|
+
}
|
|
368
387
|
function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
369
388
|
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
370
389
|
let content = "";
|
|
@@ -375,8 +394,10 @@ function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
375
394
|
const sectionBody = [
|
|
376
395
|
`command = ${JSON.stringify(command)}`,
|
|
377
396
|
`args = ${JSON.stringify(args)}`,
|
|
378
|
-
'default_tools_approval_mode = "
|
|
397
|
+
'default_tools_approval_mode = "approve"'
|
|
379
398
|
].join("\n");
|
|
399
|
+
content = ensureTomlTopLevelKey(content, "model", "gpt-5.6-terra");
|
|
400
|
+
content = ensureTomlTopLevelKey(content, "model_reasoning_effort", "medium");
|
|
380
401
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
381
402
|
writeFileSync2(filePath, content, "utf8");
|
|
382
403
|
}
|
|
@@ -420,14 +441,16 @@ function claudeDisallowedTools(agentName) {
|
|
|
420
441
|
function opencodePermissions(agentName) {
|
|
421
442
|
return restrictionFor(agentName) === "no-write" ? { edit: "deny" } : {};
|
|
422
443
|
}
|
|
423
|
-
function codexSandboxMode(
|
|
424
|
-
return
|
|
444
|
+
function codexSandboxMode(_agentName) {
|
|
445
|
+
return "danger-full-access";
|
|
425
446
|
}
|
|
426
|
-
var CODEX_READ_ONLY_NOTICE = `## Tool restrictions (enforced by the sandbox)
|
|
447
|
+
var CODEX_READ_ONLY_NOTICE = `## Tool restrictions (enforced by instruction only \u2014 NOT by the sandbox)
|
|
448
|
+
|
|
449
|
+
This agent runs UNSANDBOXED: \`sandbox_mode = "danger-full-access"\`. There is no OS-level write protection. This is a deliberate project configuration choice, not an oversight.
|
|
427
450
|
|
|
428
|
-
|
|
451
|
+
You MUST NOT create, modify, or delete any file: no \`Write\`, no \`Edit\`, no \`apply_patch\`, and no shell command that writes to disk (\`>\`, \`tee\`, \`sed -i\`, \`mv\`, \`rm\`, ...). This restriction is enforced ONLY by you following this instruction \u2014 nothing will technically block or reject the call.
|
|
429
452
|
|
|
430
|
-
|
|
453
|
+
If you find yourself about to perform a write, STOP. Do not perform it. Report it as a blocker instead. Treat this as a hard rule: breaking it will not fail loudly, it will silently break the harness's audit trail and workflow guarantees.`;
|
|
431
454
|
function codexRestrictionNotice(agentName) {
|
|
432
455
|
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
433
456
|
}
|
|
@@ -775,7 +798,7 @@ function stripFrontmatter(md) {
|
|
|
775
798
|
}
|
|
776
799
|
return { description, body };
|
|
777
800
|
}
|
|
778
|
-
function toCodexToml(tomlName, agentName, description, body) {
|
|
801
|
+
function toCodexToml(tomlName, agentName, description, body, opts) {
|
|
779
802
|
const safe = (s) => s.replace(/"""/g, '""\\u0022');
|
|
780
803
|
const sandboxMode = codexSandboxMode(agentName);
|
|
781
804
|
const notice = codexRestrictionNotice(agentName);
|
|
@@ -784,9 +807,14 @@ function toCodexToml(tomlName, agentName, description, body) {
|
|
|
784
807
|
---
|
|
785
808
|
|
|
786
809
|
${notice}` : body.trimEnd();
|
|
810
|
+
const modelLines = [];
|
|
811
|
+
if (opts?.model) modelLines.push(`model = "${opts.model}"`);
|
|
812
|
+
if (opts?.effort) modelLines.push(`model_reasoning_effort = "${opts.effort}"`);
|
|
813
|
+
const modelBlock = modelLines.length > 0 ? `${modelLines.join("\n")}
|
|
814
|
+
` : "";
|
|
787
815
|
return `name = "${tomlName}"
|
|
788
816
|
sandbox_mode = "${sandboxMode}"
|
|
789
|
-
|
|
817
|
+
${modelBlock}
|
|
790
818
|
description = """
|
|
791
819
|
${safe(description)}
|
|
792
820
|
"""
|
|
@@ -796,29 +824,29 @@ ${safe(instructions)}
|
|
|
796
824
|
"""
|
|
797
825
|
`;
|
|
798
826
|
}
|
|
799
|
-
function agentLeadToml(vars) {
|
|
827
|
+
function agentLeadToml(vars, opts) {
|
|
800
828
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
801
|
-
return toCodexToml("lead", "lead", description, body);
|
|
829
|
+
return toCodexToml("lead", "lead", description, body, opts);
|
|
802
830
|
}
|
|
803
|
-
function agentLeadAsDefaultToml(vars) {
|
|
831
|
+
function agentLeadAsDefaultToml(vars, opts) {
|
|
804
832
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
805
|
-
return toCodexToml("default", "lead", description, body);
|
|
833
|
+
return toCodexToml("default", "lead", description, body, opts);
|
|
806
834
|
}
|
|
807
|
-
function agentExplorerToml(vars) {
|
|
835
|
+
function agentExplorerToml(vars, opts) {
|
|
808
836
|
const { description, body } = stripFrontmatter(loadAgentTemplate("explorer", vars));
|
|
809
|
-
return toCodexToml("explorer", "explorer", description, body);
|
|
837
|
+
return toCodexToml("explorer", "explorer", description, body, opts);
|
|
810
838
|
}
|
|
811
|
-
function agentBuilderToml(vars) {
|
|
839
|
+
function agentBuilderToml(vars, opts) {
|
|
812
840
|
const { description, body } = stripFrontmatter(loadAgentTemplate("builder", vars));
|
|
813
|
-
return toCodexToml("builder", "builder", description, body);
|
|
841
|
+
return toCodexToml("builder", "builder", description, body, opts);
|
|
814
842
|
}
|
|
815
|
-
function agentReviewerToml(vars) {
|
|
843
|
+
function agentReviewerToml(vars, opts) {
|
|
816
844
|
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
817
|
-
return toCodexToml("reviewer", "reviewer", description, body);
|
|
845
|
+
return toCodexToml("reviewer", "reviewer", description, body, opts);
|
|
818
846
|
}
|
|
819
|
-
function agentConsultantToml(vars) {
|
|
847
|
+
function agentConsultantToml(vars, opts) {
|
|
820
848
|
const { description, body } = stripFrontmatter(loadAgentTemplate("consultant", vars));
|
|
821
|
-
return toCodexToml("consultant", "consultant", description, body);
|
|
849
|
+
return toCodexToml("consultant", "consultant", description, body, opts);
|
|
822
850
|
}
|
|
823
851
|
function stripFrontmatterBlockSequence(md, key) {
|
|
824
852
|
const re = new RegExp(`^${key}:\\n(?: - [^\\n]+\\n)+`, "m");
|
|
@@ -1083,7 +1111,7 @@ No tasks in progress.
|
|
|
1083
1111
|
],
|
|
1084
1112
|
{ force: opts.force, backupRoot: join7(cwd2, config.storage.dir, "backups") }
|
|
1085
1113
|
);
|
|
1086
|
-
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config), {
|
|
1114
|
+
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config, opts.claudeAgentModels), {
|
|
1087
1115
|
force: opts.force,
|
|
1088
1116
|
backupRoot: join7(cwd2, config.storage.dir, "backups")
|
|
1089
1117
|
});
|
|
@@ -1120,20 +1148,20 @@ No tasks in progress.
|
|
|
1120
1148
|
// src/core/materializer/codex-cli.ts
|
|
1121
1149
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1122
1150
|
import { join as join8, resolve as resolve3 } from "path";
|
|
1123
|
-
function codexAgentFiles(config) {
|
|
1151
|
+
function codexAgentFiles(config, modelsByRole) {
|
|
1124
1152
|
const projectName = config.project.name;
|
|
1125
1153
|
return [
|
|
1126
|
-
{ relPath: ".codex/agents/lead.toml", content: agentLeadToml({ projectName }) },
|
|
1127
|
-
{ relPath: ".codex/agents/explorer.toml", content: agentExplorerToml({ projectName }) },
|
|
1128
|
-
{ relPath: ".codex/agents/consultant.toml", content: agentConsultantToml({ projectName }) },
|
|
1129
|
-
{ relPath: ".codex/agents/builder.toml", content: agentBuilderToml({ projectName }) },
|
|
1130
|
-
{ relPath: ".codex/agents/reviewer.toml", content: agentReviewerToml({ projectName }) },
|
|
1131
|
-
{ relPath: ".codex/agents/default.toml", content: agentLeadAsDefaultToml({ projectName }) }
|
|
1154
|
+
{ relPath: ".codex/agents/lead.toml", content: agentLeadToml({ projectName }, modelsByRole?.lead) },
|
|
1155
|
+
{ relPath: ".codex/agents/explorer.toml", content: agentExplorerToml({ projectName }, modelsByRole?.explorer) },
|
|
1156
|
+
{ relPath: ".codex/agents/consultant.toml", content: agentConsultantToml({ projectName }, modelsByRole?.consultant) },
|
|
1157
|
+
{ relPath: ".codex/agents/builder.toml", content: agentBuilderToml({ projectName }, modelsByRole?.builder) },
|
|
1158
|
+
{ relPath: ".codex/agents/reviewer.toml", content: agentReviewerToml({ projectName }, modelsByRole?.reviewer) },
|
|
1159
|
+
{ relPath: ".codex/agents/default.toml", content: agentLeadAsDefaultToml({ projectName }, modelsByRole?.lead) }
|
|
1132
1160
|
];
|
|
1133
1161
|
}
|
|
1134
1162
|
var CodexCliMaterializer = class {
|
|
1135
1163
|
async scaffold(config, opts) {
|
|
1136
|
-
const { cwd: cwd2 } = opts;
|
|
1164
|
+
const { cwd: cwd2, codexAgentModels } = opts;
|
|
1137
1165
|
const write2 = (relPath, content, mode) => {
|
|
1138
1166
|
const abs = join8(cwd2, relPath);
|
|
1139
1167
|
mkdirSync4(resolve3(abs, ".."), { recursive: true });
|
|
@@ -1155,7 +1183,7 @@ No tasks in progress.
|
|
|
1155
1183
|
`
|
|
1156
1184
|
);
|
|
1157
1185
|
}
|
|
1158
|
-
writeAgentFiles(cwd2, codexAgentFiles(config));
|
|
1186
|
+
writeAgentFiles(cwd2, codexAgentFiles(config, codexAgentModels));
|
|
1159
1187
|
mergeCodexConfigToml(join8(cwd2, ".codex/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1160
1188
|
appendGitignore(cwd2);
|
|
1161
1189
|
writeSkills(cwd2, ".agents/skills");
|
|
@@ -1166,7 +1194,7 @@ No tasks in progress.
|
|
|
1166
1194
|
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1167
1195
|
{ force: opts.force, backupRoot: join8(cwd2, config.storage.dir, "backups") }
|
|
1168
1196
|
);
|
|
1169
|
-
const agents = writeAgentFiles(cwd2, codexAgentFiles(config), {
|
|
1197
|
+
const agents = writeAgentFiles(cwd2, codexAgentFiles(config, opts.codexAgentModels), {
|
|
1170
1198
|
force: opts.force,
|
|
1171
1199
|
backupRoot: join8(cwd2, config.storage.dir, "backups")
|
|
1172
1200
|
});
|
|
@@ -1326,20 +1354,53 @@ function getMaterializer(provider) {
|
|
|
1326
1354
|
}
|
|
1327
1355
|
}
|
|
1328
1356
|
|
|
1357
|
+
// src/commands/claude-model-prompt.ts
|
|
1358
|
+
import * as p from "@clack/prompts";
|
|
1359
|
+
var AGENT_LABELS = [
|
|
1360
|
+
{ key: "lead", label: "Lead" },
|
|
1361
|
+
{ key: "explorer", label: "Explorer" },
|
|
1362
|
+
{ key: "consultant", label: "Consultant" },
|
|
1363
|
+
{ key: "builder", label: "Builder" },
|
|
1364
|
+
{ key: "reviewer", label: "Reviewer" }
|
|
1365
|
+
];
|
|
1366
|
+
async function promptClaudeAgentModels(provider) {
|
|
1367
|
+
const claudeAgentModels = {};
|
|
1368
|
+
if (provider !== "claude-code") return claudeAgentModels;
|
|
1369
|
+
for (const agent of AGENT_LABELS) {
|
|
1370
|
+
const val = await p.select({
|
|
1371
|
+
message: `Model for ${agent.label}`,
|
|
1372
|
+
options: [
|
|
1373
|
+
{ value: "inherit", label: "inherit (default)" },
|
|
1374
|
+
{ value: "haiku", label: "haiku" },
|
|
1375
|
+
{ value: "sonnet", label: "sonnet" },
|
|
1376
|
+
{ value: "opus", label: "opus" },
|
|
1377
|
+
{ value: "fable", label: "fable" }
|
|
1378
|
+
],
|
|
1379
|
+
initialValue: "inherit"
|
|
1380
|
+
});
|
|
1381
|
+
if (p.isCancel(val)) {
|
|
1382
|
+
p.cancel("Cancelled.");
|
|
1383
|
+
process.exit(0);
|
|
1384
|
+
}
|
|
1385
|
+
claudeAgentModels[agent.key] = val;
|
|
1386
|
+
}
|
|
1387
|
+
return claudeAgentModels;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1329
1390
|
// src/commands/build.ts
|
|
1330
1391
|
async function runBuild(cwd2, opts) {
|
|
1331
1392
|
await buildOnce(cwd2, opts.force);
|
|
1332
1393
|
if (opts.sync) {
|
|
1333
|
-
|
|
1394
|
+
p2.log.step("Syncing agent permissions...");
|
|
1334
1395
|
const config = await loadConfig(cwd2);
|
|
1335
1396
|
const materializer = getMaterializer(config.provider);
|
|
1336
1397
|
await materializer.syncPermissions(cwd2);
|
|
1337
1398
|
}
|
|
1338
1399
|
if (opts.watch) {
|
|
1339
|
-
|
|
1400
|
+
p2.log.info(`Watching agent-harness-kit.config.ts for changes...`);
|
|
1340
1401
|
watch(cwd2, { recursive: false }, async (_, filename) => {
|
|
1341
1402
|
if (filename?.startsWith("agent-harness-kit.config")) {
|
|
1342
|
-
|
|
1403
|
+
p2.log.step("Config changed \u2014 rebuilding...");
|
|
1343
1404
|
await buildOnce(cwd2, false);
|
|
1344
1405
|
}
|
|
1345
1406
|
});
|
|
@@ -1348,36 +1409,45 @@ async function runBuild(cwd2, opts) {
|
|
|
1348
1409
|
}
|
|
1349
1410
|
}
|
|
1350
1411
|
async function buildOnce(cwd2, force) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1412
|
+
let config;
|
|
1413
|
+
try {
|
|
1414
|
+
config = await loadConfig(cwd2);
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
p2.log.error(err instanceof Error ? err.message : String(err));
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
let claudeAgentModels;
|
|
1420
|
+
if (force && config.provider === "claude-code") {
|
|
1421
|
+
claudeAgentModels = await promptClaudeAgentModels(config.provider);
|
|
1422
|
+
}
|
|
1423
|
+
const spinner6 = p2.spinner();
|
|
1424
|
+
spinner6.start("Rebuilding files...");
|
|
1353
1425
|
try {
|
|
1354
|
-
const config = await loadConfig(cwd2);
|
|
1355
|
-
spinner6.message("Rebuilding files...");
|
|
1356
1426
|
const materializer = getMaterializer(config.provider);
|
|
1357
|
-
const report = await materializer.build(config, cwd2, { force });
|
|
1427
|
+
const report = await materializer.build(config, cwd2, { force, claudeAgentModels });
|
|
1358
1428
|
spinner6.stop(pc2.green("Build complete"));
|
|
1359
1429
|
const d = report.derived;
|
|
1360
1430
|
const upToDate = [...d.created, ...d.current, ...d.propagated];
|
|
1361
1431
|
if (upToDate.length > 0) {
|
|
1362
|
-
|
|
1432
|
+
p2.log.success(upToDate.join(", "));
|
|
1363
1433
|
}
|
|
1364
1434
|
if (d.propagated.length > 0) {
|
|
1365
|
-
|
|
1435
|
+
p2.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
|
|
1366
1436
|
${d.propagated.join("\n ")}`);
|
|
1367
1437
|
}
|
|
1368
1438
|
if (d.overwritten.length > 0) {
|
|
1369
|
-
|
|
1439
|
+
p2.log.warn(
|
|
1370
1440
|
pc2.yellow(
|
|
1371
1441
|
`--force REGENERATED ${d.overwritten.length} hand-edited generated file(s), discarding your edits:
|
|
1372
1442
|
` + d.overwritten.join("\n ")
|
|
1373
1443
|
)
|
|
1374
1444
|
);
|
|
1375
1445
|
if (d.backupDir) {
|
|
1376
|
-
|
|
1446
|
+
p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
|
|
1377
1447
|
}
|
|
1378
1448
|
}
|
|
1379
1449
|
if (d.preserved.length > 0) {
|
|
1380
|
-
|
|
1450
|
+
p2.log.warn(
|
|
1381
1451
|
pc2.yellow(
|
|
1382
1452
|
`Left ${d.preserved.length} hand-edited generated file(s) UNTOUCHED \u2014 your edits are safe:
|
|
1383
1453
|
` + d.preserved.join("\n ") + `
|
|
@@ -1386,26 +1456,26 @@ async function buildOnce(cwd2, force) {
|
|
|
1386
1456
|
)
|
|
1387
1457
|
);
|
|
1388
1458
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1459
|
+
p2.log.success(`Agent definitions (${config.provider})`);
|
|
1460
|
+
p2.log.success("MCP config");
|
|
1391
1461
|
const { created, overwritten, preserved, backupDir } = report.agents;
|
|
1392
1462
|
if (created.length > 0) {
|
|
1393
|
-
|
|
1463
|
+
p2.log.info(`Created ${created.length} missing agent file(s):
|
|
1394
1464
|
${created.join("\n ")}`);
|
|
1395
1465
|
}
|
|
1396
1466
|
if (overwritten.length > 0) {
|
|
1397
|
-
|
|
1467
|
+
p2.log.warn(
|
|
1398
1468
|
pc2.yellow(
|
|
1399
1469
|
`--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
|
|
1400
1470
|
` + overwritten.join("\n ")
|
|
1401
1471
|
)
|
|
1402
1472
|
);
|
|
1403
1473
|
if (backupDir) {
|
|
1404
|
-
|
|
1474
|
+
p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
|
|
1405
1475
|
}
|
|
1406
1476
|
}
|
|
1407
1477
|
if (preserved.length > 0) {
|
|
1408
|
-
|
|
1478
|
+
p2.log.info(
|
|
1409
1479
|
`Left ${preserved.length} existing agent file(s) untouched \u2014 agent files are yours to edit.
|
|
1410
1480
|
Re-run with --force to regenerate them from the packaged templates (this DESTROYS your edits;
|
|
1411
1481
|
a backup is written first).`
|
|
@@ -1413,7 +1483,7 @@ async function buildOnce(cwd2, force) {
|
|
|
1413
1483
|
}
|
|
1414
1484
|
} catch (err) {
|
|
1415
1485
|
spinner6.stop(pc2.red("Build failed"));
|
|
1416
|
-
|
|
1486
|
+
p2.log.error(err instanceof Error ? err.message : String(err));
|
|
1417
1487
|
process.exit(1);
|
|
1418
1488
|
}
|
|
1419
1489
|
}
|
|
@@ -2027,7 +2097,7 @@ function getProviderHealthFiles(provider) {
|
|
|
2027
2097
|
// src/commands/init.ts
|
|
2028
2098
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2029
2099
|
import { join as join16 } from "path";
|
|
2030
|
-
import * as
|
|
2100
|
+
import * as p5 from "@clack/prompts";
|
|
2031
2101
|
import pc8 from "picocolors";
|
|
2032
2102
|
|
|
2033
2103
|
// src/schema/init.ts
|
|
@@ -2066,7 +2136,7 @@ var taskDescriptionSchema = v2.pipe(
|
|
|
2066
2136
|
);
|
|
2067
2137
|
|
|
2068
2138
|
// src/utils/form.ts
|
|
2069
|
-
import * as
|
|
2139
|
+
import * as p3 from "@clack/prompts";
|
|
2070
2140
|
import * as v3 from "valibot";
|
|
2071
2141
|
var cliFormWithRetry = async (formFn, schema) => {
|
|
2072
2142
|
while (true) {
|
|
@@ -2074,11 +2144,60 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
2074
2144
|
const result = v3.safeParse(schema, res);
|
|
2075
2145
|
if (result.success) return result.output;
|
|
2076
2146
|
const messages = result.issues.map((i) => i.message).join(", ");
|
|
2077
|
-
|
|
2078
|
-
|
|
2147
|
+
p3.log.error(messages);
|
|
2148
|
+
p3.log.info("Please try again.\n");
|
|
2079
2149
|
}
|
|
2080
2150
|
};
|
|
2081
2151
|
|
|
2152
|
+
// src/commands/codex-model-prompt.ts
|
|
2153
|
+
import * as p4 from "@clack/prompts";
|
|
2154
|
+
var AGENT_LABELS2 = [
|
|
2155
|
+
{ key: "lead", label: "Lead" },
|
|
2156
|
+
{ key: "explorer", label: "Explorer" },
|
|
2157
|
+
{ key: "consultant", label: "Consultant" },
|
|
2158
|
+
{ key: "builder", label: "Builder" },
|
|
2159
|
+
{ key: "reviewer", label: "Reviewer" }
|
|
2160
|
+
];
|
|
2161
|
+
var CODEX_MODEL_CHOICES = [
|
|
2162
|
+
"gpt-5.6-sol",
|
|
2163
|
+
"gpt-5.6-terra",
|
|
2164
|
+
"gpt-5.6-luna",
|
|
2165
|
+
"gpt-5.5",
|
|
2166
|
+
"gpt-5.4",
|
|
2167
|
+
"gpt-5.4-mini",
|
|
2168
|
+
"gpt-5.3-codex-spark"
|
|
2169
|
+
];
|
|
2170
|
+
var CODEX_EFFORT_CHOICES = ["minimal", "low", "medium", "high", "xhigh"];
|
|
2171
|
+
async function promptCodexAgentModels(provider) {
|
|
2172
|
+
const codexAgentModels = {};
|
|
2173
|
+
if (provider !== "codex-cli") return codexAgentModels;
|
|
2174
|
+
for (const agent of AGENT_LABELS2) {
|
|
2175
|
+
const modelVal = await p4.select({
|
|
2176
|
+
message: `Model for ${agent.label}`,
|
|
2177
|
+
options: CODEX_MODEL_CHOICES.map((value) => ({ value, label: value })),
|
|
2178
|
+
initialValue: "gpt-5.6-terra"
|
|
2179
|
+
});
|
|
2180
|
+
if (p4.isCancel(modelVal)) {
|
|
2181
|
+
p4.cancel("Cancelled.");
|
|
2182
|
+
process.exit(0);
|
|
2183
|
+
}
|
|
2184
|
+
const effortVal = await p4.select({
|
|
2185
|
+
message: `Reasoning effort for ${agent.label}`,
|
|
2186
|
+
options: CODEX_EFFORT_CHOICES.map((value) => ({ value, label: value })),
|
|
2187
|
+
initialValue: "medium"
|
|
2188
|
+
});
|
|
2189
|
+
if (p4.isCancel(effortVal)) {
|
|
2190
|
+
p4.cancel("Cancelled.");
|
|
2191
|
+
process.exit(0);
|
|
2192
|
+
}
|
|
2193
|
+
codexAgentModels[agent.key] = {
|
|
2194
|
+
model: modelVal,
|
|
2195
|
+
effort: effortVal
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
return codexAgentModels;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2082
2201
|
// src/commands/init-helpers.ts
|
|
2083
2202
|
import { randomUUID } from "crypto";
|
|
2084
2203
|
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
@@ -2236,25 +2355,25 @@ async function runInit(cwd2, flags) {
|
|
|
2236
2355
|
name = flags.name;
|
|
2237
2356
|
} else {
|
|
2238
2357
|
name = await cliFormWithRetry(async () => {
|
|
2239
|
-
const val = await
|
|
2358
|
+
const val = await p5.text({
|
|
2240
2359
|
message: "Project name",
|
|
2241
2360
|
placeholder: "my-app",
|
|
2242
2361
|
...detectedName && { initialValue: detectedName }
|
|
2243
2362
|
});
|
|
2244
|
-
if (
|
|
2245
|
-
|
|
2363
|
+
if (p5.isCancel(val)) {
|
|
2364
|
+
p5.cancel("Cancelled.");
|
|
2246
2365
|
process.exit(0);
|
|
2247
2366
|
}
|
|
2248
2367
|
return val;
|
|
2249
2368
|
}, initNameSchema);
|
|
2250
2369
|
}
|
|
2251
2370
|
const description = await cliFormWithRetry(async () => {
|
|
2252
|
-
const val = await
|
|
2371
|
+
const val = await p5.text({
|
|
2253
2372
|
message: "Short description (shown to agents as context)",
|
|
2254
2373
|
placeholder: "A REST API for managing notes"
|
|
2255
2374
|
});
|
|
2256
|
-
if (
|
|
2257
|
-
|
|
2375
|
+
if (p5.isCancel(val)) {
|
|
2376
|
+
p5.cancel("Cancelled.");
|
|
2258
2377
|
process.exit(0);
|
|
2259
2378
|
}
|
|
2260
2379
|
return val;
|
|
@@ -2263,7 +2382,7 @@ async function runInit(cwd2, flags) {
|
|
|
2263
2382
|
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
2264
2383
|
provider = flags.provider;
|
|
2265
2384
|
} else {
|
|
2266
|
-
const val = await
|
|
2385
|
+
const val = await p5.select({
|
|
2267
2386
|
message: "AI provider",
|
|
2268
2387
|
options: [
|
|
2269
2388
|
{ value: "opencode", label: "OpenCode" },
|
|
@@ -2272,51 +2391,25 @@ async function runInit(cwd2, flags) {
|
|
|
2272
2391
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2273
2392
|
]
|
|
2274
2393
|
});
|
|
2275
|
-
if (
|
|
2276
|
-
|
|
2394
|
+
if (p5.isCancel(val)) {
|
|
2395
|
+
p5.cancel("Cancelled.");
|
|
2277
2396
|
process.exit(0);
|
|
2278
2397
|
}
|
|
2279
2398
|
provider = val;
|
|
2280
2399
|
}
|
|
2281
|
-
const
|
|
2282
|
-
|
|
2283
|
-
{ key: "explorer", label: "Explorer" },
|
|
2284
|
-
{ key: "consultant", label: "Consultant" },
|
|
2285
|
-
{ key: "builder", label: "Builder" },
|
|
2286
|
-
{ key: "reviewer", label: "Reviewer" }
|
|
2287
|
-
];
|
|
2288
|
-
const claudeAgentModels = {};
|
|
2289
|
-
if (provider === "claude-code") {
|
|
2290
|
-
for (const agent of AGENT_LABELS) {
|
|
2291
|
-
const val = await p3.select({
|
|
2292
|
-
message: `Model for ${agent.label}`,
|
|
2293
|
-
options: [
|
|
2294
|
-
{ value: "inherit", label: "inherit (default)" },
|
|
2295
|
-
{ value: "haiku", label: "haiku" },
|
|
2296
|
-
{ value: "sonnet", label: "sonnet" },
|
|
2297
|
-
{ value: "opus", label: "opus" },
|
|
2298
|
-
{ value: "fable", label: "fable" }
|
|
2299
|
-
],
|
|
2300
|
-
initialValue: "inherit"
|
|
2301
|
-
});
|
|
2302
|
-
if (p3.isCancel(val)) {
|
|
2303
|
-
p3.cancel("Cancelled.");
|
|
2304
|
-
process.exit(0);
|
|
2305
|
-
}
|
|
2306
|
-
claudeAgentModels[agent.key] = val;
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2400
|
+
const claudeAgentModels = await promptClaudeAgentModels(provider);
|
|
2401
|
+
const codexAgentModels = await promptCodexAgentModels(provider);
|
|
2309
2402
|
let docsPath;
|
|
2310
2403
|
if (flags.docs) {
|
|
2311
2404
|
docsPath = flags.docs;
|
|
2312
2405
|
} else {
|
|
2313
2406
|
docsPath = await cliFormWithRetry(async () => {
|
|
2314
|
-
const val = await
|
|
2407
|
+
const val = await p5.text({
|
|
2315
2408
|
message: "Docs folder path (agents will search here)",
|
|
2316
2409
|
initialValue: "./docs"
|
|
2317
2410
|
});
|
|
2318
|
-
if (
|
|
2319
|
-
|
|
2411
|
+
if (p5.isCancel(val)) {
|
|
2412
|
+
p5.cancel("Cancelled.");
|
|
2320
2413
|
process.exit(0);
|
|
2321
2414
|
}
|
|
2322
2415
|
return val;
|
|
@@ -2326,7 +2419,7 @@ async function runInit(cwd2, flags) {
|
|
|
2326
2419
|
if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
|
|
2327
2420
|
storageScope = flags.storageScope;
|
|
2328
2421
|
} else {
|
|
2329
|
-
const val = await
|
|
2422
|
+
const val = await p5.select({
|
|
2330
2423
|
message: "Storage scope",
|
|
2331
2424
|
options: [
|
|
2332
2425
|
{ value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
|
|
@@ -2337,8 +2430,8 @@ async function runInit(cwd2, flags) {
|
|
|
2337
2430
|
],
|
|
2338
2431
|
initialValue: "local"
|
|
2339
2432
|
});
|
|
2340
|
-
if (
|
|
2341
|
-
|
|
2433
|
+
if (p5.isCancel(val)) {
|
|
2434
|
+
p5.cancel("Cancelled.");
|
|
2342
2435
|
process.exit(0);
|
|
2343
2436
|
}
|
|
2344
2437
|
storageScope = val;
|
|
@@ -2347,7 +2440,7 @@ async function runInit(cwd2, flags) {
|
|
|
2347
2440
|
if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
|
|
2348
2441
|
tasksAdapter = flags.tasks;
|
|
2349
2442
|
} else {
|
|
2350
|
-
const val = await
|
|
2443
|
+
const val = await p5.select({
|
|
2351
2444
|
message: "Task adapter",
|
|
2352
2445
|
options: [
|
|
2353
2446
|
{ value: "local", label: "Local (feature_list.json)" },
|
|
@@ -2355,50 +2448,50 @@ async function runInit(cwd2, flags) {
|
|
|
2355
2448
|
{ value: "linear", label: "Linear (coming soon)" }
|
|
2356
2449
|
]
|
|
2357
2450
|
});
|
|
2358
|
-
if (
|
|
2359
|
-
|
|
2451
|
+
if (p5.isCancel(val)) {
|
|
2452
|
+
p5.cancel("Cancelled");
|
|
2360
2453
|
process.exit(0);
|
|
2361
2454
|
}
|
|
2362
2455
|
tasksAdapter = val;
|
|
2363
2456
|
}
|
|
2364
|
-
const addFirstTask = await
|
|
2365
|
-
if (
|
|
2366
|
-
|
|
2457
|
+
const addFirstTask = await p5.confirm({ message: "Add your first task now?", initialValue: false });
|
|
2458
|
+
if (p5.isCancel(addFirstTask)) {
|
|
2459
|
+
p5.cancel("Cancelled");
|
|
2367
2460
|
process.exit(0);
|
|
2368
2461
|
}
|
|
2369
2462
|
let firstTask;
|
|
2370
2463
|
if (addFirstTask) {
|
|
2371
2464
|
const taskTitle = await cliFormWithRetry(async () => {
|
|
2372
|
-
const val = await
|
|
2373
|
-
if (
|
|
2374
|
-
|
|
2465
|
+
const val = await p5.text({ message: "Task title" });
|
|
2466
|
+
if (p5.isCancel(val)) {
|
|
2467
|
+
p5.cancel("Cancelled");
|
|
2375
2468
|
process.exit(0);
|
|
2376
2469
|
}
|
|
2377
2470
|
return val.trim();
|
|
2378
2471
|
}, taskTitleSchema);
|
|
2379
2472
|
const taskDesc = await cliFormWithRetry(async () => {
|
|
2380
|
-
const val = await
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2473
|
+
const val = await p5.text({ message: "Task description", placeholder: "What and why" });
|
|
2474
|
+
if (p5.isCancel(val)) {
|
|
2475
|
+
p5.cancel("Cancelled");
|
|
2383
2476
|
process.exit(0);
|
|
2384
2477
|
}
|
|
2385
2478
|
return val.trim();
|
|
2386
2479
|
}, taskDescriptionSchema);
|
|
2387
2480
|
const acceptance = [];
|
|
2388
|
-
|
|
2481
|
+
p5.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
2389
2482
|
while (true) {
|
|
2390
|
-
const criterionVal = await
|
|
2483
|
+
const criterionVal = await p5.text({
|
|
2391
2484
|
message: ">",
|
|
2392
2485
|
placeholder: "Criterion (or press Enter to finish)"
|
|
2393
2486
|
});
|
|
2394
|
-
if (
|
|
2487
|
+
if (p5.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
|
|
2395
2488
|
acceptance.push(criterionVal.trim());
|
|
2396
2489
|
}
|
|
2397
2490
|
firstTask = { title: taskTitle, description: taskDesc, acceptance };
|
|
2398
2491
|
}
|
|
2399
2492
|
let configExt = "ts";
|
|
2400
2493
|
let featureListParseFailedPath = null;
|
|
2401
|
-
const spinner6 =
|
|
2494
|
+
const spinner6 = p5.spinner();
|
|
2402
2495
|
spinner6.start("Scaffolding...");
|
|
2403
2496
|
try {
|
|
2404
2497
|
const config = applyConfigDefaults({
|
|
@@ -2428,7 +2521,7 @@ async function runInit(cwd2, flags) {
|
|
|
2428
2521
|
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2429
2522
|
const db = await openDB(config, installDir);
|
|
2430
2523
|
await db.writeStorageState(installDir);
|
|
2431
|
-
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2524
|
+
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels, codexAgentModels });
|
|
2432
2525
|
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2433
2526
|
if (parseFailed) {
|
|
2434
2527
|
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
@@ -2437,7 +2530,7 @@ async function runInit(cwd2, flags) {
|
|
|
2437
2530
|
spinner6.stop("");
|
|
2438
2531
|
} catch (err) {
|
|
2439
2532
|
spinner6.stop("Failed");
|
|
2440
|
-
|
|
2533
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
2441
2534
|
throw err;
|
|
2442
2535
|
}
|
|
2443
2536
|
if (featureListParseFailedPath) {
|
|
@@ -2489,7 +2582,7 @@ async function runInit(cwd2, flags) {
|
|
|
2489
2582
|
}
|
|
2490
2583
|
|
|
2491
2584
|
// src/commands/migrate.ts
|
|
2492
|
-
import * as
|
|
2585
|
+
import * as p6 from "@clack/prompts";
|
|
2493
2586
|
import pc9 from "picocolors";
|
|
2494
2587
|
async function runMigrate(cwd2, opts) {
|
|
2495
2588
|
const config = await loadConfig(cwd2);
|
|
@@ -2497,7 +2590,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2497
2590
|
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2498
2591
|
target = opts.to;
|
|
2499
2592
|
} else {
|
|
2500
|
-
const val = await
|
|
2593
|
+
const val = await p6.select({
|
|
2501
2594
|
message: "Migrate to provider",
|
|
2502
2595
|
options: [
|
|
2503
2596
|
{ value: "claude-code", label: "Claude Code" },
|
|
@@ -2506,8 +2599,8 @@ async function runMigrate(cwd2, opts) {
|
|
|
2506
2599
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2507
2600
|
]
|
|
2508
2601
|
});
|
|
2509
|
-
if (
|
|
2510
|
-
|
|
2602
|
+
if (p6.isCancel(val)) {
|
|
2603
|
+
p6.cancel("Cancelled.");
|
|
2511
2604
|
process.exit(0);
|
|
2512
2605
|
}
|
|
2513
2606
|
target = val;
|
|
@@ -2516,17 +2609,17 @@ async function runMigrate(cwd2, opts) {
|
|
|
2516
2609
|
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2517
2610
|
return;
|
|
2518
2611
|
}
|
|
2519
|
-
const spinner6 =
|
|
2612
|
+
const spinner6 = p6.spinner();
|
|
2520
2613
|
spinner6.start(`Migrating from ${config.provider} to ${target}...`);
|
|
2521
2614
|
try {
|
|
2522
2615
|
const targetMaterializer = getMaterializer(target);
|
|
2523
2616
|
await targetMaterializer.build(config, cwd2);
|
|
2524
2617
|
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2525
|
-
|
|
2526
|
-
|
|
2618
|
+
p6.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2619
|
+
p6.log.warn(`Then run: ahk build`);
|
|
2527
2620
|
} catch (err) {
|
|
2528
2621
|
spinner6.stop(pc9.red("Migration failed"));
|
|
2529
|
-
|
|
2622
|
+
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
2530
2623
|
process.exit(1);
|
|
2531
2624
|
}
|
|
2532
2625
|
}
|
|
@@ -2771,12 +2864,65 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2771
2864
|
}
|
|
2772
2865
|
}
|
|
2773
2866
|
|
|
2867
|
+
// src/commands/models.ts
|
|
2868
|
+
import { join as join18 } from "path";
|
|
2869
|
+
import pc11 from "picocolors";
|
|
2870
|
+
async function resolveModelsContext(cwd2) {
|
|
2871
|
+
let config;
|
|
2872
|
+
try {
|
|
2873
|
+
config = await loadConfig(cwd2);
|
|
2874
|
+
} catch {
|
|
2875
|
+
return { ok: false, reason: "no-config" };
|
|
2876
|
+
}
|
|
2877
|
+
if (config.provider !== "claude-code") {
|
|
2878
|
+
return { ok: false, reason: "not-claude-code", provider: config.provider };
|
|
2879
|
+
}
|
|
2880
|
+
return { ok: true, config };
|
|
2881
|
+
}
|
|
2882
|
+
async function runModels(cwd2) {
|
|
2883
|
+
const ctx = await resolveModelsContext(cwd2);
|
|
2884
|
+
if (!ctx.ok) {
|
|
2885
|
+
if (ctx.reason === "no-config") {
|
|
2886
|
+
console.log("");
|
|
2887
|
+
console.log(` ${pc11.cyan("config".padEnd(16))}${pc11.yellow("[!]")} no agent-harness-kit.config found`);
|
|
2888
|
+
console.log(` ${"".padEnd(16)} ${pc11.dim("run: ahk init")}`);
|
|
2889
|
+
console.log("");
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
console.log(
|
|
2893
|
+
pc11.dim(
|
|
2894
|
+
`ahk models only applies to Claude Code projects (this project uses '${ctx.provider}') \u2014 nothing to do.`
|
|
2895
|
+
)
|
|
2896
|
+
);
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
const { config } = ctx;
|
|
2900
|
+
const claudeAgentModels = await promptClaudeAgentModels(config.provider);
|
|
2901
|
+
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config, claudeAgentModels), {
|
|
2902
|
+
force: true,
|
|
2903
|
+
backupRoot: join18(cwd2, config.storage.dir, "backups")
|
|
2904
|
+
});
|
|
2905
|
+
console.log("");
|
|
2906
|
+
if (agents.overwritten.length > 0) {
|
|
2907
|
+
console.log(pc11.green(`\u2713 Regenerated ${agents.overwritten.length} agent file(s) with updated models:`));
|
|
2908
|
+
for (const file of agents.overwritten) console.log(pc11.green(` \u2713 ${file}`));
|
|
2909
|
+
if (agents.backupDir) {
|
|
2910
|
+
console.log(pc11.dim(` Previous content backed up \u2192 ${agents.backupDir}`));
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
if (agents.created.length > 0) {
|
|
2914
|
+
console.log(pc11.green(`\u2713 Created ${agents.created.length} missing agent file(s):`));
|
|
2915
|
+
for (const file of agents.created) console.log(pc11.green(` \u2713 ${file}`));
|
|
2916
|
+
}
|
|
2917
|
+
console.log("");
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2774
2920
|
// src/commands/reset.ts
|
|
2775
2921
|
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2776
2922
|
import { homedir as homedir4 } from "os";
|
|
2777
|
-
import { join as
|
|
2778
|
-
import * as
|
|
2779
|
-
import
|
|
2923
|
+
import { join as join19, resolve as resolve8 } from "path";
|
|
2924
|
+
import * as p7 from "@clack/prompts";
|
|
2925
|
+
import pc12 from "picocolors";
|
|
2780
2926
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2781
2927
|
var PROVIDER_AGENT_DIRS = {
|
|
2782
2928
|
"claude-code": ".claude/agents",
|
|
@@ -2795,7 +2941,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2795
2941
|
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2796
2942
|
const agentExt = PROVIDER_AGENT_EXT[provider];
|
|
2797
2943
|
if (!existsSync16(agentDirPath)) {
|
|
2798
|
-
console.log(
|
|
2944
|
+
console.log(pc12.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2799
2945
|
return;
|
|
2800
2946
|
}
|
|
2801
2947
|
const existingFiles = [];
|
|
@@ -2807,32 +2953,32 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2807
2953
|
}
|
|
2808
2954
|
}
|
|
2809
2955
|
} catch {
|
|
2810
|
-
console.log(
|
|
2956
|
+
console.log(pc12.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2811
2957
|
return;
|
|
2812
2958
|
}
|
|
2813
2959
|
if (existingFiles.length === 0) {
|
|
2814
|
-
console.log(
|
|
2960
|
+
console.log(pc12.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2815
2961
|
return;
|
|
2816
2962
|
}
|
|
2817
2963
|
for (const file of existingFiles) {
|
|
2818
|
-
const confirm3 = await
|
|
2964
|
+
const confirm3 = await p7.confirm({
|
|
2819
2965
|
message: `Remove ${file}?`,
|
|
2820
2966
|
initialValue: true
|
|
2821
2967
|
});
|
|
2822
|
-
if (
|
|
2823
|
-
console.log(
|
|
2968
|
+
if (p7.isCancel(confirm3)) {
|
|
2969
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2824
2970
|
return;
|
|
2825
2971
|
}
|
|
2826
2972
|
if (confirm3) {
|
|
2827
2973
|
try {
|
|
2828
|
-
const filePath =
|
|
2974
|
+
const filePath = join19(agentDirPath, file);
|
|
2829
2975
|
rmSync2(filePath, { force: true });
|
|
2830
|
-
console.log(
|
|
2976
|
+
console.log(pc12.green(` Removed ${file}`));
|
|
2831
2977
|
} catch {
|
|
2832
|
-
console.error(
|
|
2978
|
+
console.error(pc12.red(` Failed to remove ${file}`));
|
|
2833
2979
|
}
|
|
2834
2980
|
} else {
|
|
2835
|
-
console.log(
|
|
2981
|
+
console.log(pc12.cyan(` Skipped ${file}`));
|
|
2836
2982
|
}
|
|
2837
2983
|
}
|
|
2838
2984
|
}
|
|
@@ -2841,7 +2987,7 @@ async function runReset(cwd2, opts) {
|
|
|
2841
2987
|
try {
|
|
2842
2988
|
config = await loadConfig(cwd2);
|
|
2843
2989
|
} catch {
|
|
2844
|
-
console.error(
|
|
2990
|
+
console.error(pc12.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2845
2991
|
process.exit(1);
|
|
2846
2992
|
}
|
|
2847
2993
|
const storageDir = config.storage.dir || ".harness";
|
|
@@ -2855,33 +3001,33 @@ async function runReset(cwd2, opts) {
|
|
|
2855
3001
|
resetDb = true;
|
|
2856
3002
|
} else {
|
|
2857
3003
|
if (config.database.type !== "sqlite") {
|
|
2858
|
-
console.log(
|
|
3004
|
+
console.log(pc12.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2859
3005
|
resetDb = false;
|
|
2860
3006
|
} else {
|
|
2861
|
-
const confirm3 = await
|
|
3007
|
+
const confirm3 = await p7.confirm({
|
|
2862
3008
|
message: `Delete database (${dbPath})?`,
|
|
2863
3009
|
initialValue: true
|
|
2864
3010
|
});
|
|
2865
|
-
if (
|
|
2866
|
-
console.log(
|
|
3011
|
+
if (p7.isCancel(confirm3)) {
|
|
3012
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2867
3013
|
return;
|
|
2868
3014
|
}
|
|
2869
3015
|
resetDb = confirm3;
|
|
2870
3016
|
}
|
|
2871
3017
|
}
|
|
2872
3018
|
} else if (!dbPath) {
|
|
2873
|
-
console.log(
|
|
3019
|
+
console.log(pc12.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2874
3020
|
}
|
|
2875
3021
|
if (existsSync16(featureListPath)) {
|
|
2876
3022
|
if (opts.force) {
|
|
2877
3023
|
resetFeatureList = true;
|
|
2878
3024
|
} else {
|
|
2879
|
-
const confirm3 = await
|
|
3025
|
+
const confirm3 = await p7.confirm({
|
|
2880
3026
|
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
2881
3027
|
initialValue: true
|
|
2882
3028
|
});
|
|
2883
|
-
if (
|
|
2884
|
-
console.log(
|
|
3029
|
+
if (p7.isCancel(confirm3)) {
|
|
3030
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2885
3031
|
return;
|
|
2886
3032
|
}
|
|
2887
3033
|
resetFeatureList = confirm3;
|
|
@@ -2895,17 +3041,17 @@ async function runReset(cwd2, opts) {
|
|
|
2895
3041
|
rmSync2(dbPath, { force: true });
|
|
2896
3042
|
rmSync2(`${dbPath}-wal`, { force: true });
|
|
2897
3043
|
rmSync2(`${dbPath}-shm`, { force: true });
|
|
2898
|
-
console.log(
|
|
3044
|
+
console.log(pc12.green(` \u2713 Removed ${dbPath}`));
|
|
2899
3045
|
} catch {
|
|
2900
|
-
console.error(
|
|
3046
|
+
console.error(pc12.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2901
3047
|
}
|
|
2902
3048
|
}
|
|
2903
3049
|
if (resetFeatureList) {
|
|
2904
3050
|
try {
|
|
2905
3051
|
rmSync2(featureListPath, { force: true });
|
|
2906
|
-
console.log(
|
|
3052
|
+
console.log(pc12.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2907
3053
|
} catch {
|
|
2908
|
-
console.error(
|
|
3054
|
+
console.error(pc12.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2909
3055
|
}
|
|
2910
3056
|
}
|
|
2911
3057
|
if (resetAgentMdsFlag) {
|
|
@@ -2913,16 +3059,16 @@ async function runReset(cwd2, opts) {
|
|
|
2913
3059
|
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2914
3060
|
}
|
|
2915
3061
|
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2916
|
-
console.log(
|
|
3062
|
+
console.log(pc12.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2917
3063
|
return;
|
|
2918
3064
|
}
|
|
2919
3065
|
console.log("");
|
|
2920
|
-
console.log(
|
|
3066
|
+
console.log(pc12.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2921
3067
|
}
|
|
2922
3068
|
|
|
2923
3069
|
// src/core/mcp-server.ts
|
|
2924
3070
|
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
2925
|
-
import { join as
|
|
3071
|
+
import { join as join21, resolve as resolve9 } from "path";
|
|
2926
3072
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2927
3073
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2928
3074
|
import {
|
|
@@ -2932,7 +3078,7 @@ import {
|
|
|
2932
3078
|
|
|
2933
3079
|
// src/core/permissions-check.ts
|
|
2934
3080
|
import { existsSync as existsSync17 } from "fs";
|
|
2935
|
-
import { join as
|
|
3081
|
+
import { join as join20 } from "path";
|
|
2936
3082
|
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2937
3083
|
function checkPermissionsSync(cwd2, config) {
|
|
2938
3084
|
if (config.provider !== "claude-code") {
|
|
@@ -2941,7 +3087,7 @@ function checkPermissionsSync(cwd2, config) {
|
|
|
2941
3087
|
const agents = {};
|
|
2942
3088
|
let in_sync = true;
|
|
2943
3089
|
for (const agent of AGENTS) {
|
|
2944
|
-
const filePath =
|
|
3090
|
+
const filePath = join20(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2945
3091
|
const exists = existsSync17(filePath);
|
|
2946
3092
|
if (!exists) in_sync = false;
|
|
2947
3093
|
agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
|
|
@@ -3403,7 +3549,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3403
3549
|
return ok2(JSON.stringify(result));
|
|
3404
3550
|
}
|
|
3405
3551
|
case "deps.snapshot": {
|
|
3406
|
-
const pkgPath2 =
|
|
3552
|
+
const pkgPath2 = join21(cwd2, "package.json");
|
|
3407
3553
|
if (!existsSync18(pkgPath2)) {
|
|
3408
3554
|
return ok2("package.json not found in project root", true);
|
|
3409
3555
|
}
|
|
@@ -3413,9 +3559,9 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3413
3559
|
dependencies: pkg2.dependencies ?? {},
|
|
3414
3560
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3415
3561
|
};
|
|
3416
|
-
const harnessDir =
|
|
3562
|
+
const harnessDir = join21(cwd2, ".harness");
|
|
3417
3563
|
mkdirSync9(harnessDir, { recursive: true });
|
|
3418
|
-
writeFileSync10(
|
|
3564
|
+
writeFileSync10(join21(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3419
3565
|
return ok2(
|
|
3420
3566
|
JSON.stringify({
|
|
3421
3567
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3424,8 +3570,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3424
3570
|
);
|
|
3425
3571
|
}
|
|
3426
3572
|
case "deps.check": {
|
|
3427
|
-
const pkgPath2 =
|
|
3428
|
-
const lockPath =
|
|
3573
|
+
const pkgPath2 = join21(cwd2, "package.json");
|
|
3574
|
+
const lockPath = join21(cwd2, ".harness", "deps-lock.json");
|
|
3429
3575
|
if (!existsSync18(pkgPath2)) {
|
|
3430
3576
|
return ok2("package.json not found in project root", true);
|
|
3431
3577
|
}
|
|
@@ -3531,7 +3677,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3531
3677
|
const files = [];
|
|
3532
3678
|
try {
|
|
3533
3679
|
for (const entry of readdirSync2(dir)) {
|
|
3534
|
-
const full =
|
|
3680
|
+
const full = join21(dir, entry);
|
|
3535
3681
|
const stat = statSync(full);
|
|
3536
3682
|
if (stat.isDirectory()) {
|
|
3537
3683
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3588,12 +3734,12 @@ async function runServe(cwd2, opts) {
|
|
|
3588
3734
|
|
|
3589
3735
|
// src/commands/status.ts
|
|
3590
3736
|
import Table from "cli-table3";
|
|
3591
|
-
import
|
|
3737
|
+
import pc13 from "picocolors";
|
|
3592
3738
|
var STATUS_COLOR = {
|
|
3593
|
-
pending: (s) =>
|
|
3594
|
-
in_progress: (s) =>
|
|
3595
|
-
done: (s) =>
|
|
3596
|
-
blocked: (s) =>
|
|
3739
|
+
pending: (s) => pc13.dim(s),
|
|
3740
|
+
in_progress: (s) => pc13.cyan(s),
|
|
3741
|
+
done: (s) => pc13.green(s),
|
|
3742
|
+
blocked: (s) => pc13.red(s)
|
|
3597
3743
|
};
|
|
3598
3744
|
async function runStatus(cwd2, opts) {
|
|
3599
3745
|
const config = await loadConfig(cwd2);
|
|
@@ -3614,11 +3760,11 @@ async function runStatus(cwd2, opts) {
|
|
|
3614
3760
|
return;
|
|
3615
3761
|
}
|
|
3616
3762
|
if (tasks.length === 0) {
|
|
3617
|
-
console.log(
|
|
3763
|
+
console.log(pc13.dim("No tasks yet. Run: ahk task add"));
|
|
3618
3764
|
return;
|
|
3619
3765
|
}
|
|
3620
3766
|
const table = new Table({
|
|
3621
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
3767
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc13.bold(h)),
|
|
3622
3768
|
style: { head: [], border: [] }
|
|
3623
3769
|
});
|
|
3624
3770
|
for (const t of tasks) {
|
|
@@ -3636,12 +3782,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3636
3782
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3637
3783
|
if (inProgress.length > 0) {
|
|
3638
3784
|
console.log("");
|
|
3639
|
-
console.log(
|
|
3785
|
+
console.log(pc13.bold("Active actions:"));
|
|
3640
3786
|
for (const t of inProgress) {
|
|
3641
3787
|
const actions = await db.getActionsForTask(t.id);
|
|
3642
3788
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
3643
3789
|
for (const a of active) {
|
|
3644
|
-
console.log(` ${
|
|
3790
|
+
console.log(` ${pc13.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
3645
3791
|
}
|
|
3646
3792
|
}
|
|
3647
3793
|
}
|
|
@@ -3650,10 +3796,10 @@ async function runStatus(cwd2, opts) {
|
|
|
3650
3796
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
3651
3797
|
return `${fn(s.status)}: ${s.total}`;
|
|
3652
3798
|
});
|
|
3653
|
-
console.log(
|
|
3799
|
+
console.log(pc13.dim("Tasks \u2014 ") + parts.join(pc13.dim(" | ")));
|
|
3654
3800
|
const archivedTasks = await db.getArchivedTasks();
|
|
3655
3801
|
if (archivedTasks.length > 0) {
|
|
3656
|
-
console.log(
|
|
3802
|
+
console.log(pc13.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
|
|
3657
3803
|
}
|
|
3658
3804
|
} finally {
|
|
3659
3805
|
await db.close();
|
|
@@ -3662,12 +3808,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3662
3808
|
|
|
3663
3809
|
// src/commands/sync.ts
|
|
3664
3810
|
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
3665
|
-
import { join as
|
|
3666
|
-
import
|
|
3811
|
+
import { join as join22, resolve as resolve10 } from "path";
|
|
3812
|
+
import pc14 from "picocolors";
|
|
3667
3813
|
async function runSync(cwd2, opts) {
|
|
3668
3814
|
const config = await loadConfig(cwd2);
|
|
3669
3815
|
const direction = opts.direction ?? "both";
|
|
3670
|
-
const featureListPath = resolve10(
|
|
3816
|
+
const featureListPath = resolve10(join22(cwd2, config.storage.dir, "feature_list.json"));
|
|
3671
3817
|
const db = await openDB(config, cwd2);
|
|
3672
3818
|
try {
|
|
3673
3819
|
if (direction === "in" || direction === "both") {
|
|
@@ -3682,48 +3828,48 @@ async function runSync(cwd2, opts) {
|
|
|
3682
3828
|
}
|
|
3683
3829
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3684
3830
|
if (!existsSync19(featureListPath)) {
|
|
3685
|
-
console.log(
|
|
3831
|
+
console.log(pc14.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3686
3832
|
return;
|
|
3687
3833
|
}
|
|
3688
3834
|
let seeds;
|
|
3689
3835
|
try {
|
|
3690
3836
|
seeds = JSON.parse(readFileSync11(featureListPath, "utf8"));
|
|
3691
3837
|
} catch (err) {
|
|
3692
|
-
console.error(
|
|
3838
|
+
console.error(pc14.red(`Failed to parse feature_list.json: ${err}`));
|
|
3693
3839
|
process.exit(1);
|
|
3694
3840
|
}
|
|
3695
3841
|
if (dryRun) {
|
|
3696
|
-
console.log(
|
|
3842
|
+
console.log(pc14.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
3697
3843
|
for (const t of seeds) {
|
|
3698
3844
|
const existing = await db.getTaskBySlug(t.slug);
|
|
3699
|
-
console.log(` ${existing ?
|
|
3845
|
+
console.log(` ${existing ? pc14.dim("skip") : pc14.green("add ")} ${t.slug}`);
|
|
3700
3846
|
}
|
|
3701
3847
|
return;
|
|
3702
3848
|
}
|
|
3703
3849
|
const result = await db.syncFromFeatureList(seeds);
|
|
3704
|
-
console.log(
|
|
3850
|
+
console.log(pc14.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
3705
3851
|
}
|
|
3706
3852
|
async function syncOut(db, cwd2, dryRun) {
|
|
3707
3853
|
if (dryRun) {
|
|
3708
3854
|
const tasks = await db.getTasks();
|
|
3709
|
-
console.log(
|
|
3855
|
+
console.log(pc14.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
3710
3856
|
console.log(` ${tasks.length} tasks would be written`);
|
|
3711
3857
|
return;
|
|
3712
3858
|
}
|
|
3713
3859
|
await db.writeFeatureList(cwd2);
|
|
3714
|
-
console.log(
|
|
3860
|
+
console.log(pc14.green("\u2713 Out-sync: feature_list.json updated"));
|
|
3715
3861
|
}
|
|
3716
3862
|
|
|
3717
3863
|
// src/commands/task/add.ts
|
|
3718
|
-
import * as
|
|
3719
|
-
import
|
|
3864
|
+
import * as p8 from "@clack/prompts";
|
|
3865
|
+
import pc15 from "picocolors";
|
|
3720
3866
|
async function runTaskAdd(cwd2) {
|
|
3721
|
-
|
|
3867
|
+
p8.intro(pc15.bold("agent-harness-kit \u2014 add task"));
|
|
3722
3868
|
const title = await cliFormWithRetry(
|
|
3723
3869
|
async () => {
|
|
3724
|
-
const val = await
|
|
3725
|
-
if (
|
|
3726
|
-
|
|
3870
|
+
const val = await p8.text({ message: "Task title" });
|
|
3871
|
+
if (p8.isCancel(val)) {
|
|
3872
|
+
p8.cancel("Cancelled.");
|
|
3727
3873
|
process.exit(0);
|
|
3728
3874
|
}
|
|
3729
3875
|
return val.trim();
|
|
@@ -3732,12 +3878,12 @@ async function runTaskAdd(cwd2) {
|
|
|
3732
3878
|
);
|
|
3733
3879
|
const description = await cliFormWithRetry(
|
|
3734
3880
|
async () => {
|
|
3735
|
-
const val = await
|
|
3881
|
+
const val = await p8.text({
|
|
3736
3882
|
message: "Description (what and why)",
|
|
3737
3883
|
placeholder: "Describe the task in more detail, including any relevant context or instructions for the agents."
|
|
3738
3884
|
});
|
|
3739
|
-
if (
|
|
3740
|
-
|
|
3885
|
+
if (p8.isCancel(val)) {
|
|
3886
|
+
p8.cancel("Cancelled.");
|
|
3741
3887
|
process.exit(0);
|
|
3742
3888
|
}
|
|
3743
3889
|
return val.trim();
|
|
@@ -3745,13 +3891,13 @@ async function runTaskAdd(cwd2) {
|
|
|
3745
3891
|
taskDescriptionSchema
|
|
3746
3892
|
);
|
|
3747
3893
|
const acceptance = [];
|
|
3748
|
-
|
|
3894
|
+
p8.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
3749
3895
|
while (true) {
|
|
3750
|
-
const val = await
|
|
3751
|
-
if (
|
|
3896
|
+
const val = await p8.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
|
|
3897
|
+
if (p8.isCancel(val) || !val || !val.trim()) break;
|
|
3752
3898
|
acceptance.push(val.trim());
|
|
3753
3899
|
}
|
|
3754
|
-
const spinner6 =
|
|
3900
|
+
const spinner6 = p8.spinner();
|
|
3755
3901
|
spinner6.start("Saving...");
|
|
3756
3902
|
try {
|
|
3757
3903
|
const config = await loadConfig(cwd2);
|
|
@@ -3761,11 +3907,11 @@ async function runTaskAdd(cwd2) {
|
|
|
3761
3907
|
await db.writeFeatureList(cwd2);
|
|
3762
3908
|
await db.close();
|
|
3763
3909
|
spinner6.stop("");
|
|
3764
|
-
console.log(
|
|
3765
|
-
console.log(
|
|
3910
|
+
console.log(pc15.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
|
|
3911
|
+
console.log(pc15.cyan("\u2192") + " " + pc15.cyan("ahk status") + " to see all tasks");
|
|
3766
3912
|
} catch (err) {
|
|
3767
|
-
spinner6.stop(
|
|
3768
|
-
|
|
3913
|
+
spinner6.stop(pc15.red("Failed"));
|
|
3914
|
+
p8.log.error(err instanceof Error ? err.message : String(err));
|
|
3769
3915
|
process.exit(1);
|
|
3770
3916
|
}
|
|
3771
3917
|
}
|
|
@@ -3774,7 +3920,7 @@ async function runTaskAdd(cwd2) {
|
|
|
3774
3920
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3775
3921
|
import { existsSync as existsSync20 } from "fs";
|
|
3776
3922
|
import { resolve as resolve11 } from "path";
|
|
3777
|
-
import
|
|
3923
|
+
import pc16 from "picocolors";
|
|
3778
3924
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3779
3925
|
const config = await loadConfig(cwd2);
|
|
3780
3926
|
if (config.health.required) {
|
|
@@ -3782,7 +3928,7 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3782
3928
|
if (existsSync20(scriptPath)) {
|
|
3783
3929
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3784
3930
|
if (result.status !== 0) {
|
|
3785
|
-
console.error(
|
|
3931
|
+
console.error(pc16.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
3786
3932
|
if (result.stdout) console.error(result.stdout);
|
|
3787
3933
|
if (result.stderr) console.error(result.stderr);
|
|
3788
3934
|
process.exit(1);
|
|
@@ -3795,79 +3941,79 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3795
3941
|
const isId = !isNaN(parsed);
|
|
3796
3942
|
const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
|
|
3797
3943
|
if (!task2) {
|
|
3798
|
-
console.error(
|
|
3944
|
+
console.error(pc16.red(`Task not found: ${idOrSlug}`));
|
|
3799
3945
|
process.exit(1);
|
|
3800
3946
|
}
|
|
3801
3947
|
if (task2.status === "done") {
|
|
3802
|
-
console.log(
|
|
3948
|
+
console.log(pc16.dim(`Task #${task2.id} is already done.`));
|
|
3803
3949
|
return;
|
|
3804
3950
|
}
|
|
3805
3951
|
await db.updateTaskStatus(task2.id, "done");
|
|
3806
3952
|
await db.writeFeatureList(cwd2);
|
|
3807
|
-
console.log(
|
|
3953
|
+
console.log(pc16.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
3808
3954
|
} finally {
|
|
3809
3955
|
await db.close();
|
|
3810
3956
|
}
|
|
3811
3957
|
}
|
|
3812
3958
|
|
|
3813
3959
|
// src/commands/task/edit.ts
|
|
3814
|
-
import * as
|
|
3815
|
-
import
|
|
3960
|
+
import * as p9 from "@clack/prompts";
|
|
3961
|
+
import pc17 from "picocolors";
|
|
3816
3962
|
async function runTaskEdit(cwd2) {
|
|
3817
|
-
|
|
3963
|
+
p9.intro(pc17.bold("agent-harness-kit \u2014 edit task"));
|
|
3818
3964
|
const config = await loadConfig(cwd2);
|
|
3819
3965
|
const db = await openDB(config, cwd2);
|
|
3820
3966
|
try {
|
|
3821
3967
|
const allTasks = await db.getTasks();
|
|
3822
3968
|
const activeTasks = allTasks.filter((t) => t.status !== "done");
|
|
3823
3969
|
if (activeTasks.length === 0) {
|
|
3824
|
-
|
|
3970
|
+
p9.log.error("No active tasks to edit.");
|
|
3825
3971
|
return;
|
|
3826
3972
|
}
|
|
3827
|
-
const taskId = await
|
|
3973
|
+
const taskId = await p9.select({
|
|
3828
3974
|
message: "Select a task to edit",
|
|
3829
3975
|
options: activeTasks.map((t) => ({
|
|
3830
3976
|
label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
|
|
3831
3977
|
value: t.id
|
|
3832
3978
|
}))
|
|
3833
3979
|
});
|
|
3834
|
-
if (
|
|
3835
|
-
|
|
3980
|
+
if (p9.isCancel(taskId)) {
|
|
3981
|
+
p9.cancel("Cancelled.");
|
|
3836
3982
|
process.exit(0);
|
|
3837
3983
|
}
|
|
3838
3984
|
const task2 = await db.getTaskById(taskId);
|
|
3839
3985
|
if (!task2) {
|
|
3840
|
-
|
|
3986
|
+
p9.log.error("Task not found");
|
|
3841
3987
|
process.exit(1);
|
|
3842
3988
|
}
|
|
3843
|
-
const title = await
|
|
3989
|
+
const title = await p9.text({
|
|
3844
3990
|
message: "Title",
|
|
3845
3991
|
initialValue: task2.title
|
|
3846
3992
|
});
|
|
3847
|
-
if (
|
|
3848
|
-
|
|
3993
|
+
if (p9.isCancel(title)) {
|
|
3994
|
+
p9.cancel("Cancelled.");
|
|
3849
3995
|
process.exit(0);
|
|
3850
3996
|
}
|
|
3851
|
-
const description = await
|
|
3997
|
+
const description = await p9.text({
|
|
3852
3998
|
message: "Description (what and why)",
|
|
3853
3999
|
initialValue: task2.description ?? ""
|
|
3854
4000
|
});
|
|
3855
|
-
if (
|
|
3856
|
-
|
|
4001
|
+
if (p9.isCancel(description)) {
|
|
4002
|
+
p9.cancel("Cancelled.");
|
|
3857
4003
|
process.exit(0);
|
|
3858
4004
|
}
|
|
3859
4005
|
const currentAcceptance = await db.getTaskAcceptance(task2.id);
|
|
3860
4006
|
const newAcceptance = [];
|
|
3861
|
-
|
|
4007
|
+
p9.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
|
|
3862
4008
|
for (let i = 0; i < currentAcceptance.length; i++) {
|
|
3863
4009
|
const ac = currentAcceptance[i];
|
|
3864
|
-
const val = await
|
|
4010
|
+
const val = await p9.text({
|
|
3865
4011
|
message: `#${i + 1}/${currentAcceptance.length}`,
|
|
3866
4012
|
initialValue: ac.criterion,
|
|
3867
4013
|
defaultValue: ""
|
|
3868
4014
|
});
|
|
3869
|
-
if (
|
|
3870
|
-
|
|
4015
|
+
if (p9.isCancel(val)) {
|
|
4016
|
+
p9.cancel("Cancelled.");
|
|
3871
4017
|
process.exit(0);
|
|
3872
4018
|
}
|
|
3873
4019
|
const trimmed = val.trim();
|
|
@@ -3876,11 +4022,11 @@ async function runTaskEdit(cwd2) {
|
|
|
3876
4022
|
}
|
|
3877
4023
|
}
|
|
3878
4024
|
while (true) {
|
|
3879
|
-
const val = await
|
|
3880
|
-
if (
|
|
4025
|
+
const val = await p9.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
|
|
4026
|
+
if (p9.isCancel(val) || !val || !val.trim()) break;
|
|
3881
4027
|
newAcceptance.push(val.trim());
|
|
3882
4028
|
}
|
|
3883
|
-
const spinner6 =
|
|
4029
|
+
const spinner6 = p9.spinner();
|
|
3884
4030
|
spinner6.start("Saving...");
|
|
3885
4031
|
try {
|
|
3886
4032
|
const newSlug = slugify(title);
|
|
@@ -3892,10 +4038,10 @@ async function runTaskEdit(cwd2) {
|
|
|
3892
4038
|
await db.updateTaskAcceptance(task2.id, newAcceptance);
|
|
3893
4039
|
await db.writeFeatureList(cwd2);
|
|
3894
4040
|
spinner6.stop("");
|
|
3895
|
-
console.log(
|
|
4041
|
+
console.log(pc17.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3896
4042
|
} catch (err) {
|
|
3897
|
-
spinner6.stop(
|
|
3898
|
-
|
|
4043
|
+
spinner6.stop(pc17.red("Failed"));
|
|
4044
|
+
p9.log.error(err instanceof Error ? err.message : String(err));
|
|
3899
4045
|
process.exit(1);
|
|
3900
4046
|
}
|
|
3901
4047
|
} finally {
|
|
@@ -3905,12 +4051,12 @@ async function runTaskEdit(cwd2) {
|
|
|
3905
4051
|
|
|
3906
4052
|
// src/commands/task/list.ts
|
|
3907
4053
|
import Table2 from "cli-table3";
|
|
3908
|
-
import
|
|
4054
|
+
import pc18 from "picocolors";
|
|
3909
4055
|
var STATUS_COLOR2 = {
|
|
3910
|
-
pending: (s) =>
|
|
3911
|
-
in_progress: (s) =>
|
|
3912
|
-
done: (s) =>
|
|
3913
|
-
blocked: (s) =>
|
|
4056
|
+
pending: (s) => pc18.dim(s),
|
|
4057
|
+
in_progress: (s) => pc18.cyan(s),
|
|
4058
|
+
done: (s) => pc18.green(s),
|
|
4059
|
+
blocked: (s) => pc18.red(s)
|
|
3914
4060
|
};
|
|
3915
4061
|
async function runTaskList(cwd2, opts) {
|
|
3916
4062
|
const config = await loadConfig(cwd2);
|
|
@@ -3927,11 +4073,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
3927
4073
|
let msg = "No tasks";
|
|
3928
4074
|
if (filterStatus) msg += ` with status: ${filterStatus}`;
|
|
3929
4075
|
if (opts.archived) msg += " (archived)";
|
|
3930
|
-
console.log(
|
|
4076
|
+
console.log(pc18.dim(msg + "."));
|
|
3931
4077
|
return;
|
|
3932
4078
|
}
|
|
3933
4079
|
const table = new Table2({
|
|
3934
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
4080
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc18.bold(h)),
|
|
3935
4081
|
style: { head: [], border: [] }
|
|
3936
4082
|
});
|
|
3937
4083
|
for (const t of tasks) {
|
|
@@ -3942,7 +4088,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3942
4088
|
if (!opts.archived && !opts.includeArchived) {
|
|
3943
4089
|
const archivedTasks = await db.getArchivedTasks();
|
|
3944
4090
|
if (archivedTasks.length > 0) {
|
|
3945
|
-
console.log(
|
|
4091
|
+
console.log(pc18.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
|
|
3946
4092
|
}
|
|
3947
4093
|
}
|
|
3948
4094
|
} finally {
|
|
@@ -3952,8 +4098,8 @@ async function runTaskList(cwd2, opts) {
|
|
|
3952
4098
|
|
|
3953
4099
|
// src/core/path-probe.ts
|
|
3954
4100
|
import { accessSync, constants, readdirSync as readdirSync3 } from "fs";
|
|
3955
|
-
import { join as
|
|
3956
|
-
import
|
|
4101
|
+
import { join as join23 } from "path";
|
|
4102
|
+
import pc19 from "picocolors";
|
|
3957
4103
|
var DEFAULT_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
3958
4104
|
function defaultIsExecutable(filePath) {
|
|
3959
4105
|
try {
|
|
@@ -3997,7 +4143,7 @@ function resolveOnPath(name, options = {}) {
|
|
|
3997
4143
|
}
|
|
3998
4144
|
for (const dir of dirs) {
|
|
3999
4145
|
try {
|
|
4000
|
-
if (isExecutable(
|
|
4146
|
+
if (isExecutable(join23(dir, name))) return true;
|
|
4001
4147
|
} catch {
|
|
4002
4148
|
continue;
|
|
4003
4149
|
}
|
|
@@ -4008,16 +4154,16 @@ function isExecutableOnPath(name) {
|
|
|
4008
4154
|
return resolveOnPath(name);
|
|
4009
4155
|
}
|
|
4010
4156
|
function printMissingGlobalBinaryWarning() {
|
|
4011
|
-
console.error(
|
|
4012
|
-
console.error(
|
|
4013
|
-
console.error(
|
|
4014
|
-
console.error(
|
|
4015
|
-
console.error(
|
|
4016
|
-
console.error(
|
|
4157
|
+
console.error(pc19.yellow("\u26A0 `ahk` was not found on your PATH."));
|
|
4158
|
+
console.error(pc19.dim(" Your project has no local install, so the generated MCP config launches"));
|
|
4159
|
+
console.error(pc19.dim(" `ahk serve` directly. Without `ahk` on your PATH, starting the MCP server"));
|
|
4160
|
+
console.error(pc19.dim(" from that config will fail. This is only a warning \u2014 the command continues."));
|
|
4161
|
+
console.error(pc19.dim(` Run: npm i -g ${pkg.name} (install globally)`));
|
|
4162
|
+
console.error(pc19.dim(` or: npm install --save-dev ${pkg.name} (install locally in this project)`));
|
|
4017
4163
|
}
|
|
4018
4164
|
|
|
4019
4165
|
// src/core/update-check.ts
|
|
4020
|
-
import
|
|
4166
|
+
import pc20 from "picocolors";
|
|
4021
4167
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
4022
4168
|
var TIMEOUT_MS2 = 2500;
|
|
4023
4169
|
function checkForUpdate(currentVersion) {
|
|
@@ -4035,8 +4181,8 @@ function checkForUpdate(currentVersion) {
|
|
|
4035
4181
|
}
|
|
4036
4182
|
function printUpdateMessage({ current, latest }) {
|
|
4037
4183
|
const lines = [
|
|
4038
|
-
` Update available ${
|
|
4039
|
-
` Run: ${
|
|
4184
|
+
` Update available ${pc20.dim(current)} \u2192 ${pc20.green(latest)} `,
|
|
4185
|
+
` Run: ${pc20.cyan(`pnpm i ${pkg.name}@${latest}`)} `
|
|
4040
4186
|
];
|
|
4041
4187
|
drawBox(lines);
|
|
4042
4188
|
}
|
|
@@ -4113,7 +4259,7 @@ migrate.command("storage").description(
|
|
|
4113
4259
|
try {
|
|
4114
4260
|
await runMigrateStorage(cwd, { force: opts.force, dryRun: opts["dry-run"] });
|
|
4115
4261
|
} catch (err) {
|
|
4116
|
-
console.error(
|
|
4262
|
+
console.error(pc21.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
|
|
4117
4263
|
process.exit(1);
|
|
4118
4264
|
}
|
|
4119
4265
|
});
|
|
@@ -4126,6 +4272,9 @@ program.command("reset").description("Reset/clear harness data (DB, feature list
|
|
|
4126
4272
|
program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
|
|
4127
4273
|
await runDoctor(cwd);
|
|
4128
4274
|
});
|
|
4275
|
+
program.command("models").description("Re-prompt per-role Claude Code models and regenerate .claude/agents/*.md (claude-code projects only)").action(async () => {
|
|
4276
|
+
await runModels(cwd);
|
|
4277
|
+
});
|
|
4129
4278
|
program.hook("preAction", () => {
|
|
4130
4279
|
if (!isLocalInstallSatisfied(cwd)) {
|
|
4131
4280
|
printLocalInstallWarning();
|