@cardor/agent-harness-kit 2.1.1 → 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 +11 -5
- package/dist/cli.js +184 -114
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -53,7 +53,7 @@ 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
|
|
@@ -373,6 +373,17 @@ function mergeTomlSection(content, sectionName, sectionBody) {
|
|
|
373
373
|
];
|
|
374
374
|
return newLines.join("\n");
|
|
375
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
|
+
}
|
|
376
387
|
function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
377
388
|
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
378
389
|
let content = "";
|
|
@@ -383,8 +394,10 @@ function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
383
394
|
const sectionBody = [
|
|
384
395
|
`command = ${JSON.stringify(command)}`,
|
|
385
396
|
`args = ${JSON.stringify(args)}`,
|
|
386
|
-
'default_tools_approval_mode = "
|
|
397
|
+
'default_tools_approval_mode = "approve"'
|
|
387
398
|
].join("\n");
|
|
399
|
+
content = ensureTomlTopLevelKey(content, "model", "gpt-5.6-terra");
|
|
400
|
+
content = ensureTomlTopLevelKey(content, "model_reasoning_effort", "medium");
|
|
388
401
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
389
402
|
writeFileSync2(filePath, content, "utf8");
|
|
390
403
|
}
|
|
@@ -428,14 +441,16 @@ function claudeDisallowedTools(agentName) {
|
|
|
428
441
|
function opencodePermissions(agentName) {
|
|
429
442
|
return restrictionFor(agentName) === "no-write" ? { edit: "deny" } : {};
|
|
430
443
|
}
|
|
431
|
-
function codexSandboxMode(
|
|
432
|
-
return
|
|
444
|
+
function codexSandboxMode(_agentName) {
|
|
445
|
+
return "danger-full-access";
|
|
433
446
|
}
|
|
434
|
-
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)
|
|
435
448
|
|
|
436
|
-
This agent runs
|
|
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.
|
|
437
450
|
|
|
438
|
-
|
|
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.
|
|
452
|
+
|
|
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.`;
|
|
439
454
|
function codexRestrictionNotice(agentName) {
|
|
440
455
|
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
441
456
|
}
|
|
@@ -783,7 +798,7 @@ function stripFrontmatter(md) {
|
|
|
783
798
|
}
|
|
784
799
|
return { description, body };
|
|
785
800
|
}
|
|
786
|
-
function toCodexToml(tomlName, agentName, description, body) {
|
|
801
|
+
function toCodexToml(tomlName, agentName, description, body, opts) {
|
|
787
802
|
const safe = (s) => s.replace(/"""/g, '""\\u0022');
|
|
788
803
|
const sandboxMode = codexSandboxMode(agentName);
|
|
789
804
|
const notice = codexRestrictionNotice(agentName);
|
|
@@ -792,9 +807,14 @@ function toCodexToml(tomlName, agentName, description, body) {
|
|
|
792
807
|
---
|
|
793
808
|
|
|
794
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
|
+
` : "";
|
|
795
815
|
return `name = "${tomlName}"
|
|
796
816
|
sandbox_mode = "${sandboxMode}"
|
|
797
|
-
|
|
817
|
+
${modelBlock}
|
|
798
818
|
description = """
|
|
799
819
|
${safe(description)}
|
|
800
820
|
"""
|
|
@@ -804,29 +824,29 @@ ${safe(instructions)}
|
|
|
804
824
|
"""
|
|
805
825
|
`;
|
|
806
826
|
}
|
|
807
|
-
function agentLeadToml(vars) {
|
|
827
|
+
function agentLeadToml(vars, opts) {
|
|
808
828
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
809
|
-
return toCodexToml("lead", "lead", description, body);
|
|
829
|
+
return toCodexToml("lead", "lead", description, body, opts);
|
|
810
830
|
}
|
|
811
|
-
function agentLeadAsDefaultToml(vars) {
|
|
831
|
+
function agentLeadAsDefaultToml(vars, opts) {
|
|
812
832
|
const { description, body } = stripFrontmatter(loadAgentTemplate("lead", vars));
|
|
813
|
-
return toCodexToml("default", "lead", description, body);
|
|
833
|
+
return toCodexToml("default", "lead", description, body, opts);
|
|
814
834
|
}
|
|
815
|
-
function agentExplorerToml(vars) {
|
|
835
|
+
function agentExplorerToml(vars, opts) {
|
|
816
836
|
const { description, body } = stripFrontmatter(loadAgentTemplate("explorer", vars));
|
|
817
|
-
return toCodexToml("explorer", "explorer", description, body);
|
|
837
|
+
return toCodexToml("explorer", "explorer", description, body, opts);
|
|
818
838
|
}
|
|
819
|
-
function agentBuilderToml(vars) {
|
|
839
|
+
function agentBuilderToml(vars, opts) {
|
|
820
840
|
const { description, body } = stripFrontmatter(loadAgentTemplate("builder", vars));
|
|
821
|
-
return toCodexToml("builder", "builder", description, body);
|
|
841
|
+
return toCodexToml("builder", "builder", description, body, opts);
|
|
822
842
|
}
|
|
823
|
-
function agentReviewerToml(vars) {
|
|
843
|
+
function agentReviewerToml(vars, opts) {
|
|
824
844
|
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
825
|
-
return toCodexToml("reviewer", "reviewer", description, body);
|
|
845
|
+
return toCodexToml("reviewer", "reviewer", description, body, opts);
|
|
826
846
|
}
|
|
827
|
-
function agentConsultantToml(vars) {
|
|
847
|
+
function agentConsultantToml(vars, opts) {
|
|
828
848
|
const { description, body } = stripFrontmatter(loadAgentTemplate("consultant", vars));
|
|
829
|
-
return toCodexToml("consultant", "consultant", description, body);
|
|
849
|
+
return toCodexToml("consultant", "consultant", description, body, opts);
|
|
830
850
|
}
|
|
831
851
|
function stripFrontmatterBlockSequence(md, key) {
|
|
832
852
|
const re = new RegExp(`^${key}:\\n(?: - [^\\n]+\\n)+`, "m");
|
|
@@ -1128,20 +1148,20 @@ No tasks in progress.
|
|
|
1128
1148
|
// src/core/materializer/codex-cli.ts
|
|
1129
1149
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1130
1150
|
import { join as join8, resolve as resolve3 } from "path";
|
|
1131
|
-
function codexAgentFiles(config) {
|
|
1151
|
+
function codexAgentFiles(config, modelsByRole) {
|
|
1132
1152
|
const projectName = config.project.name;
|
|
1133
1153
|
return [
|
|
1134
|
-
{ relPath: ".codex/agents/lead.toml", content: agentLeadToml({ projectName }) },
|
|
1135
|
-
{ relPath: ".codex/agents/explorer.toml", content: agentExplorerToml({ projectName }) },
|
|
1136
|
-
{ relPath: ".codex/agents/consultant.toml", content: agentConsultantToml({ projectName }) },
|
|
1137
|
-
{ relPath: ".codex/agents/builder.toml", content: agentBuilderToml({ projectName }) },
|
|
1138
|
-
{ relPath: ".codex/agents/reviewer.toml", content: agentReviewerToml({ projectName }) },
|
|
1139
|
-
{ 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) }
|
|
1140
1160
|
];
|
|
1141
1161
|
}
|
|
1142
1162
|
var CodexCliMaterializer = class {
|
|
1143
1163
|
async scaffold(config, opts) {
|
|
1144
|
-
const { cwd: cwd2 } = opts;
|
|
1164
|
+
const { cwd: cwd2, codexAgentModels } = opts;
|
|
1145
1165
|
const write2 = (relPath, content, mode) => {
|
|
1146
1166
|
const abs = join8(cwd2, relPath);
|
|
1147
1167
|
mkdirSync4(resolve3(abs, ".."), { recursive: true });
|
|
@@ -1163,7 +1183,7 @@ No tasks in progress.
|
|
|
1163
1183
|
`
|
|
1164
1184
|
);
|
|
1165
1185
|
}
|
|
1166
|
-
writeAgentFiles(cwd2, codexAgentFiles(config));
|
|
1186
|
+
writeAgentFiles(cwd2, codexAgentFiles(config, codexAgentModels));
|
|
1167
1187
|
mergeCodexConfigToml(join8(cwd2, ".codex/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1168
1188
|
appendGitignore(cwd2);
|
|
1169
1189
|
writeSkills(cwd2, ".agents/skills");
|
|
@@ -1174,7 +1194,7 @@ No tasks in progress.
|
|
|
1174
1194
|
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1175
1195
|
{ force: opts.force, backupRoot: join8(cwd2, config.storage.dir, "backups") }
|
|
1176
1196
|
);
|
|
1177
|
-
const agents = writeAgentFiles(cwd2, codexAgentFiles(config), {
|
|
1197
|
+
const agents = writeAgentFiles(cwd2, codexAgentFiles(config, opts.codexAgentModels), {
|
|
1178
1198
|
force: opts.force,
|
|
1179
1199
|
backupRoot: join8(cwd2, config.storage.dir, "backups")
|
|
1180
1200
|
});
|
|
@@ -2077,7 +2097,7 @@ function getProviderHealthFiles(provider) {
|
|
|
2077
2097
|
// src/commands/init.ts
|
|
2078
2098
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2079
2099
|
import { join as join16 } from "path";
|
|
2080
|
-
import * as
|
|
2100
|
+
import * as p5 from "@clack/prompts";
|
|
2081
2101
|
import pc8 from "picocolors";
|
|
2082
2102
|
|
|
2083
2103
|
// src/schema/init.ts
|
|
@@ -2129,6 +2149,55 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
2129
2149
|
}
|
|
2130
2150
|
};
|
|
2131
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
|
+
|
|
2132
2201
|
// src/commands/init-helpers.ts
|
|
2133
2202
|
import { randomUUID } from "crypto";
|
|
2134
2203
|
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
@@ -2286,25 +2355,25 @@ async function runInit(cwd2, flags) {
|
|
|
2286
2355
|
name = flags.name;
|
|
2287
2356
|
} else {
|
|
2288
2357
|
name = await cliFormWithRetry(async () => {
|
|
2289
|
-
const val = await
|
|
2358
|
+
const val = await p5.text({
|
|
2290
2359
|
message: "Project name",
|
|
2291
2360
|
placeholder: "my-app",
|
|
2292
2361
|
...detectedName && { initialValue: detectedName }
|
|
2293
2362
|
});
|
|
2294
|
-
if (
|
|
2295
|
-
|
|
2363
|
+
if (p5.isCancel(val)) {
|
|
2364
|
+
p5.cancel("Cancelled.");
|
|
2296
2365
|
process.exit(0);
|
|
2297
2366
|
}
|
|
2298
2367
|
return val;
|
|
2299
2368
|
}, initNameSchema);
|
|
2300
2369
|
}
|
|
2301
2370
|
const description = await cliFormWithRetry(async () => {
|
|
2302
|
-
const val = await
|
|
2371
|
+
const val = await p5.text({
|
|
2303
2372
|
message: "Short description (shown to agents as context)",
|
|
2304
2373
|
placeholder: "A REST API for managing notes"
|
|
2305
2374
|
});
|
|
2306
|
-
if (
|
|
2307
|
-
|
|
2375
|
+
if (p5.isCancel(val)) {
|
|
2376
|
+
p5.cancel("Cancelled.");
|
|
2308
2377
|
process.exit(0);
|
|
2309
2378
|
}
|
|
2310
2379
|
return val;
|
|
@@ -2313,7 +2382,7 @@ async function runInit(cwd2, flags) {
|
|
|
2313
2382
|
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
2314
2383
|
provider = flags.provider;
|
|
2315
2384
|
} else {
|
|
2316
|
-
const val = await
|
|
2385
|
+
const val = await p5.select({
|
|
2317
2386
|
message: "AI provider",
|
|
2318
2387
|
options: [
|
|
2319
2388
|
{ value: "opencode", label: "OpenCode" },
|
|
@@ -2322,24 +2391,25 @@ async function runInit(cwd2, flags) {
|
|
|
2322
2391
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2323
2392
|
]
|
|
2324
2393
|
});
|
|
2325
|
-
if (
|
|
2326
|
-
|
|
2394
|
+
if (p5.isCancel(val)) {
|
|
2395
|
+
p5.cancel("Cancelled.");
|
|
2327
2396
|
process.exit(0);
|
|
2328
2397
|
}
|
|
2329
2398
|
provider = val;
|
|
2330
2399
|
}
|
|
2331
2400
|
const claudeAgentModels = await promptClaudeAgentModels(provider);
|
|
2401
|
+
const codexAgentModels = await promptCodexAgentModels(provider);
|
|
2332
2402
|
let docsPath;
|
|
2333
2403
|
if (flags.docs) {
|
|
2334
2404
|
docsPath = flags.docs;
|
|
2335
2405
|
} else {
|
|
2336
2406
|
docsPath = await cliFormWithRetry(async () => {
|
|
2337
|
-
const val = await
|
|
2407
|
+
const val = await p5.text({
|
|
2338
2408
|
message: "Docs folder path (agents will search here)",
|
|
2339
2409
|
initialValue: "./docs"
|
|
2340
2410
|
});
|
|
2341
|
-
if (
|
|
2342
|
-
|
|
2411
|
+
if (p5.isCancel(val)) {
|
|
2412
|
+
p5.cancel("Cancelled.");
|
|
2343
2413
|
process.exit(0);
|
|
2344
2414
|
}
|
|
2345
2415
|
return val;
|
|
@@ -2349,7 +2419,7 @@ async function runInit(cwd2, flags) {
|
|
|
2349
2419
|
if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
|
|
2350
2420
|
storageScope = flags.storageScope;
|
|
2351
2421
|
} else {
|
|
2352
|
-
const val = await
|
|
2422
|
+
const val = await p5.select({
|
|
2353
2423
|
message: "Storage scope",
|
|
2354
2424
|
options: [
|
|
2355
2425
|
{ value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
|
|
@@ -2360,8 +2430,8 @@ async function runInit(cwd2, flags) {
|
|
|
2360
2430
|
],
|
|
2361
2431
|
initialValue: "local"
|
|
2362
2432
|
});
|
|
2363
|
-
if (
|
|
2364
|
-
|
|
2433
|
+
if (p5.isCancel(val)) {
|
|
2434
|
+
p5.cancel("Cancelled.");
|
|
2365
2435
|
process.exit(0);
|
|
2366
2436
|
}
|
|
2367
2437
|
storageScope = val;
|
|
@@ -2370,7 +2440,7 @@ async function runInit(cwd2, flags) {
|
|
|
2370
2440
|
if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
|
|
2371
2441
|
tasksAdapter = flags.tasks;
|
|
2372
2442
|
} else {
|
|
2373
|
-
const val = await
|
|
2443
|
+
const val = await p5.select({
|
|
2374
2444
|
message: "Task adapter",
|
|
2375
2445
|
options: [
|
|
2376
2446
|
{ value: "local", label: "Local (feature_list.json)" },
|
|
@@ -2378,50 +2448,50 @@ async function runInit(cwd2, flags) {
|
|
|
2378
2448
|
{ value: "linear", label: "Linear (coming soon)" }
|
|
2379
2449
|
]
|
|
2380
2450
|
});
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2451
|
+
if (p5.isCancel(val)) {
|
|
2452
|
+
p5.cancel("Cancelled");
|
|
2383
2453
|
process.exit(0);
|
|
2384
2454
|
}
|
|
2385
2455
|
tasksAdapter = val;
|
|
2386
2456
|
}
|
|
2387
|
-
const addFirstTask = await
|
|
2388
|
-
if (
|
|
2389
|
-
|
|
2457
|
+
const addFirstTask = await p5.confirm({ message: "Add your first task now?", initialValue: false });
|
|
2458
|
+
if (p5.isCancel(addFirstTask)) {
|
|
2459
|
+
p5.cancel("Cancelled");
|
|
2390
2460
|
process.exit(0);
|
|
2391
2461
|
}
|
|
2392
2462
|
let firstTask;
|
|
2393
2463
|
if (addFirstTask) {
|
|
2394
2464
|
const taskTitle = await cliFormWithRetry(async () => {
|
|
2395
|
-
const val = await
|
|
2396
|
-
if (
|
|
2397
|
-
|
|
2465
|
+
const val = await p5.text({ message: "Task title" });
|
|
2466
|
+
if (p5.isCancel(val)) {
|
|
2467
|
+
p5.cancel("Cancelled");
|
|
2398
2468
|
process.exit(0);
|
|
2399
2469
|
}
|
|
2400
2470
|
return val.trim();
|
|
2401
2471
|
}, taskTitleSchema);
|
|
2402
2472
|
const taskDesc = await cliFormWithRetry(async () => {
|
|
2403
|
-
const val = await
|
|
2404
|
-
if (
|
|
2405
|
-
|
|
2473
|
+
const val = await p5.text({ message: "Task description", placeholder: "What and why" });
|
|
2474
|
+
if (p5.isCancel(val)) {
|
|
2475
|
+
p5.cancel("Cancelled");
|
|
2406
2476
|
process.exit(0);
|
|
2407
2477
|
}
|
|
2408
2478
|
return val.trim();
|
|
2409
2479
|
}, taskDescriptionSchema);
|
|
2410
2480
|
const acceptance = [];
|
|
2411
|
-
|
|
2481
|
+
p5.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
2412
2482
|
while (true) {
|
|
2413
|
-
const criterionVal = await
|
|
2483
|
+
const criterionVal = await p5.text({
|
|
2414
2484
|
message: ">",
|
|
2415
2485
|
placeholder: "Criterion (or press Enter to finish)"
|
|
2416
2486
|
});
|
|
2417
|
-
if (
|
|
2487
|
+
if (p5.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
|
|
2418
2488
|
acceptance.push(criterionVal.trim());
|
|
2419
2489
|
}
|
|
2420
2490
|
firstTask = { title: taskTitle, description: taskDesc, acceptance };
|
|
2421
2491
|
}
|
|
2422
2492
|
let configExt = "ts";
|
|
2423
2493
|
let featureListParseFailedPath = null;
|
|
2424
|
-
const spinner6 =
|
|
2494
|
+
const spinner6 = p5.spinner();
|
|
2425
2495
|
spinner6.start("Scaffolding...");
|
|
2426
2496
|
try {
|
|
2427
2497
|
const config = applyConfigDefaults({
|
|
@@ -2451,7 +2521,7 @@ async function runInit(cwd2, flags) {
|
|
|
2451
2521
|
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2452
2522
|
const db = await openDB(config, installDir);
|
|
2453
2523
|
await db.writeStorageState(installDir);
|
|
2454
|
-
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2524
|
+
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels, codexAgentModels });
|
|
2455
2525
|
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2456
2526
|
if (parseFailed) {
|
|
2457
2527
|
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
@@ -2460,7 +2530,7 @@ async function runInit(cwd2, flags) {
|
|
|
2460
2530
|
spinner6.stop("");
|
|
2461
2531
|
} catch (err) {
|
|
2462
2532
|
spinner6.stop("Failed");
|
|
2463
|
-
|
|
2533
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
2464
2534
|
throw err;
|
|
2465
2535
|
}
|
|
2466
2536
|
if (featureListParseFailedPath) {
|
|
@@ -2512,7 +2582,7 @@ async function runInit(cwd2, flags) {
|
|
|
2512
2582
|
}
|
|
2513
2583
|
|
|
2514
2584
|
// src/commands/migrate.ts
|
|
2515
|
-
import * as
|
|
2585
|
+
import * as p6 from "@clack/prompts";
|
|
2516
2586
|
import pc9 from "picocolors";
|
|
2517
2587
|
async function runMigrate(cwd2, opts) {
|
|
2518
2588
|
const config = await loadConfig(cwd2);
|
|
@@ -2520,7 +2590,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2520
2590
|
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2521
2591
|
target = opts.to;
|
|
2522
2592
|
} else {
|
|
2523
|
-
const val = await
|
|
2593
|
+
const val = await p6.select({
|
|
2524
2594
|
message: "Migrate to provider",
|
|
2525
2595
|
options: [
|
|
2526
2596
|
{ value: "claude-code", label: "Claude Code" },
|
|
@@ -2529,8 +2599,8 @@ async function runMigrate(cwd2, opts) {
|
|
|
2529
2599
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2530
2600
|
]
|
|
2531
2601
|
});
|
|
2532
|
-
if (
|
|
2533
|
-
|
|
2602
|
+
if (p6.isCancel(val)) {
|
|
2603
|
+
p6.cancel("Cancelled.");
|
|
2534
2604
|
process.exit(0);
|
|
2535
2605
|
}
|
|
2536
2606
|
target = val;
|
|
@@ -2539,17 +2609,17 @@ async function runMigrate(cwd2, opts) {
|
|
|
2539
2609
|
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2540
2610
|
return;
|
|
2541
2611
|
}
|
|
2542
|
-
const spinner6 =
|
|
2612
|
+
const spinner6 = p6.spinner();
|
|
2543
2613
|
spinner6.start(`Migrating from ${config.provider} to ${target}...`);
|
|
2544
2614
|
try {
|
|
2545
2615
|
const targetMaterializer = getMaterializer(target);
|
|
2546
2616
|
await targetMaterializer.build(config, cwd2);
|
|
2547
2617
|
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2548
|
-
|
|
2549
|
-
|
|
2618
|
+
p6.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2619
|
+
p6.log.warn(`Then run: ahk build`);
|
|
2550
2620
|
} catch (err) {
|
|
2551
2621
|
spinner6.stop(pc9.red("Migration failed"));
|
|
2552
|
-
|
|
2622
|
+
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
2553
2623
|
process.exit(1);
|
|
2554
2624
|
}
|
|
2555
2625
|
}
|
|
@@ -2851,7 +2921,7 @@ async function runModels(cwd2) {
|
|
|
2851
2921
|
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2852
2922
|
import { homedir as homedir4 } from "os";
|
|
2853
2923
|
import { join as join19, resolve as resolve8 } from "path";
|
|
2854
|
-
import * as
|
|
2924
|
+
import * as p7 from "@clack/prompts";
|
|
2855
2925
|
import pc12 from "picocolors";
|
|
2856
2926
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2857
2927
|
var PROVIDER_AGENT_DIRS = {
|
|
@@ -2891,11 +2961,11 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2891
2961
|
return;
|
|
2892
2962
|
}
|
|
2893
2963
|
for (const file of existingFiles) {
|
|
2894
|
-
const confirm3 = await
|
|
2964
|
+
const confirm3 = await p7.confirm({
|
|
2895
2965
|
message: `Remove ${file}?`,
|
|
2896
2966
|
initialValue: true
|
|
2897
2967
|
});
|
|
2898
|
-
if (
|
|
2968
|
+
if (p7.isCancel(confirm3)) {
|
|
2899
2969
|
console.log(pc12.red(" Cancelled by user."));
|
|
2900
2970
|
return;
|
|
2901
2971
|
}
|
|
@@ -2934,11 +3004,11 @@ async function runReset(cwd2, opts) {
|
|
|
2934
3004
|
console.log(pc12.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2935
3005
|
resetDb = false;
|
|
2936
3006
|
} else {
|
|
2937
|
-
const confirm3 = await
|
|
3007
|
+
const confirm3 = await p7.confirm({
|
|
2938
3008
|
message: `Delete database (${dbPath})?`,
|
|
2939
3009
|
initialValue: true
|
|
2940
3010
|
});
|
|
2941
|
-
if (
|
|
3011
|
+
if (p7.isCancel(confirm3)) {
|
|
2942
3012
|
console.log(pc12.red(" Cancelled by user."));
|
|
2943
3013
|
return;
|
|
2944
3014
|
}
|
|
@@ -2952,11 +3022,11 @@ async function runReset(cwd2, opts) {
|
|
|
2952
3022
|
if (opts.force) {
|
|
2953
3023
|
resetFeatureList = true;
|
|
2954
3024
|
} else {
|
|
2955
|
-
const confirm3 = await
|
|
3025
|
+
const confirm3 = await p7.confirm({
|
|
2956
3026
|
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
2957
3027
|
initialValue: true
|
|
2958
3028
|
});
|
|
2959
|
-
if (
|
|
3029
|
+
if (p7.isCancel(confirm3)) {
|
|
2960
3030
|
console.log(pc12.red(" Cancelled by user."));
|
|
2961
3031
|
return;
|
|
2962
3032
|
}
|
|
@@ -3791,15 +3861,15 @@ async function syncOut(db, cwd2, dryRun) {
|
|
|
3791
3861
|
}
|
|
3792
3862
|
|
|
3793
3863
|
// src/commands/task/add.ts
|
|
3794
|
-
import * as
|
|
3864
|
+
import * as p8 from "@clack/prompts";
|
|
3795
3865
|
import pc15 from "picocolors";
|
|
3796
3866
|
async function runTaskAdd(cwd2) {
|
|
3797
|
-
|
|
3867
|
+
p8.intro(pc15.bold("agent-harness-kit \u2014 add task"));
|
|
3798
3868
|
const title = await cliFormWithRetry(
|
|
3799
3869
|
async () => {
|
|
3800
|
-
const val = await
|
|
3801
|
-
if (
|
|
3802
|
-
|
|
3870
|
+
const val = await p8.text({ message: "Task title" });
|
|
3871
|
+
if (p8.isCancel(val)) {
|
|
3872
|
+
p8.cancel("Cancelled.");
|
|
3803
3873
|
process.exit(0);
|
|
3804
3874
|
}
|
|
3805
3875
|
return val.trim();
|
|
@@ -3808,12 +3878,12 @@ async function runTaskAdd(cwd2) {
|
|
|
3808
3878
|
);
|
|
3809
3879
|
const description = await cliFormWithRetry(
|
|
3810
3880
|
async () => {
|
|
3811
|
-
const val = await
|
|
3881
|
+
const val = await p8.text({
|
|
3812
3882
|
message: "Description (what and why)",
|
|
3813
3883
|
placeholder: "Describe the task in more detail, including any relevant context or instructions for the agents."
|
|
3814
3884
|
});
|
|
3815
|
-
if (
|
|
3816
|
-
|
|
3885
|
+
if (p8.isCancel(val)) {
|
|
3886
|
+
p8.cancel("Cancelled.");
|
|
3817
3887
|
process.exit(0);
|
|
3818
3888
|
}
|
|
3819
3889
|
return val.trim();
|
|
@@ -3821,13 +3891,13 @@ async function runTaskAdd(cwd2) {
|
|
|
3821
3891
|
taskDescriptionSchema
|
|
3822
3892
|
);
|
|
3823
3893
|
const acceptance = [];
|
|
3824
|
-
|
|
3894
|
+
p8.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
3825
3895
|
while (true) {
|
|
3826
|
-
const val = await
|
|
3827
|
-
if (
|
|
3896
|
+
const val = await p8.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
|
|
3897
|
+
if (p8.isCancel(val) || !val || !val.trim()) break;
|
|
3828
3898
|
acceptance.push(val.trim());
|
|
3829
3899
|
}
|
|
3830
|
-
const spinner6 =
|
|
3900
|
+
const spinner6 = p8.spinner();
|
|
3831
3901
|
spinner6.start("Saving...");
|
|
3832
3902
|
try {
|
|
3833
3903
|
const config = await loadConfig(cwd2);
|
|
@@ -3841,7 +3911,7 @@ async function runTaskAdd(cwd2) {
|
|
|
3841
3911
|
console.log(pc15.cyan("\u2192") + " " + pc15.cyan("ahk status") + " to see all tasks");
|
|
3842
3912
|
} catch (err) {
|
|
3843
3913
|
spinner6.stop(pc15.red("Failed"));
|
|
3844
|
-
|
|
3914
|
+
p8.log.error(err instanceof Error ? err.message : String(err));
|
|
3845
3915
|
process.exit(1);
|
|
3846
3916
|
}
|
|
3847
3917
|
}
|
|
@@ -3887,63 +3957,63 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3887
3957
|
}
|
|
3888
3958
|
|
|
3889
3959
|
// src/commands/task/edit.ts
|
|
3890
|
-
import * as
|
|
3960
|
+
import * as p9 from "@clack/prompts";
|
|
3891
3961
|
import pc17 from "picocolors";
|
|
3892
3962
|
async function runTaskEdit(cwd2) {
|
|
3893
|
-
|
|
3963
|
+
p9.intro(pc17.bold("agent-harness-kit \u2014 edit task"));
|
|
3894
3964
|
const config = await loadConfig(cwd2);
|
|
3895
3965
|
const db = await openDB(config, cwd2);
|
|
3896
3966
|
try {
|
|
3897
3967
|
const allTasks = await db.getTasks();
|
|
3898
3968
|
const activeTasks = allTasks.filter((t) => t.status !== "done");
|
|
3899
3969
|
if (activeTasks.length === 0) {
|
|
3900
|
-
|
|
3970
|
+
p9.log.error("No active tasks to edit.");
|
|
3901
3971
|
return;
|
|
3902
3972
|
}
|
|
3903
|
-
const taskId = await
|
|
3973
|
+
const taskId = await p9.select({
|
|
3904
3974
|
message: "Select a task to edit",
|
|
3905
3975
|
options: activeTasks.map((t) => ({
|
|
3906
3976
|
label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
|
|
3907
3977
|
value: t.id
|
|
3908
3978
|
}))
|
|
3909
3979
|
});
|
|
3910
|
-
if (
|
|
3911
|
-
|
|
3980
|
+
if (p9.isCancel(taskId)) {
|
|
3981
|
+
p9.cancel("Cancelled.");
|
|
3912
3982
|
process.exit(0);
|
|
3913
3983
|
}
|
|
3914
3984
|
const task2 = await db.getTaskById(taskId);
|
|
3915
3985
|
if (!task2) {
|
|
3916
|
-
|
|
3986
|
+
p9.log.error("Task not found");
|
|
3917
3987
|
process.exit(1);
|
|
3918
3988
|
}
|
|
3919
|
-
const title = await
|
|
3989
|
+
const title = await p9.text({
|
|
3920
3990
|
message: "Title",
|
|
3921
3991
|
initialValue: task2.title
|
|
3922
3992
|
});
|
|
3923
|
-
if (
|
|
3924
|
-
|
|
3993
|
+
if (p9.isCancel(title)) {
|
|
3994
|
+
p9.cancel("Cancelled.");
|
|
3925
3995
|
process.exit(0);
|
|
3926
3996
|
}
|
|
3927
|
-
const description = await
|
|
3997
|
+
const description = await p9.text({
|
|
3928
3998
|
message: "Description (what and why)",
|
|
3929
3999
|
initialValue: task2.description ?? ""
|
|
3930
4000
|
});
|
|
3931
|
-
if (
|
|
3932
|
-
|
|
4001
|
+
if (p9.isCancel(description)) {
|
|
4002
|
+
p9.cancel("Cancelled.");
|
|
3933
4003
|
process.exit(0);
|
|
3934
4004
|
}
|
|
3935
4005
|
const currentAcceptance = await db.getTaskAcceptance(task2.id);
|
|
3936
4006
|
const newAcceptance = [];
|
|
3937
|
-
|
|
4007
|
+
p9.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
|
|
3938
4008
|
for (let i = 0; i < currentAcceptance.length; i++) {
|
|
3939
4009
|
const ac = currentAcceptance[i];
|
|
3940
|
-
const val = await
|
|
4010
|
+
const val = await p9.text({
|
|
3941
4011
|
message: `#${i + 1}/${currentAcceptance.length}`,
|
|
3942
4012
|
initialValue: ac.criterion,
|
|
3943
4013
|
defaultValue: ""
|
|
3944
4014
|
});
|
|
3945
|
-
if (
|
|
3946
|
-
|
|
4015
|
+
if (p9.isCancel(val)) {
|
|
4016
|
+
p9.cancel("Cancelled.");
|
|
3947
4017
|
process.exit(0);
|
|
3948
4018
|
}
|
|
3949
4019
|
const trimmed = val.trim();
|
|
@@ -3952,11 +4022,11 @@ async function runTaskEdit(cwd2) {
|
|
|
3952
4022
|
}
|
|
3953
4023
|
}
|
|
3954
4024
|
while (true) {
|
|
3955
|
-
const val = await
|
|
3956
|
-
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;
|
|
3957
4027
|
newAcceptance.push(val.trim());
|
|
3958
4028
|
}
|
|
3959
|
-
const spinner6 =
|
|
4029
|
+
const spinner6 = p9.spinner();
|
|
3960
4030
|
spinner6.start("Saving...");
|
|
3961
4031
|
try {
|
|
3962
4032
|
const newSlug = slugify(title);
|
|
@@ -3971,7 +4041,7 @@ async function runTaskEdit(cwd2) {
|
|
|
3971
4041
|
console.log(pc17.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3972
4042
|
} catch (err) {
|
|
3973
4043
|
spinner6.stop(pc17.red("Failed"));
|
|
3974
|
-
|
|
4044
|
+
p9.log.error(err instanceof Error ? err.message : String(err));
|
|
3975
4045
|
process.exit(1);
|
|
3976
4046
|
}
|
|
3977
4047
|
} finally {
|