@cardor/agent-harness-kit 2.1.1 → 2.3.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 +14 -6
- package/dist/cli.js +213 -137
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import pc21 from "picocolors";
|
|
|
20
20
|
|
|
21
21
|
// src/commands/build.ts
|
|
22
22
|
import { watch } from "fs";
|
|
23
|
-
import * as
|
|
23
|
+
import * as p3 from "@clack/prompts";
|
|
24
24
|
import pc2 from "picocolors";
|
|
25
25
|
|
|
26
26
|
// src/core/materializer/claude-code.ts
|
|
@@ -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
|
});
|
|
@@ -1367,20 +1387,69 @@ async function promptClaudeAgentModels(provider) {
|
|
|
1367
1387
|
return claudeAgentModels;
|
|
1368
1388
|
}
|
|
1369
1389
|
|
|
1390
|
+
// src/commands/codex-model-prompt.ts
|
|
1391
|
+
import * as p2 from "@clack/prompts";
|
|
1392
|
+
var AGENT_LABELS2 = [
|
|
1393
|
+
{ key: "lead", label: "Lead" },
|
|
1394
|
+
{ key: "explorer", label: "Explorer" },
|
|
1395
|
+
{ key: "consultant", label: "Consultant" },
|
|
1396
|
+
{ key: "builder", label: "Builder" },
|
|
1397
|
+
{ key: "reviewer", label: "Reviewer" }
|
|
1398
|
+
];
|
|
1399
|
+
var CODEX_MODEL_CHOICES = [
|
|
1400
|
+
"gpt-5.6-sol",
|
|
1401
|
+
"gpt-5.6-terra",
|
|
1402
|
+
"gpt-5.6-luna",
|
|
1403
|
+
"gpt-5.5",
|
|
1404
|
+
"gpt-5.4",
|
|
1405
|
+
"gpt-5.4-mini",
|
|
1406
|
+
"gpt-5.3-codex-spark"
|
|
1407
|
+
];
|
|
1408
|
+
var CODEX_EFFORT_CHOICES = ["minimal", "low", "medium", "high", "xhigh"];
|
|
1409
|
+
async function promptCodexAgentModels(provider) {
|
|
1410
|
+
const codexAgentModels = {};
|
|
1411
|
+
if (provider !== "codex-cli") return codexAgentModels;
|
|
1412
|
+
for (const agent of AGENT_LABELS2) {
|
|
1413
|
+
const modelVal = await p2.select({
|
|
1414
|
+
message: `Model for ${agent.label}`,
|
|
1415
|
+
options: CODEX_MODEL_CHOICES.map((value) => ({ value, label: value })),
|
|
1416
|
+
initialValue: "gpt-5.6-terra"
|
|
1417
|
+
});
|
|
1418
|
+
if (p2.isCancel(modelVal)) {
|
|
1419
|
+
p2.cancel("Cancelled.");
|
|
1420
|
+
process.exit(0);
|
|
1421
|
+
}
|
|
1422
|
+
const effortVal = await p2.select({
|
|
1423
|
+
message: `Reasoning effort for ${agent.label}`,
|
|
1424
|
+
options: CODEX_EFFORT_CHOICES.map((value) => ({ value, label: value })),
|
|
1425
|
+
initialValue: "medium"
|
|
1426
|
+
});
|
|
1427
|
+
if (p2.isCancel(effortVal)) {
|
|
1428
|
+
p2.cancel("Cancelled.");
|
|
1429
|
+
process.exit(0);
|
|
1430
|
+
}
|
|
1431
|
+
codexAgentModels[agent.key] = {
|
|
1432
|
+
model: modelVal,
|
|
1433
|
+
effort: effortVal
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
return codexAgentModels;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1370
1439
|
// src/commands/build.ts
|
|
1371
1440
|
async function runBuild(cwd2, opts) {
|
|
1372
1441
|
await buildOnce(cwd2, opts.force);
|
|
1373
1442
|
if (opts.sync) {
|
|
1374
|
-
|
|
1443
|
+
p3.log.step("Syncing agent permissions...");
|
|
1375
1444
|
const config = await loadConfig(cwd2);
|
|
1376
1445
|
const materializer = getMaterializer(config.provider);
|
|
1377
1446
|
await materializer.syncPermissions(cwd2);
|
|
1378
1447
|
}
|
|
1379
1448
|
if (opts.watch) {
|
|
1380
|
-
|
|
1449
|
+
p3.log.info(`Watching agent-harness-kit.config.ts for changes...`);
|
|
1381
1450
|
watch(cwd2, { recursive: false }, async (_, filename) => {
|
|
1382
1451
|
if (filename?.startsWith("agent-harness-kit.config")) {
|
|
1383
|
-
|
|
1452
|
+
p3.log.step("Config changed \u2014 rebuilding...");
|
|
1384
1453
|
await buildOnce(cwd2, false);
|
|
1385
1454
|
}
|
|
1386
1455
|
});
|
|
@@ -1393,41 +1462,45 @@ async function buildOnce(cwd2, force) {
|
|
|
1393
1462
|
try {
|
|
1394
1463
|
config = await loadConfig(cwd2);
|
|
1395
1464
|
} catch (err) {
|
|
1396
|
-
|
|
1465
|
+
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
1397
1466
|
process.exit(1);
|
|
1398
1467
|
}
|
|
1399
1468
|
let claudeAgentModels;
|
|
1400
1469
|
if (force && config.provider === "claude-code") {
|
|
1401
1470
|
claudeAgentModels = await promptClaudeAgentModels(config.provider);
|
|
1402
1471
|
}
|
|
1403
|
-
|
|
1472
|
+
let codexAgentModels;
|
|
1473
|
+
if (force && config.provider === "codex-cli") {
|
|
1474
|
+
codexAgentModels = await promptCodexAgentModels(config.provider);
|
|
1475
|
+
}
|
|
1476
|
+
const spinner6 = p3.spinner();
|
|
1404
1477
|
spinner6.start("Rebuilding files...");
|
|
1405
1478
|
try {
|
|
1406
1479
|
const materializer = getMaterializer(config.provider);
|
|
1407
|
-
const report = await materializer.build(config, cwd2, { force, claudeAgentModels });
|
|
1480
|
+
const report = await materializer.build(config, cwd2, { force, claudeAgentModels, codexAgentModels });
|
|
1408
1481
|
spinner6.stop(pc2.green("Build complete"));
|
|
1409
1482
|
const d = report.derived;
|
|
1410
1483
|
const upToDate = [...d.created, ...d.current, ...d.propagated];
|
|
1411
1484
|
if (upToDate.length > 0) {
|
|
1412
|
-
|
|
1485
|
+
p3.log.success(upToDate.join(", "));
|
|
1413
1486
|
}
|
|
1414
1487
|
if (d.propagated.length > 0) {
|
|
1415
|
-
|
|
1488
|
+
p3.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
|
|
1416
1489
|
${d.propagated.join("\n ")}`);
|
|
1417
1490
|
}
|
|
1418
1491
|
if (d.overwritten.length > 0) {
|
|
1419
|
-
|
|
1492
|
+
p3.log.warn(
|
|
1420
1493
|
pc2.yellow(
|
|
1421
1494
|
`--force REGENERATED ${d.overwritten.length} hand-edited generated file(s), discarding your edits:
|
|
1422
1495
|
` + d.overwritten.join("\n ")
|
|
1423
1496
|
)
|
|
1424
1497
|
);
|
|
1425
1498
|
if (d.backupDir) {
|
|
1426
|
-
|
|
1499
|
+
p3.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
|
|
1427
1500
|
}
|
|
1428
1501
|
}
|
|
1429
1502
|
if (d.preserved.length > 0) {
|
|
1430
|
-
|
|
1503
|
+
p3.log.warn(
|
|
1431
1504
|
pc2.yellow(
|
|
1432
1505
|
`Left ${d.preserved.length} hand-edited generated file(s) UNTOUCHED \u2014 your edits are safe:
|
|
1433
1506
|
` + d.preserved.join("\n ") + `
|
|
@@ -1436,26 +1509,26 @@ async function buildOnce(cwd2, force) {
|
|
|
1436
1509
|
)
|
|
1437
1510
|
);
|
|
1438
1511
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1512
|
+
p3.log.success(`Agent definitions (${config.provider})`);
|
|
1513
|
+
p3.log.success("MCP config");
|
|
1441
1514
|
const { created, overwritten, preserved, backupDir } = report.agents;
|
|
1442
1515
|
if (created.length > 0) {
|
|
1443
|
-
|
|
1516
|
+
p3.log.info(`Created ${created.length} missing agent file(s):
|
|
1444
1517
|
${created.join("\n ")}`);
|
|
1445
1518
|
}
|
|
1446
1519
|
if (overwritten.length > 0) {
|
|
1447
|
-
|
|
1520
|
+
p3.log.warn(
|
|
1448
1521
|
pc2.yellow(
|
|
1449
1522
|
`--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
|
|
1450
1523
|
` + overwritten.join("\n ")
|
|
1451
1524
|
)
|
|
1452
1525
|
);
|
|
1453
1526
|
if (backupDir) {
|
|
1454
|
-
|
|
1527
|
+
p3.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
|
|
1455
1528
|
}
|
|
1456
1529
|
}
|
|
1457
1530
|
if (preserved.length > 0) {
|
|
1458
|
-
|
|
1531
|
+
p3.log.info(
|
|
1459
1532
|
`Left ${preserved.length} existing agent file(s) untouched \u2014 agent files are yours to edit.
|
|
1460
1533
|
Re-run with --force to regenerate them from the packaged templates (this DESTROYS your edits;
|
|
1461
1534
|
a backup is written first).`
|
|
@@ -1463,7 +1536,7 @@ async function buildOnce(cwd2, force) {
|
|
|
1463
1536
|
}
|
|
1464
1537
|
} catch (err) {
|
|
1465
1538
|
spinner6.stop(pc2.red("Build failed"));
|
|
1466
|
-
|
|
1539
|
+
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
1467
1540
|
process.exit(1);
|
|
1468
1541
|
}
|
|
1469
1542
|
}
|
|
@@ -2077,7 +2150,7 @@ function getProviderHealthFiles(provider) {
|
|
|
2077
2150
|
// src/commands/init.ts
|
|
2078
2151
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2079
2152
|
import { join as join16 } from "path";
|
|
2080
|
-
import * as
|
|
2153
|
+
import * as p5 from "@clack/prompts";
|
|
2081
2154
|
import pc8 from "picocolors";
|
|
2082
2155
|
|
|
2083
2156
|
// src/schema/init.ts
|
|
@@ -2116,7 +2189,7 @@ var taskDescriptionSchema = v2.pipe(
|
|
|
2116
2189
|
);
|
|
2117
2190
|
|
|
2118
2191
|
// src/utils/form.ts
|
|
2119
|
-
import * as
|
|
2192
|
+
import * as p4 from "@clack/prompts";
|
|
2120
2193
|
import * as v3 from "valibot";
|
|
2121
2194
|
var cliFormWithRetry = async (formFn, schema) => {
|
|
2122
2195
|
while (true) {
|
|
@@ -2124,8 +2197,8 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
2124
2197
|
const result = v3.safeParse(schema, res);
|
|
2125
2198
|
if (result.success) return result.output;
|
|
2126
2199
|
const messages = result.issues.map((i) => i.message).join(", ");
|
|
2127
|
-
|
|
2128
|
-
|
|
2200
|
+
p4.log.error(messages);
|
|
2201
|
+
p4.log.info("Please try again.\n");
|
|
2129
2202
|
}
|
|
2130
2203
|
};
|
|
2131
2204
|
|
|
@@ -2286,25 +2359,25 @@ async function runInit(cwd2, flags) {
|
|
|
2286
2359
|
name = flags.name;
|
|
2287
2360
|
} else {
|
|
2288
2361
|
name = await cliFormWithRetry(async () => {
|
|
2289
|
-
const val = await
|
|
2362
|
+
const val = await p5.text({
|
|
2290
2363
|
message: "Project name",
|
|
2291
2364
|
placeholder: "my-app",
|
|
2292
2365
|
...detectedName && { initialValue: detectedName }
|
|
2293
2366
|
});
|
|
2294
|
-
if (
|
|
2295
|
-
|
|
2367
|
+
if (p5.isCancel(val)) {
|
|
2368
|
+
p5.cancel("Cancelled.");
|
|
2296
2369
|
process.exit(0);
|
|
2297
2370
|
}
|
|
2298
2371
|
return val;
|
|
2299
2372
|
}, initNameSchema);
|
|
2300
2373
|
}
|
|
2301
2374
|
const description = await cliFormWithRetry(async () => {
|
|
2302
|
-
const val = await
|
|
2375
|
+
const val = await p5.text({
|
|
2303
2376
|
message: "Short description (shown to agents as context)",
|
|
2304
2377
|
placeholder: "A REST API for managing notes"
|
|
2305
2378
|
});
|
|
2306
|
-
if (
|
|
2307
|
-
|
|
2379
|
+
if (p5.isCancel(val)) {
|
|
2380
|
+
p5.cancel("Cancelled.");
|
|
2308
2381
|
process.exit(0);
|
|
2309
2382
|
}
|
|
2310
2383
|
return val;
|
|
@@ -2313,7 +2386,7 @@ async function runInit(cwd2, flags) {
|
|
|
2313
2386
|
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
2314
2387
|
provider = flags.provider;
|
|
2315
2388
|
} else {
|
|
2316
|
-
const val = await
|
|
2389
|
+
const val = await p5.select({
|
|
2317
2390
|
message: "AI provider",
|
|
2318
2391
|
options: [
|
|
2319
2392
|
{ value: "opencode", label: "OpenCode" },
|
|
@@ -2322,24 +2395,25 @@ async function runInit(cwd2, flags) {
|
|
|
2322
2395
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2323
2396
|
]
|
|
2324
2397
|
});
|
|
2325
|
-
if (
|
|
2326
|
-
|
|
2398
|
+
if (p5.isCancel(val)) {
|
|
2399
|
+
p5.cancel("Cancelled.");
|
|
2327
2400
|
process.exit(0);
|
|
2328
2401
|
}
|
|
2329
2402
|
provider = val;
|
|
2330
2403
|
}
|
|
2331
2404
|
const claudeAgentModels = await promptClaudeAgentModels(provider);
|
|
2405
|
+
const codexAgentModels = await promptCodexAgentModels(provider);
|
|
2332
2406
|
let docsPath;
|
|
2333
2407
|
if (flags.docs) {
|
|
2334
2408
|
docsPath = flags.docs;
|
|
2335
2409
|
} else {
|
|
2336
2410
|
docsPath = await cliFormWithRetry(async () => {
|
|
2337
|
-
const val = await
|
|
2411
|
+
const val = await p5.text({
|
|
2338
2412
|
message: "Docs folder path (agents will search here)",
|
|
2339
2413
|
initialValue: "./docs"
|
|
2340
2414
|
});
|
|
2341
|
-
if (
|
|
2342
|
-
|
|
2415
|
+
if (p5.isCancel(val)) {
|
|
2416
|
+
p5.cancel("Cancelled.");
|
|
2343
2417
|
process.exit(0);
|
|
2344
2418
|
}
|
|
2345
2419
|
return val;
|
|
@@ -2349,7 +2423,7 @@ async function runInit(cwd2, flags) {
|
|
|
2349
2423
|
if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
|
|
2350
2424
|
storageScope = flags.storageScope;
|
|
2351
2425
|
} else {
|
|
2352
|
-
const val = await
|
|
2426
|
+
const val = await p5.select({
|
|
2353
2427
|
message: "Storage scope",
|
|
2354
2428
|
options: [
|
|
2355
2429
|
{ value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
|
|
@@ -2360,8 +2434,8 @@ async function runInit(cwd2, flags) {
|
|
|
2360
2434
|
],
|
|
2361
2435
|
initialValue: "local"
|
|
2362
2436
|
});
|
|
2363
|
-
if (
|
|
2364
|
-
|
|
2437
|
+
if (p5.isCancel(val)) {
|
|
2438
|
+
p5.cancel("Cancelled.");
|
|
2365
2439
|
process.exit(0);
|
|
2366
2440
|
}
|
|
2367
2441
|
storageScope = val;
|
|
@@ -2370,7 +2444,7 @@ async function runInit(cwd2, flags) {
|
|
|
2370
2444
|
if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
|
|
2371
2445
|
tasksAdapter = flags.tasks;
|
|
2372
2446
|
} else {
|
|
2373
|
-
const val = await
|
|
2447
|
+
const val = await p5.select({
|
|
2374
2448
|
message: "Task adapter",
|
|
2375
2449
|
options: [
|
|
2376
2450
|
{ value: "local", label: "Local (feature_list.json)" },
|
|
@@ -2378,50 +2452,50 @@ async function runInit(cwd2, flags) {
|
|
|
2378
2452
|
{ value: "linear", label: "Linear (coming soon)" }
|
|
2379
2453
|
]
|
|
2380
2454
|
});
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2455
|
+
if (p5.isCancel(val)) {
|
|
2456
|
+
p5.cancel("Cancelled");
|
|
2383
2457
|
process.exit(0);
|
|
2384
2458
|
}
|
|
2385
2459
|
tasksAdapter = val;
|
|
2386
2460
|
}
|
|
2387
|
-
const addFirstTask = await
|
|
2388
|
-
if (
|
|
2389
|
-
|
|
2461
|
+
const addFirstTask = await p5.confirm({ message: "Add your first task now?", initialValue: false });
|
|
2462
|
+
if (p5.isCancel(addFirstTask)) {
|
|
2463
|
+
p5.cancel("Cancelled");
|
|
2390
2464
|
process.exit(0);
|
|
2391
2465
|
}
|
|
2392
2466
|
let firstTask;
|
|
2393
2467
|
if (addFirstTask) {
|
|
2394
2468
|
const taskTitle = await cliFormWithRetry(async () => {
|
|
2395
|
-
const val = await
|
|
2396
|
-
if (
|
|
2397
|
-
|
|
2469
|
+
const val = await p5.text({ message: "Task title" });
|
|
2470
|
+
if (p5.isCancel(val)) {
|
|
2471
|
+
p5.cancel("Cancelled");
|
|
2398
2472
|
process.exit(0);
|
|
2399
2473
|
}
|
|
2400
2474
|
return val.trim();
|
|
2401
2475
|
}, taskTitleSchema);
|
|
2402
2476
|
const taskDesc = await cliFormWithRetry(async () => {
|
|
2403
|
-
const val = await
|
|
2404
|
-
if (
|
|
2405
|
-
|
|
2477
|
+
const val = await p5.text({ message: "Task description", placeholder: "What and why" });
|
|
2478
|
+
if (p5.isCancel(val)) {
|
|
2479
|
+
p5.cancel("Cancelled");
|
|
2406
2480
|
process.exit(0);
|
|
2407
2481
|
}
|
|
2408
2482
|
return val.trim();
|
|
2409
2483
|
}, taskDescriptionSchema);
|
|
2410
2484
|
const acceptance = [];
|
|
2411
|
-
|
|
2485
|
+
p5.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
2412
2486
|
while (true) {
|
|
2413
|
-
const criterionVal = await
|
|
2487
|
+
const criterionVal = await p5.text({
|
|
2414
2488
|
message: ">",
|
|
2415
2489
|
placeholder: "Criterion (or press Enter to finish)"
|
|
2416
2490
|
});
|
|
2417
|
-
if (
|
|
2491
|
+
if (p5.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
|
|
2418
2492
|
acceptance.push(criterionVal.trim());
|
|
2419
2493
|
}
|
|
2420
2494
|
firstTask = { title: taskTitle, description: taskDesc, acceptance };
|
|
2421
2495
|
}
|
|
2422
2496
|
let configExt = "ts";
|
|
2423
2497
|
let featureListParseFailedPath = null;
|
|
2424
|
-
const spinner6 =
|
|
2498
|
+
const spinner6 = p5.spinner();
|
|
2425
2499
|
spinner6.start("Scaffolding...");
|
|
2426
2500
|
try {
|
|
2427
2501
|
const config = applyConfigDefaults({
|
|
@@ -2451,7 +2525,7 @@ async function runInit(cwd2, flags) {
|
|
|
2451
2525
|
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2452
2526
|
const db = await openDB(config, installDir);
|
|
2453
2527
|
await db.writeStorageState(installDir);
|
|
2454
|
-
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2528
|
+
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels, codexAgentModels });
|
|
2455
2529
|
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2456
2530
|
if (parseFailed) {
|
|
2457
2531
|
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
@@ -2460,7 +2534,7 @@ async function runInit(cwd2, flags) {
|
|
|
2460
2534
|
spinner6.stop("");
|
|
2461
2535
|
} catch (err) {
|
|
2462
2536
|
spinner6.stop("Failed");
|
|
2463
|
-
|
|
2537
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
2464
2538
|
throw err;
|
|
2465
2539
|
}
|
|
2466
2540
|
if (featureListParseFailedPath) {
|
|
@@ -2512,7 +2586,7 @@ async function runInit(cwd2, flags) {
|
|
|
2512
2586
|
}
|
|
2513
2587
|
|
|
2514
2588
|
// src/commands/migrate.ts
|
|
2515
|
-
import * as
|
|
2589
|
+
import * as p6 from "@clack/prompts";
|
|
2516
2590
|
import pc9 from "picocolors";
|
|
2517
2591
|
async function runMigrate(cwd2, opts) {
|
|
2518
2592
|
const config = await loadConfig(cwd2);
|
|
@@ -2520,7 +2594,7 @@ async function runMigrate(cwd2, opts) {
|
|
|
2520
2594
|
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2521
2595
|
target = opts.to;
|
|
2522
2596
|
} else {
|
|
2523
|
-
const val = await
|
|
2597
|
+
const val = await p6.select({
|
|
2524
2598
|
message: "Migrate to provider",
|
|
2525
2599
|
options: [
|
|
2526
2600
|
{ value: "claude-code", label: "Claude Code" },
|
|
@@ -2529,8 +2603,8 @@ async function runMigrate(cwd2, opts) {
|
|
|
2529
2603
|
{ value: "grok-cli", label: "Grok CLI" }
|
|
2530
2604
|
]
|
|
2531
2605
|
});
|
|
2532
|
-
if (
|
|
2533
|
-
|
|
2606
|
+
if (p6.isCancel(val)) {
|
|
2607
|
+
p6.cancel("Cancelled.");
|
|
2534
2608
|
process.exit(0);
|
|
2535
2609
|
}
|
|
2536
2610
|
target = val;
|
|
@@ -2539,17 +2613,19 @@ async function runMigrate(cwd2, opts) {
|
|
|
2539
2613
|
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2540
2614
|
return;
|
|
2541
2615
|
}
|
|
2542
|
-
const
|
|
2616
|
+
const claudeAgentModels = await promptClaudeAgentModels(target);
|
|
2617
|
+
const codexAgentModels = await promptCodexAgentModels(target);
|
|
2618
|
+
const spinner6 = p6.spinner();
|
|
2543
2619
|
spinner6.start(`Migrating from ${config.provider} to ${target}...`);
|
|
2544
2620
|
try {
|
|
2545
2621
|
const targetMaterializer = getMaterializer(target);
|
|
2546
|
-
await targetMaterializer.build(config, cwd2);
|
|
2622
|
+
await targetMaterializer.build(config, cwd2, { claudeAgentModels, codexAgentModels });
|
|
2547
2623
|
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2548
|
-
|
|
2549
|
-
|
|
2624
|
+
p6.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2625
|
+
p6.log.warn(`Then run: ahk build`);
|
|
2550
2626
|
} catch (err) {
|
|
2551
2627
|
spinner6.stop(pc9.red("Migration failed"));
|
|
2552
|
-
|
|
2628
|
+
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
2553
2629
|
process.exit(1);
|
|
2554
2630
|
}
|
|
2555
2631
|
}
|
|
@@ -2851,7 +2927,7 @@ async function runModels(cwd2) {
|
|
|
2851
2927
|
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2852
2928
|
import { homedir as homedir4 } from "os";
|
|
2853
2929
|
import { join as join19, resolve as resolve8 } from "path";
|
|
2854
|
-
import * as
|
|
2930
|
+
import * as p7 from "@clack/prompts";
|
|
2855
2931
|
import pc12 from "picocolors";
|
|
2856
2932
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2857
2933
|
var PROVIDER_AGENT_DIRS = {
|
|
@@ -2891,11 +2967,11 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2891
2967
|
return;
|
|
2892
2968
|
}
|
|
2893
2969
|
for (const file of existingFiles) {
|
|
2894
|
-
const confirm3 = await
|
|
2970
|
+
const confirm3 = await p7.confirm({
|
|
2895
2971
|
message: `Remove ${file}?`,
|
|
2896
2972
|
initialValue: true
|
|
2897
2973
|
});
|
|
2898
|
-
if (
|
|
2974
|
+
if (p7.isCancel(confirm3)) {
|
|
2899
2975
|
console.log(pc12.red(" Cancelled by user."));
|
|
2900
2976
|
return;
|
|
2901
2977
|
}
|
|
@@ -2934,11 +3010,11 @@ async function runReset(cwd2, opts) {
|
|
|
2934
3010
|
console.log(pc12.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2935
3011
|
resetDb = false;
|
|
2936
3012
|
} else {
|
|
2937
|
-
const confirm3 = await
|
|
3013
|
+
const confirm3 = await p7.confirm({
|
|
2938
3014
|
message: `Delete database (${dbPath})?`,
|
|
2939
3015
|
initialValue: true
|
|
2940
3016
|
});
|
|
2941
|
-
if (
|
|
3017
|
+
if (p7.isCancel(confirm3)) {
|
|
2942
3018
|
console.log(pc12.red(" Cancelled by user."));
|
|
2943
3019
|
return;
|
|
2944
3020
|
}
|
|
@@ -2952,11 +3028,11 @@ async function runReset(cwd2, opts) {
|
|
|
2952
3028
|
if (opts.force) {
|
|
2953
3029
|
resetFeatureList = true;
|
|
2954
3030
|
} else {
|
|
2955
|
-
const confirm3 = await
|
|
3031
|
+
const confirm3 = await p7.confirm({
|
|
2956
3032
|
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
2957
3033
|
initialValue: true
|
|
2958
3034
|
});
|
|
2959
|
-
if (
|
|
3035
|
+
if (p7.isCancel(confirm3)) {
|
|
2960
3036
|
console.log(pc12.red(" Cancelled by user."));
|
|
2961
3037
|
return;
|
|
2962
3038
|
}
|
|
@@ -3791,15 +3867,15 @@ async function syncOut(db, cwd2, dryRun) {
|
|
|
3791
3867
|
}
|
|
3792
3868
|
|
|
3793
3869
|
// src/commands/task/add.ts
|
|
3794
|
-
import * as
|
|
3870
|
+
import * as p8 from "@clack/prompts";
|
|
3795
3871
|
import pc15 from "picocolors";
|
|
3796
3872
|
async function runTaskAdd(cwd2) {
|
|
3797
|
-
|
|
3873
|
+
p8.intro(pc15.bold("agent-harness-kit \u2014 add task"));
|
|
3798
3874
|
const title = await cliFormWithRetry(
|
|
3799
3875
|
async () => {
|
|
3800
|
-
const val = await
|
|
3801
|
-
if (
|
|
3802
|
-
|
|
3876
|
+
const val = await p8.text({ message: "Task title" });
|
|
3877
|
+
if (p8.isCancel(val)) {
|
|
3878
|
+
p8.cancel("Cancelled.");
|
|
3803
3879
|
process.exit(0);
|
|
3804
3880
|
}
|
|
3805
3881
|
return val.trim();
|
|
@@ -3808,12 +3884,12 @@ async function runTaskAdd(cwd2) {
|
|
|
3808
3884
|
);
|
|
3809
3885
|
const description = await cliFormWithRetry(
|
|
3810
3886
|
async () => {
|
|
3811
|
-
const val = await
|
|
3887
|
+
const val = await p8.text({
|
|
3812
3888
|
message: "Description (what and why)",
|
|
3813
3889
|
placeholder: "Describe the task in more detail, including any relevant context or instructions for the agents."
|
|
3814
3890
|
});
|
|
3815
|
-
if (
|
|
3816
|
-
|
|
3891
|
+
if (p8.isCancel(val)) {
|
|
3892
|
+
p8.cancel("Cancelled.");
|
|
3817
3893
|
process.exit(0);
|
|
3818
3894
|
}
|
|
3819
3895
|
return val.trim();
|
|
@@ -3821,13 +3897,13 @@ async function runTaskAdd(cwd2) {
|
|
|
3821
3897
|
taskDescriptionSchema
|
|
3822
3898
|
);
|
|
3823
3899
|
const acceptance = [];
|
|
3824
|
-
|
|
3900
|
+
p8.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
3825
3901
|
while (true) {
|
|
3826
|
-
const val = await
|
|
3827
|
-
if (
|
|
3902
|
+
const val = await p8.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
|
|
3903
|
+
if (p8.isCancel(val) || !val || !val.trim()) break;
|
|
3828
3904
|
acceptance.push(val.trim());
|
|
3829
3905
|
}
|
|
3830
|
-
const spinner6 =
|
|
3906
|
+
const spinner6 = p8.spinner();
|
|
3831
3907
|
spinner6.start("Saving...");
|
|
3832
3908
|
try {
|
|
3833
3909
|
const config = await loadConfig(cwd2);
|
|
@@ -3841,7 +3917,7 @@ async function runTaskAdd(cwd2) {
|
|
|
3841
3917
|
console.log(pc15.cyan("\u2192") + " " + pc15.cyan("ahk status") + " to see all tasks");
|
|
3842
3918
|
} catch (err) {
|
|
3843
3919
|
spinner6.stop(pc15.red("Failed"));
|
|
3844
|
-
|
|
3920
|
+
p8.log.error(err instanceof Error ? err.message : String(err));
|
|
3845
3921
|
process.exit(1);
|
|
3846
3922
|
}
|
|
3847
3923
|
}
|
|
@@ -3887,63 +3963,63 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3887
3963
|
}
|
|
3888
3964
|
|
|
3889
3965
|
// src/commands/task/edit.ts
|
|
3890
|
-
import * as
|
|
3966
|
+
import * as p9 from "@clack/prompts";
|
|
3891
3967
|
import pc17 from "picocolors";
|
|
3892
3968
|
async function runTaskEdit(cwd2) {
|
|
3893
|
-
|
|
3969
|
+
p9.intro(pc17.bold("agent-harness-kit \u2014 edit task"));
|
|
3894
3970
|
const config = await loadConfig(cwd2);
|
|
3895
3971
|
const db = await openDB(config, cwd2);
|
|
3896
3972
|
try {
|
|
3897
3973
|
const allTasks = await db.getTasks();
|
|
3898
3974
|
const activeTasks = allTasks.filter((t) => t.status !== "done");
|
|
3899
3975
|
if (activeTasks.length === 0) {
|
|
3900
|
-
|
|
3976
|
+
p9.log.error("No active tasks to edit.");
|
|
3901
3977
|
return;
|
|
3902
3978
|
}
|
|
3903
|
-
const taskId = await
|
|
3979
|
+
const taskId = await p9.select({
|
|
3904
3980
|
message: "Select a task to edit",
|
|
3905
3981
|
options: activeTasks.map((t) => ({
|
|
3906
3982
|
label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
|
|
3907
3983
|
value: t.id
|
|
3908
3984
|
}))
|
|
3909
3985
|
});
|
|
3910
|
-
if (
|
|
3911
|
-
|
|
3986
|
+
if (p9.isCancel(taskId)) {
|
|
3987
|
+
p9.cancel("Cancelled.");
|
|
3912
3988
|
process.exit(0);
|
|
3913
3989
|
}
|
|
3914
3990
|
const task2 = await db.getTaskById(taskId);
|
|
3915
3991
|
if (!task2) {
|
|
3916
|
-
|
|
3992
|
+
p9.log.error("Task not found");
|
|
3917
3993
|
process.exit(1);
|
|
3918
3994
|
}
|
|
3919
|
-
const title = await
|
|
3995
|
+
const title = await p9.text({
|
|
3920
3996
|
message: "Title",
|
|
3921
3997
|
initialValue: task2.title
|
|
3922
3998
|
});
|
|
3923
|
-
if (
|
|
3924
|
-
|
|
3999
|
+
if (p9.isCancel(title)) {
|
|
4000
|
+
p9.cancel("Cancelled.");
|
|
3925
4001
|
process.exit(0);
|
|
3926
4002
|
}
|
|
3927
|
-
const description = await
|
|
4003
|
+
const description = await p9.text({
|
|
3928
4004
|
message: "Description (what and why)",
|
|
3929
4005
|
initialValue: task2.description ?? ""
|
|
3930
4006
|
});
|
|
3931
|
-
if (
|
|
3932
|
-
|
|
4007
|
+
if (p9.isCancel(description)) {
|
|
4008
|
+
p9.cancel("Cancelled.");
|
|
3933
4009
|
process.exit(0);
|
|
3934
4010
|
}
|
|
3935
4011
|
const currentAcceptance = await db.getTaskAcceptance(task2.id);
|
|
3936
4012
|
const newAcceptance = [];
|
|
3937
|
-
|
|
4013
|
+
p9.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
|
|
3938
4014
|
for (let i = 0; i < currentAcceptance.length; i++) {
|
|
3939
4015
|
const ac = currentAcceptance[i];
|
|
3940
|
-
const val = await
|
|
4016
|
+
const val = await p9.text({
|
|
3941
4017
|
message: `#${i + 1}/${currentAcceptance.length}`,
|
|
3942
4018
|
initialValue: ac.criterion,
|
|
3943
4019
|
defaultValue: ""
|
|
3944
4020
|
});
|
|
3945
|
-
if (
|
|
3946
|
-
|
|
4021
|
+
if (p9.isCancel(val)) {
|
|
4022
|
+
p9.cancel("Cancelled.");
|
|
3947
4023
|
process.exit(0);
|
|
3948
4024
|
}
|
|
3949
4025
|
const trimmed = val.trim();
|
|
@@ -3952,11 +4028,11 @@ async function runTaskEdit(cwd2) {
|
|
|
3952
4028
|
}
|
|
3953
4029
|
}
|
|
3954
4030
|
while (true) {
|
|
3955
|
-
const val = await
|
|
3956
|
-
if (
|
|
4031
|
+
const val = await p9.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
|
|
4032
|
+
if (p9.isCancel(val) || !val || !val.trim()) break;
|
|
3957
4033
|
newAcceptance.push(val.trim());
|
|
3958
4034
|
}
|
|
3959
|
-
const spinner6 =
|
|
4035
|
+
const spinner6 = p9.spinner();
|
|
3960
4036
|
spinner6.start("Saving...");
|
|
3961
4037
|
try {
|
|
3962
4038
|
const newSlug = slugify(title);
|
|
@@ -3971,7 +4047,7 @@ async function runTaskEdit(cwd2) {
|
|
|
3971
4047
|
console.log(pc17.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3972
4048
|
} catch (err) {
|
|
3973
4049
|
spinner6.stop(pc17.red("Failed"));
|
|
3974
|
-
|
|
4050
|
+
p9.log.error(err instanceof Error ? err.message : String(err));
|
|
3975
4051
|
process.exit(1);
|
|
3976
4052
|
}
|
|
3977
4053
|
} finally {
|