harnessed 3.3.1 → 3.4.1
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 +207 -198
- package/dist/cli.mjs +471 -140
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/workflows/capabilities.yaml +64 -0
- package/workflows/disciplines/language.yaml +33 -13
- package/workflows/discuss/phase/SKILL.md +8 -0
- package/workflows/discuss/strategic/SKILL.md +8 -0
- package/workflows/discuss/subtask/SKILL.md +8 -0
- package/workflows/plan/architecture/SKILL.md +8 -0
- package/workflows/plan/phase/SKILL.md +8 -0
- package/workflows/retro/SKILL.md +8 -0
- package/workflows/task/clarify/SKILL.md +8 -0
- package/workflows/task/code/SKILL.md +8 -0
- package/workflows/task/deliver/SKILL.md +8 -0
- package/workflows/task/test/SKILL.md +8 -0
- package/workflows/verify/code-review/SKILL.md +8 -0
- package/workflows/verify/design/SKILL.md +8 -0
- package/workflows/verify/multispec/SKILL.md +8 -0
- package/workflows/verify/paranoid/SKILL.md +8 -0
- package/workflows/verify/progress/SKILL.md +8 -0
- package/workflows/verify/qa/SKILL.md +8 -0
- package/workflows/verify/security/SKILL.md +8 -0
- package/workflows/verify/simplify/SKILL.md +8 -0
package/dist/cli.mjs
CHANGED
|
@@ -11,12 +11,12 @@ import lockfile from 'proper-lockfile';
|
|
|
11
11
|
import { Command } from 'commander';
|
|
12
12
|
import { Ajv } from 'ajv';
|
|
13
13
|
import * as ajvFormatsNs from 'ajv-formats';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
14
15
|
import { createHash } from 'crypto';
|
|
15
16
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
16
17
|
import * as p from '@clack/prompts';
|
|
17
18
|
import { createPatch } from 'diff';
|
|
18
19
|
import pc from 'picocolors';
|
|
19
|
-
import { fileURLToPath } from 'url';
|
|
20
20
|
import { stdout, stdin } from 'process';
|
|
21
21
|
import * as readline from 'readline/promises';
|
|
22
22
|
|
|
@@ -498,7 +498,7 @@ function checkTokenBudget() {
|
|
|
498
498
|
const msg = `${items.length} skill(s) total ${total} tokens (under 1% / 2000 threshold)`;
|
|
499
499
|
return { name: "token budget", status: "pass", message: msg };
|
|
500
500
|
}
|
|
501
|
-
const top = [...items].sort((a, b) => b.tokens - a.tokens).slice(0, 3).map((
|
|
501
|
+
const top = [...items].sort((a, b) => b.tokens - a.tokens).slice(0, 3).map((t2) => `${t2.name}:${t2.tokens}`).join(", ");
|
|
502
502
|
const pct = (total / CONTEXT_WINDOW_TOKENS * 100).toFixed(2);
|
|
503
503
|
const message = `${items.length} skill(s) total ${total} tokens (${pct}% of 200000) \u2014 top: ${top}`;
|
|
504
504
|
return {
|
|
@@ -847,7 +847,7 @@ var init_resume = __esm({
|
|
|
847
847
|
|
|
848
848
|
// package.json
|
|
849
849
|
var package_default = {
|
|
850
|
-
version: "3.
|
|
850
|
+
version: "3.4.1"};
|
|
851
851
|
|
|
852
852
|
// src/manifest/errors.ts
|
|
853
853
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1600,6 +1600,71 @@ audited ${yamls.length} manifest${yamls.length === 1 ? "" : "s"} \u2014 ${findin
|
|
|
1600
1600
|
process.exit(errorCount > 0 ? 1 : 0);
|
|
1601
1601
|
});
|
|
1602
1602
|
}
|
|
1603
|
+
var SUPPORTED = /* @__PURE__ */ new Set(["en", "zh-Hans"]);
|
|
1604
|
+
var currentLocale = null;
|
|
1605
|
+
var cache = {};
|
|
1606
|
+
function mapToSupported(raw) {
|
|
1607
|
+
if (!raw) return "en";
|
|
1608
|
+
if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
|
|
1609
|
+
return "en";
|
|
1610
|
+
}
|
|
1611
|
+
function detectLocale() {
|
|
1612
|
+
const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale();
|
|
1613
|
+
return mapToSupported(raw);
|
|
1614
|
+
}
|
|
1615
|
+
function safeIntlLocale() {
|
|
1616
|
+
try {
|
|
1617
|
+
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
1618
|
+
} catch {
|
|
1619
|
+
return void 0;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function messagesDir() {
|
|
1623
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1624
|
+
const candidates = [resolve(here, "..", "..", "messages"), resolve(here, "..", "messages")];
|
|
1625
|
+
for (const c of candidates) {
|
|
1626
|
+
try {
|
|
1627
|
+
readFileSync(join(c, "en.json"), "utf8");
|
|
1628
|
+
return c;
|
|
1629
|
+
} catch {
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return resolve(process.cwd(), "messages");
|
|
1633
|
+
}
|
|
1634
|
+
function loadLocale(locale) {
|
|
1635
|
+
if (cache[locale]) return cache[locale];
|
|
1636
|
+
const path = join(messagesDir(), `${locale}.json`);
|
|
1637
|
+
try {
|
|
1638
|
+
const raw = readFileSync(path, "utf8");
|
|
1639
|
+
cache[locale] = JSON.parse(raw);
|
|
1640
|
+
} catch {
|
|
1641
|
+
cache[locale] = {};
|
|
1642
|
+
}
|
|
1643
|
+
return cache[locale];
|
|
1644
|
+
}
|
|
1645
|
+
function setLocale(locale) {
|
|
1646
|
+
if (!locale) return;
|
|
1647
|
+
const mapped = mapToSupported(locale);
|
|
1648
|
+
if (SUPPORTED.has(mapped)) currentLocale = mapped;
|
|
1649
|
+
}
|
|
1650
|
+
function getLocale() {
|
|
1651
|
+
if (currentLocale === null) currentLocale = detectLocale();
|
|
1652
|
+
return currentLocale;
|
|
1653
|
+
}
|
|
1654
|
+
function t(key, params) {
|
|
1655
|
+
const locale = getLocale();
|
|
1656
|
+
const primary = loadLocale(locale);
|
|
1657
|
+
let template = primary[key];
|
|
1658
|
+
if (template === void 0 && locale !== "en") {
|
|
1659
|
+
template = loadLocale("en")[key];
|
|
1660
|
+
}
|
|
1661
|
+
if (template === void 0) template = key;
|
|
1662
|
+
if (!params) return template;
|
|
1663
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
1664
|
+
const v = params[name];
|
|
1665
|
+
return v === void 0 ? `{{${name}}}` : String(v);
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1603
1668
|
|
|
1604
1669
|
// src/cli/audit-log.ts
|
|
1605
1670
|
init_harnessedRoot();
|
|
@@ -1633,7 +1698,7 @@ function renderHumanTable(records) {
|
|
|
1633
1698
|
}
|
|
1634
1699
|
}
|
|
1635
1700
|
function pipeToJq(filterExpr, lines) {
|
|
1636
|
-
return new Promise((
|
|
1701
|
+
return new Promise((resolve12, reject) => {
|
|
1637
1702
|
const child = spawn("jq", [filterExpr], {
|
|
1638
1703
|
stdio: ["pipe", "inherit", "inherit"],
|
|
1639
1704
|
windowsHide: true
|
|
@@ -1641,13 +1706,13 @@ function pipeToJq(filterExpr, lines) {
|
|
|
1641
1706
|
child.on("error", (err2) => {
|
|
1642
1707
|
const e = err2;
|
|
1643
1708
|
if (e.code === "ENOENT") {
|
|
1644
|
-
console.error("
|
|
1645
|
-
|
|
1709
|
+
console.error(t("audit_log.jq_missing"));
|
|
1710
|
+
resolve12(1);
|
|
1646
1711
|
} else {
|
|
1647
1712
|
reject(err2);
|
|
1648
1713
|
}
|
|
1649
1714
|
});
|
|
1650
|
-
child.on("close", (code) =>
|
|
1715
|
+
child.on("close", (code) => resolve12(code ?? 0));
|
|
1651
1716
|
child.stdin.write(lines.join("\n"));
|
|
1652
1717
|
child.stdin.end();
|
|
1653
1718
|
});
|
|
@@ -1659,23 +1724,23 @@ function registerAuditLog(program2) {
|
|
|
1659
1724
|
async (opts) => {
|
|
1660
1725
|
const tailN = opts.tail !== void 0 ? Number(opts.tail) : 50;
|
|
1661
1726
|
if (Number.isNaN(tailN) || tailN < 1) {
|
|
1662
|
-
console.error("
|
|
1727
|
+
console.error(t("audit_log.tail_invalid"));
|
|
1663
1728
|
process.exit(2);
|
|
1664
1729
|
}
|
|
1665
1730
|
const headN = opts.head !== void 0 ? Number(opts.head) : void 0;
|
|
1666
1731
|
if (headN !== void 0 && (Number.isNaN(headN) || headN < 1)) {
|
|
1667
|
-
console.error("
|
|
1732
|
+
console.error(t("audit_log.head_invalid"));
|
|
1668
1733
|
process.exit(2);
|
|
1669
1734
|
}
|
|
1670
1735
|
const path = auditPath();
|
|
1671
1736
|
if (!existsSync(path)) {
|
|
1672
|
-
console.log(
|
|
1737
|
+
console.log(t("audit_log.no_records_file", { path }));
|
|
1673
1738
|
process.exit(0);
|
|
1674
1739
|
}
|
|
1675
1740
|
const raw = readFileSync(path, "utf8");
|
|
1676
1741
|
const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1677
1742
|
if (lines.length === 0) {
|
|
1678
|
-
console.log("
|
|
1743
|
+
console.log(t("audit_log.no_records_empty"));
|
|
1679
1744
|
process.exit(0);
|
|
1680
1745
|
}
|
|
1681
1746
|
let records = [];
|
|
@@ -1832,11 +1897,11 @@ function registerBackupList(program2) {
|
|
|
1832
1897
|
try {
|
|
1833
1898
|
dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
1834
1899
|
} catch {
|
|
1835
|
-
console.log(
|
|
1900
|
+
console.log(t("backup.no_backups", { root }));
|
|
1836
1901
|
return;
|
|
1837
1902
|
}
|
|
1838
1903
|
if (dirs.length === 0) {
|
|
1839
|
-
console.log(
|
|
1904
|
+
console.log(t("backup.no_backups_empty", { root }));
|
|
1840
1905
|
return;
|
|
1841
1906
|
}
|
|
1842
1907
|
for (const ts of dirs) {
|
|
@@ -1851,8 +1916,7 @@ function registerBackupList(program2) {
|
|
|
1851
1916
|
console.log(`${ts} (metadata.json missing or unreadable)`);
|
|
1852
1917
|
}
|
|
1853
1918
|
}
|
|
1854
|
-
console.log(
|
|
1855
|
-
${dirs.length} snapshot${dirs.length === 1 ? "" : "s"} total`);
|
|
1919
|
+
console.log(t("backup.total_snapshots", { count: dirs.length }));
|
|
1856
1920
|
});
|
|
1857
1921
|
}
|
|
1858
1922
|
function checkNodeVersion() {
|
|
@@ -1998,7 +2062,7 @@ function registerDoctor(program2) {
|
|
|
1998
2062
|
if (r.status !== "pass" && r.fix) console.log(` fix: ${r.fix}`);
|
|
1999
2063
|
}
|
|
2000
2064
|
console.log(
|
|
2001
|
-
hasFail ? "
|
|
2065
|
+
hasFail ? t("doctor.summary.fail") : hasWarn ? t("doctor.summary.warn") : t("doctor.summary.pass")
|
|
2002
2066
|
);
|
|
2003
2067
|
}
|
|
2004
2068
|
process.exit(hasFail ? 1 : 0);
|
|
@@ -3005,7 +3069,7 @@ function registerExecuteTask(program2) {
|
|
|
3005
3069
|
"Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
3006
3070
|
).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
|
|
3007
3071
|
if (!raw.task) {
|
|
3008
|
-
console.error("
|
|
3072
|
+
console.error(t("execute_task.require_task"));
|
|
3009
3073
|
process.exit(2);
|
|
3010
3074
|
}
|
|
3011
3075
|
const workflowName = raw.workflow ?? "execute-task";
|
|
@@ -3014,7 +3078,10 @@ function registerExecuteTask(program2) {
|
|
|
3014
3078
|
phases = loadPhases(`workflows/${workflowName}/phases.yaml`);
|
|
3015
3079
|
} catch (error) {
|
|
3016
3080
|
console.error(
|
|
3017
|
-
|
|
3081
|
+
t("execute_task.load_phases_failed", {
|
|
3082
|
+
workflow: workflowName,
|
|
3083
|
+
message: error.message
|
|
3084
|
+
})
|
|
3018
3085
|
);
|
|
3019
3086
|
process.exit(2);
|
|
3020
3087
|
}
|
|
@@ -3053,9 +3120,7 @@ function registerExecuteTask(program2) {
|
|
|
3053
3120
|
// apply-immediate default
|
|
3054
3121
|
});
|
|
3055
3122
|
} catch (err2) {
|
|
3056
|
-
console.warn(
|
|
3057
|
-
`\u26A0\uFE0F before-commit pre-flight skipped (${err2.message}); subagent will biome-check at commit time.`
|
|
3058
|
-
);
|
|
3123
|
+
console.warn(t("execute_task.precommit_skipped", { message: err2.message }));
|
|
3059
3124
|
}
|
|
3060
3125
|
const result = await runRouting(taskCtx, {
|
|
3061
3126
|
maxIterations: raw.maxIterations ?? 20,
|
|
@@ -3064,7 +3129,7 @@ function registerExecuteTask(program2) {
|
|
|
3064
3129
|
...fallbackPhaseId ? { fallbackPhaseId } : {}
|
|
3065
3130
|
});
|
|
3066
3131
|
if ("aborted" in result) {
|
|
3067
|
-
console.error(
|
|
3132
|
+
console.error(t("install.aborted", { reason: result.reason }));
|
|
3068
3133
|
process.exit(2);
|
|
3069
3134
|
}
|
|
3070
3135
|
if ("ok" in result && result.ok === false) {
|
|
@@ -3110,8 +3175,8 @@ function registerGc(program2) {
|
|
|
3110
3175
|
const olderMs = parseDuration(opts.olderThan ?? "30d");
|
|
3111
3176
|
if (olderMs == null) {
|
|
3112
3177
|
console.error(
|
|
3113
|
-
|
|
3114
|
-
|
|
3178
|
+
`${t("gc.invalid_duration", { value: opts.olderThan ?? "" })}
|
|
3179
|
+
${t("gc.invalid_duration.fix")}`
|
|
3115
3180
|
);
|
|
3116
3181
|
process.exit(1);
|
|
3117
3182
|
return;
|
|
@@ -3122,7 +3187,7 @@ function registerGc(program2) {
|
|
|
3122
3187
|
try {
|
|
3123
3188
|
dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
3124
3189
|
} catch {
|
|
3125
|
-
console.log(
|
|
3190
|
+
console.log(t("gc.no_backups", { root }));
|
|
3126
3191
|
return;
|
|
3127
3192
|
}
|
|
3128
3193
|
const cutoff = Date.now() - olderMs;
|
|
@@ -3132,8 +3197,8 @@ function registerGc(program2) {
|
|
|
3132
3197
|
if (kept.has(ts)) continue;
|
|
3133
3198
|
const path = join(root, ts);
|
|
3134
3199
|
const iso = ts.replace(/T(\d{2})-(\d{2})-(\d{2})/, "T$1:$2:$3");
|
|
3135
|
-
const
|
|
3136
|
-
if (Number.isNaN(
|
|
3200
|
+
const t2 = Date.parse(iso);
|
|
3201
|
+
if (Number.isNaN(t2) || t2 > cutoff) continue;
|
|
3137
3202
|
let manifest = "(unknown)";
|
|
3138
3203
|
try {
|
|
3139
3204
|
const meta = JSON.parse(
|
|
@@ -3146,17 +3211,19 @@ function registerGc(program2) {
|
|
|
3146
3211
|
candidates.push({ ts, path, manifest, sizeKb });
|
|
3147
3212
|
}
|
|
3148
3213
|
if (candidates.length === 0) {
|
|
3149
|
-
console.log(
|
|
3214
|
+
console.log(
|
|
3215
|
+
t("gc.no_old_snapshots", { cutoff: opts.olderThan ?? "", keptCount: kept.size })
|
|
3216
|
+
);
|
|
3150
3217
|
return;
|
|
3151
3218
|
}
|
|
3152
3219
|
const totalKb = candidates.reduce((a, c) => a + c.sizeKb, 0);
|
|
3153
|
-
const
|
|
3154
|
-
console.log(
|
|
3220
|
+
const key = dryRun ? "gc.summary_will_delete" : "gc.summary_deleting";
|
|
3221
|
+
console.log(t(key, { count: candidates.length, sizeKb: totalKb }));
|
|
3155
3222
|
for (const c of candidates) {
|
|
3156
3223
|
console.log(` ${c.ts} ${c.manifest} (${c.sizeKb} KB)`);
|
|
3157
3224
|
if (!dryRun) await rm(c.path, { recursive: true, force: true });
|
|
3158
3225
|
}
|
|
3159
|
-
if (dryRun) console.log(
|
|
3226
|
+
if (dryRun) console.log(t("gc.dry_run_rerun_hint"));
|
|
3160
3227
|
});
|
|
3161
3228
|
}
|
|
3162
3229
|
async function confirmAt(level, ctx) {
|
|
@@ -3364,10 +3431,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3364
3431
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
|
|
3365
3432
|
return { ok: false, phase: "preflight", error: e };
|
|
3366
3433
|
}
|
|
3367
|
-
const
|
|
3434
|
+
const settingsPath3 = join(homedir(), ".claude", "settings.json");
|
|
3368
3435
|
let existing;
|
|
3369
3436
|
try {
|
|
3370
|
-
existing = await readFile(
|
|
3437
|
+
existing = await readFile(settingsPath3, "utf8");
|
|
3371
3438
|
} catch {
|
|
3372
3439
|
existing = null;
|
|
3373
3440
|
}
|
|
@@ -3398,7 +3465,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3398
3465
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
3399
3466
|
`;
|
|
3400
3467
|
const plan = {
|
|
3401
|
-
files: [{ target:
|
|
3468
|
+
files: [{ target: settingsPath3, scope: "HOME", oldText: existing ?? "", newText }]
|
|
3402
3469
|
};
|
|
3403
3470
|
process.stdout.write(renderDiff(plan, ctx));
|
|
3404
3471
|
const conf = await confirmAt("L3", { ...ctx});
|
|
@@ -3409,10 +3476,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3409
3476
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3410
3477
|
const bk = await backup(plan, ctx);
|
|
3411
3478
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3412
|
-
await writeFile(
|
|
3479
|
+
await writeFile(settingsPath3, newText);
|
|
3413
3480
|
let verify;
|
|
3414
3481
|
try {
|
|
3415
|
-
verify = JSON.parse(await readFile(
|
|
3482
|
+
verify = JSON.parse(await readFile(settingsPath3, "utf8"));
|
|
3416
3483
|
} catch (e) {
|
|
3417
3484
|
return {
|
|
3418
3485
|
ok: false,
|
|
@@ -3440,7 +3507,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3440
3507
|
};
|
|
3441
3508
|
}
|
|
3442
3509
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
3443
|
-
return { ok: true, backupId: bk.backupId, appliedFiles: [
|
|
3510
|
+
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
|
|
3444
3511
|
};
|
|
3445
3512
|
function getUserClaudeJsonPath() {
|
|
3446
3513
|
return join(homedir(), ".claude.json");
|
|
@@ -3476,7 +3543,7 @@ async function isPluginRegistered(pluginName) {
|
|
|
3476
3543
|
return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
|
|
3477
3544
|
}
|
|
3478
3545
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
3479
|
-
return new Promise((
|
|
3546
|
+
return new Promise((resolve12) => {
|
|
3480
3547
|
const isWin = process.platform === "win32";
|
|
3481
3548
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
3482
3549
|
let stderr = "";
|
|
@@ -3485,15 +3552,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
3485
3552
|
});
|
|
3486
3553
|
const timer = setTimeout(() => {
|
|
3487
3554
|
child.kill("SIGKILL");
|
|
3488
|
-
|
|
3555
|
+
resolve12({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
3489
3556
|
}, timeoutMs);
|
|
3490
3557
|
child.on("error", (e) => {
|
|
3491
3558
|
clearTimeout(timer);
|
|
3492
|
-
|
|
3559
|
+
resolve12({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
3493
3560
|
});
|
|
3494
3561
|
child.on("close", (code) => {
|
|
3495
3562
|
clearTimeout(timer);
|
|
3496
|
-
|
|
3563
|
+
resolve12({ exitCode: code ?? -1, stderr });
|
|
3497
3564
|
});
|
|
3498
3565
|
});
|
|
3499
3566
|
}
|
|
@@ -3672,10 +3739,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3672
3739
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
3673
3740
|
stderr += chunk;
|
|
3674
3741
|
});
|
|
3675
|
-
return await new Promise((
|
|
3742
|
+
return await new Promise((resolve12) => {
|
|
3676
3743
|
const timer = setTimeout(() => {
|
|
3677
3744
|
child.kill("SIGKILL");
|
|
3678
|
-
|
|
3745
|
+
resolve12({
|
|
3679
3746
|
ok: false,
|
|
3680
3747
|
phase: "spawn",
|
|
3681
3748
|
error: {
|
|
@@ -3690,7 +3757,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3690
3757
|
}, effectiveTimeoutMs);
|
|
3691
3758
|
child.on("error", (err2) => {
|
|
3692
3759
|
clearTimeout(timer);
|
|
3693
|
-
|
|
3760
|
+
resolve12({
|
|
3694
3761
|
ok: false,
|
|
3695
3762
|
phase: "spawn",
|
|
3696
3763
|
error: {
|
|
@@ -3705,14 +3772,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3705
3772
|
});
|
|
3706
3773
|
child.on("close", (code) => {
|
|
3707
3774
|
clearTimeout(timer);
|
|
3708
|
-
|
|
3775
|
+
resolve12({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
3709
3776
|
});
|
|
3710
3777
|
});
|
|
3711
3778
|
}
|
|
3712
3779
|
|
|
3713
3780
|
// src/installers/gitCloneWithSetup.ts
|
|
3714
3781
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
3715
|
-
return new Promise((
|
|
3782
|
+
return new Promise((resolve12) => {
|
|
3716
3783
|
const isWin = process.platform === "win32";
|
|
3717
3784
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
3718
3785
|
let stdout2 = "";
|
|
@@ -3721,15 +3788,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
3721
3788
|
});
|
|
3722
3789
|
const timer = setTimeout(() => {
|
|
3723
3790
|
child.kill("SIGKILL");
|
|
3724
|
-
|
|
3791
|
+
resolve12({ sha: "", exit: -1 });
|
|
3725
3792
|
}, timeoutMs);
|
|
3726
3793
|
child.on("error", () => {
|
|
3727
3794
|
clearTimeout(timer);
|
|
3728
|
-
|
|
3795
|
+
resolve12({ sha: "", exit: -1 });
|
|
3729
3796
|
});
|
|
3730
3797
|
child.on("close", (code) => {
|
|
3731
3798
|
clearTimeout(timer);
|
|
3732
|
-
|
|
3799
|
+
resolve12({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
3733
3800
|
});
|
|
3734
3801
|
});
|
|
3735
3802
|
}
|
|
@@ -3740,11 +3807,11 @@ function extractCloneTarget(cmd) {
|
|
|
3740
3807
|
const tokens = tail.split(/\s+/);
|
|
3741
3808
|
let i = 0;
|
|
3742
3809
|
while (i < tokens.length) {
|
|
3743
|
-
const
|
|
3744
|
-
if (
|
|
3745
|
-
if (
|
|
3810
|
+
const t2 = tokens[i];
|
|
3811
|
+
if (t2 === void 0 || !t2.startsWith("-")) break;
|
|
3812
|
+
if (t2 === "--depth" || t2 === "--branch" || t2 === "-b") {
|
|
3746
3813
|
i += 2;
|
|
3747
|
-
} else if (
|
|
3814
|
+
} else if (t2.includes("=")) {
|
|
3748
3815
|
i += 1;
|
|
3749
3816
|
} else {
|
|
3750
3817
|
i += 2;
|
|
@@ -4479,8 +4546,8 @@ function registerInstall(program2) {
|
|
|
4479
4546
|
chosenPath = skillPackPath;
|
|
4480
4547
|
} catch {
|
|
4481
4548
|
console.error(
|
|
4482
|
-
|
|
4483
|
-
|
|
4549
|
+
`${t("install.manifest_not_found", { name: resolvedName })}
|
|
4550
|
+
${t("install.manifest_not_found.fix", { name: resolvedName })}`
|
|
4484
4551
|
);
|
|
4485
4552
|
process.exit(1);
|
|
4486
4553
|
}
|
|
@@ -4488,7 +4555,7 @@ function registerInstall(program2) {
|
|
|
4488
4555
|
const v = validateManifestFile(yamlSrc, chosenPath);
|
|
4489
4556
|
if (!v.ok) {
|
|
4490
4557
|
for (const e of v.errors) console.error(`error: ${e.message} at ${e.path}`);
|
|
4491
|
-
console.error(
|
|
4558
|
+
console.error(t("install.audit_hint"));
|
|
4492
4559
|
process.exit(1);
|
|
4493
4560
|
}
|
|
4494
4561
|
const dryRun = raw.dryRun === true;
|
|
@@ -4510,12 +4577,14 @@ function registerInstall(program2) {
|
|
|
4510
4577
|
}
|
|
4511
4578
|
const result = await runInstall(v.manifest, opts);
|
|
4512
4579
|
if ("aborted" in result) {
|
|
4513
|
-
console.error(
|
|
4580
|
+
console.error(t("install.aborted", { reason: result.reason }));
|
|
4514
4581
|
process.exit(2);
|
|
4515
4582
|
}
|
|
4516
4583
|
if (result.ok) {
|
|
4517
4584
|
const version = v.manifest.spec.install.method === "npm-cli" && "npm_version" in v.manifest.spec.install ? v.manifest.spec.install.npm_version : "";
|
|
4518
|
-
console.log(
|
|
4585
|
+
console.log(
|
|
4586
|
+
version ? t("install.success_with_version", { name: v.manifest.metadata.name, version }) : t("install.success", { name: v.manifest.metadata.name })
|
|
4587
|
+
);
|
|
4519
4588
|
process.exit(0);
|
|
4520
4589
|
}
|
|
4521
4590
|
console.error(formatError(result.error));
|
|
@@ -4613,10 +4682,8 @@ function registerManifestAdd(program2) {
|
|
|
4613
4682
|
const category = raw.category ?? "skill-packs";
|
|
4614
4683
|
const outPath = `manifests/${category}/${name}.ee5-answers.json`;
|
|
4615
4684
|
if (raw.nonInteractive) {
|
|
4616
|
-
console.warn(
|
|
4617
|
-
|
|
4618
|
-
);
|
|
4619
|
-
console.log(`[manifest-add] dry-run preview for upstream: ${upstream} \u2192 ${outPath}`);
|
|
4685
|
+
console.warn(t("manifest_add.non_interactive_warn"));
|
|
4686
|
+
console.log(t("manifest_add.dry_run_preview", { upstream, path: outPath }));
|
|
4620
4687
|
process.exit(0);
|
|
4621
4688
|
}
|
|
4622
4689
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
@@ -4629,7 +4696,7 @@ function registerManifestAdd(program2) {
|
|
|
4629
4696
|
const a = (await rl.question(`${q}
|
|
4630
4697
|
> `)).trim();
|
|
4631
4698
|
if (!a) {
|
|
4632
|
-
console.error("
|
|
4699
|
+
console.error(t("manifest_add.empty_answer"));
|
|
4633
4700
|
rl.close();
|
|
4634
4701
|
process.exit(1);
|
|
4635
4702
|
}
|
|
@@ -4640,9 +4707,9 @@ function registerManifestAdd(program2) {
|
|
|
4640
4707
|
if (!dryRun) {
|
|
4641
4708
|
writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
|
|
4642
4709
|
`, "utf8");
|
|
4643
|
-
console.log(
|
|
4710
|
+
console.log(t("manifest_add.gate_passed_wrote", { path: outPath }));
|
|
4644
4711
|
} else {
|
|
4645
|
-
console.log(
|
|
4712
|
+
console.log(t("manifest_add.gate_passed_dry_run", { path: outPath }));
|
|
4646
4713
|
console.log(JSON.stringify(payload, null, 2));
|
|
4647
4714
|
}
|
|
4648
4715
|
process.exit(0);
|
|
@@ -4655,7 +4722,7 @@ function registerResearch(program2) {
|
|
|
4655
4722
|
"Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
4656
4723
|
).requiredOption("--query <text>", "research prompt (required)").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
|
|
4657
4724
|
if (!raw.query) {
|
|
4658
|
-
console.error("
|
|
4725
|
+
console.error(t("research.require_query"));
|
|
4659
4726
|
process.exit(2);
|
|
4660
4727
|
}
|
|
4661
4728
|
const taskCtx = { task: raw.query, task_type: "search" };
|
|
@@ -4668,31 +4735,33 @@ function registerResearch(program2) {
|
|
|
4668
4735
|
...raw.model ? { agentOpts: { modelOverride: raw.model } } : {}
|
|
4669
4736
|
});
|
|
4670
4737
|
if ("aborted" in preview) {
|
|
4671
|
-
console.error(
|
|
4738
|
+
console.error(t("install.aborted", { reason: preview.reason }));
|
|
4672
4739
|
process.exit(2);
|
|
4673
4740
|
}
|
|
4674
4741
|
if ("ok" in preview && preview.ok === false) {
|
|
4675
4742
|
console.error(`error: ${preview.phase} \u2014 ${preview.error.message}`);
|
|
4676
4743
|
process.exit(1);
|
|
4677
4744
|
}
|
|
4678
|
-
console.log(`[dry-run] matched_rule: ${preview.matchedRule?.id ?? "(fallback supervisor)"}`);
|
|
4679
|
-
console.log(`[dry-run] query: ${raw.query}`);
|
|
4680
4745
|
console.log(
|
|
4681
|
-
"
|
|
4746
|
+
t("research.dry_run.matched_rule", {
|
|
4747
|
+
rule: preview.matchedRule?.id ?? "(fallback supervisor)"
|
|
4748
|
+
})
|
|
4682
4749
|
);
|
|
4750
|
+
console.log(t("research.dry_run.query", { query: raw.query }));
|
|
4751
|
+
console.log(t("research.dry_run.run_hint"));
|
|
4683
4752
|
process.exit(0);
|
|
4684
4753
|
}
|
|
4685
4754
|
const result = await runRouting(taskCtx, {
|
|
4686
4755
|
...raw.model ? { agentOpts: { modelOverride: raw.model } } : {}
|
|
4687
4756
|
});
|
|
4688
4757
|
if ("aborted" in result) {
|
|
4689
|
-
console.error(
|
|
4758
|
+
console.error(t("install.aborted", { reason: result.reason }));
|
|
4690
4759
|
process.exit(2);
|
|
4691
4760
|
}
|
|
4692
4761
|
if ("ok" in result && result.ok === false) {
|
|
4693
4762
|
console.error(`error: ${result.phase} \u2014 ${result.error.message}`);
|
|
4694
4763
|
if (result.phase === "install") {
|
|
4695
|
-
console.error(
|
|
4764
|
+
console.error(t("research.install_fix_hint"));
|
|
4696
4765
|
}
|
|
4697
4766
|
process.exit(1);
|
|
4698
4767
|
}
|
|
@@ -4714,12 +4783,11 @@ function registerResume(program2) {
|
|
|
4714
4783
|
return;
|
|
4715
4784
|
}
|
|
4716
4785
|
if (r.status === "no-paused-phase") {
|
|
4717
|
-
console.error(
|
|
4786
|
+
console.error(t("resume.fail", { error: r.error }));
|
|
4718
4787
|
process.exit(1);
|
|
4719
4788
|
}
|
|
4720
4789
|
if (r.status === "corrupt") {
|
|
4721
|
-
console.error(
|
|
4722
|
-
path: ${r.path}`);
|
|
4790
|
+
console.error(t("resume.corrupt", { error: r.error, path: r.path }));
|
|
4723
4791
|
process.exit(1);
|
|
4724
4792
|
}
|
|
4725
4793
|
if (r.cwdWarn) console.error(r.cwdWarn);
|
|
@@ -4748,8 +4816,8 @@ function registerRollback(program2) {
|
|
|
4748
4816
|
meta = JSON.parse(await readFile(metaPath, "utf8"));
|
|
4749
4817
|
} catch (err2) {
|
|
4750
4818
|
console.error(
|
|
4751
|
-
|
|
4752
|
-
|
|
4819
|
+
`${t("rollback.metadata_unreadable", { path: metaPath, message: err2.message })}
|
|
4820
|
+
${t("rollback.metadata_unreadable.fix")}`
|
|
4753
4821
|
);
|
|
4754
4822
|
process.exit(1);
|
|
4755
4823
|
return;
|
|
@@ -4772,14 +4840,18 @@ function registerRollback(program2) {
|
|
|
4772
4840
|
const sha1 = createHash("sha1").update(buf).digest("hex");
|
|
4773
4841
|
if (sha1 !== entry.sha1) {
|
|
4774
4842
|
console.error(
|
|
4775
|
-
|
|
4843
|
+
t("rollback.checksum_mismatch", {
|
|
4844
|
+
target: entry.target,
|
|
4845
|
+
expected: entry.sha1.slice(0, 12),
|
|
4846
|
+
actual: sha1.slice(0, 12)
|
|
4847
|
+
})
|
|
4776
4848
|
);
|
|
4777
4849
|
process.exit(1);
|
|
4778
4850
|
return;
|
|
4779
4851
|
}
|
|
4780
4852
|
await writeFile(entry.target, normalizeEol(buf, entry.eol));
|
|
4781
4853
|
}
|
|
4782
|
-
console.log(
|
|
4854
|
+
console.log(t("rollback.restored", { count: meta.files.length, timestamp }));
|
|
4783
4855
|
});
|
|
4784
4856
|
}
|
|
4785
4857
|
|
|
@@ -4856,6 +4928,221 @@ async function atomicWrite(path, content) {
|
|
|
4856
4928
|
return `write ${path} failed: ${err2.message}`;
|
|
4857
4929
|
}
|
|
4858
4930
|
}
|
|
4931
|
+
|
|
4932
|
+
// src/cli/lib/enableUserLangInSettings.ts
|
|
4933
|
+
init_harnessedRoot();
|
|
4934
|
+
function settingsPath2() {
|
|
4935
|
+
return resolve(homedir(), ".claude", "settings.json");
|
|
4936
|
+
}
|
|
4937
|
+
function detectUserLang(override) {
|
|
4938
|
+
if (override) {
|
|
4939
|
+
if (/^zh([^a-z]|$)/i.test(override)) return "zh-Hans";
|
|
4940
|
+
return "en";
|
|
4941
|
+
}
|
|
4942
|
+
const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale2() || "";
|
|
4943
|
+
if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
|
|
4944
|
+
return "en";
|
|
4945
|
+
}
|
|
4946
|
+
function safeIntlLocale2() {
|
|
4947
|
+
try {
|
|
4948
|
+
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
4949
|
+
} catch {
|
|
4950
|
+
return void 0;
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
async function enableUserLangInSettings(override) {
|
|
4954
|
+
const path = settingsPath2();
|
|
4955
|
+
const detected = detectUserLang(override);
|
|
4956
|
+
let raw;
|
|
4957
|
+
try {
|
|
4958
|
+
raw = await readFile(path, "utf8");
|
|
4959
|
+
} catch (err2) {
|
|
4960
|
+
const code = err2.code;
|
|
4961
|
+
if (code !== "ENOENT") {
|
|
4962
|
+
return { status: "warn", message: `read ${path} failed: ${err2.message}` };
|
|
4963
|
+
}
|
|
4964
|
+
return createFreshSettings2(path, detected);
|
|
4965
|
+
}
|
|
4966
|
+
let data;
|
|
4967
|
+
try {
|
|
4968
|
+
const parsed = JSON.parse(raw);
|
|
4969
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4970
|
+
return { status: "warn", message: `${path} is not a JSON object` };
|
|
4971
|
+
}
|
|
4972
|
+
data = parsed;
|
|
4973
|
+
} catch (err2) {
|
|
4974
|
+
return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
|
|
4975
|
+
}
|
|
4976
|
+
const env = data.env ?? {};
|
|
4977
|
+
const existing = env.HARNESSED_USER_LANG;
|
|
4978
|
+
if (typeof existing === "string" && existing.length > 0 && override === void 0) {
|
|
4979
|
+
return { status: "already-set", path, existing };
|
|
4980
|
+
}
|
|
4981
|
+
const backupPath = await backupOriginal2(raw);
|
|
4982
|
+
if (backupPath.status === "warn") return backupPath;
|
|
4983
|
+
data.env = { ...env, HARNESSED_USER_LANG: detected };
|
|
4984
|
+
const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
|
|
4985
|
+
`);
|
|
4986
|
+
if (writeErr) return { status: "warn", message: writeErr };
|
|
4987
|
+
return { status: "enabled", path, backupPath: backupPath.path, detected };
|
|
4988
|
+
}
|
|
4989
|
+
async function createFreshSettings2(path, detected) {
|
|
4990
|
+
const data = { env: { HARNESSED_USER_LANG: detected } };
|
|
4991
|
+
try {
|
|
4992
|
+
await mkdir(join(homedir(), ".claude"), { recursive: true });
|
|
4993
|
+
} catch (err2) {
|
|
4994
|
+
return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
|
|
4995
|
+
}
|
|
4996
|
+
const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
|
|
4997
|
+
`);
|
|
4998
|
+
if (writeErr) return { status: "warn", message: writeErr };
|
|
4999
|
+
return { status: "created", path, detected };
|
|
5000
|
+
}
|
|
5001
|
+
async function backupOriginal2(raw) {
|
|
5002
|
+
const backupRoot = harnessedSubdir("backups");
|
|
5003
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
5004
|
+
const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
|
|
5005
|
+
try {
|
|
5006
|
+
await mkdir(backupRoot, { recursive: true });
|
|
5007
|
+
await writeFile(backupPath, raw, "utf8");
|
|
5008
|
+
return { status: "ok", path: backupPath };
|
|
5009
|
+
} catch (err2) {
|
|
5010
|
+
return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
async function atomicWrite2(path, content) {
|
|
5014
|
+
const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
5015
|
+
try {
|
|
5016
|
+
await writeFile(tmpPath, content, "utf8");
|
|
5017
|
+
await rename(tmpPath, path);
|
|
5018
|
+
return void 0;
|
|
5019
|
+
} catch (err2) {
|
|
5020
|
+
return `write ${path} failed: ${err2.message}`;
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
function readInstalledPlugins(homedirOverride) {
|
|
5024
|
+
const home = homedir();
|
|
5025
|
+
const path = join(home, ".claude", "plugins", "installed_plugins.json");
|
|
5026
|
+
let raw;
|
|
5027
|
+
try {
|
|
5028
|
+
raw = readFileSync(path, "utf8");
|
|
5029
|
+
} catch {
|
|
5030
|
+
return /* @__PURE__ */ new Set();
|
|
5031
|
+
}
|
|
5032
|
+
let parsed;
|
|
5033
|
+
try {
|
|
5034
|
+
parsed = JSON.parse(raw);
|
|
5035
|
+
} catch {
|
|
5036
|
+
return /* @__PURE__ */ new Set();
|
|
5037
|
+
}
|
|
5038
|
+
if (!parsed || typeof parsed !== "object") return /* @__PURE__ */ new Set();
|
|
5039
|
+
const plugins = parsed.plugins;
|
|
5040
|
+
if (!plugins || typeof plugins !== "object") return /* @__PURE__ */ new Set();
|
|
5041
|
+
const out = /* @__PURE__ */ new Set();
|
|
5042
|
+
for (const key of Object.keys(plugins)) {
|
|
5043
|
+
const at = key.indexOf("@");
|
|
5044
|
+
if (at <= 0) continue;
|
|
5045
|
+
out.add(key.slice(0, at));
|
|
5046
|
+
}
|
|
5047
|
+
return out;
|
|
5048
|
+
}
|
|
5049
|
+
function resolveCapabilityCmd(capability, installedPlugins) {
|
|
5050
|
+
const { cmd, plugin_namespace } = capability;
|
|
5051
|
+
if (!plugin_namespace) return { renderedCmd: cmd };
|
|
5052
|
+
if (cmd.includes(":")) return { renderedCmd: cmd };
|
|
5053
|
+
if (!cmd.startsWith("/")) return { renderedCmd: cmd };
|
|
5054
|
+
if (!installedPlugins.has(plugin_namespace)) {
|
|
5055
|
+
return {
|
|
5056
|
+
renderedCmd: cmd,
|
|
5057
|
+
warning: `plugin '${plugin_namespace}' not installed \u2014 '${cmd}' will not resolve via Claude Code. install: 'claude plugin install ${plugin_namespace}' or see plugin docs.`
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
const bare = cmd.slice(1);
|
|
5061
|
+
return { renderedCmd: `/${plugin_namespace}:${bare}` };
|
|
5062
|
+
}
|
|
5063
|
+
var CAPABILITY_CMD_TEMPLATE = /\{\{\s*capabilities\.([a-zA-Z0-9_-]+)\.cmd\s*\}\}/g;
|
|
5064
|
+
function renderSkillBody(body, capabilities, installedPlugins) {
|
|
5065
|
+
const warningsSet = /* @__PURE__ */ new Set();
|
|
5066
|
+
const out = body.replace(CAPABILITY_CMD_TEMPLATE, (match2, name) => {
|
|
5067
|
+
const cap = capabilities[name];
|
|
5068
|
+
if (!cap) {
|
|
5069
|
+
warningsSet.add(
|
|
5070
|
+
`capability '${name}' referenced in SKILL.md but not defined in capabilities.yaml`
|
|
5071
|
+
);
|
|
5072
|
+
return match2;
|
|
5073
|
+
}
|
|
5074
|
+
const { renderedCmd, warning } = resolveCapabilityCmd(cap, installedPlugins);
|
|
5075
|
+
if (warning) warningsSet.add(warning);
|
|
5076
|
+
return renderedCmd;
|
|
5077
|
+
});
|
|
5078
|
+
return { body: out, warnings: [...warningsSet] };
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
// src/cli/lib/renderSkillTemplates.ts
|
|
5082
|
+
async function loadCapabilities(workflowsDir) {
|
|
5083
|
+
const path = join(workflowsDir, "capabilities.yaml");
|
|
5084
|
+
const raw = await readFile(path, "utf8");
|
|
5085
|
+
const doc = parse(raw);
|
|
5086
|
+
return doc?.capabilities ?? {};
|
|
5087
|
+
}
|
|
5088
|
+
async function renderSkillFile(skillName, skillsBase, capabilities, installedPlugins) {
|
|
5089
|
+
const skillPath = join(skillsBase, skillName, "SKILL.md");
|
|
5090
|
+
const result = {
|
|
5091
|
+
name: skillName,
|
|
5092
|
+
skillPath,
|
|
5093
|
+
rendered: false,
|
|
5094
|
+
warnings: []
|
|
5095
|
+
};
|
|
5096
|
+
let body;
|
|
5097
|
+
try {
|
|
5098
|
+
body = await readFile(skillPath, "utf8");
|
|
5099
|
+
} catch (e) {
|
|
5100
|
+
result.error = `read failed: ${e.message}`;
|
|
5101
|
+
return result;
|
|
5102
|
+
}
|
|
5103
|
+
const rendered = renderSkillBody(body, capabilities, installedPlugins);
|
|
5104
|
+
if (rendered.body === body) {
|
|
5105
|
+
result.warnings = rendered.warnings;
|
|
5106
|
+
return result;
|
|
5107
|
+
}
|
|
5108
|
+
try {
|
|
5109
|
+
await writeFile(skillPath, rendered.body, "utf8");
|
|
5110
|
+
result.rendered = true;
|
|
5111
|
+
result.warnings = rendered.warnings;
|
|
5112
|
+
} catch (e) {
|
|
5113
|
+
result.error = `write failed: ${e.message}`;
|
|
5114
|
+
}
|
|
5115
|
+
return result;
|
|
5116
|
+
}
|
|
5117
|
+
async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOverride) {
|
|
5118
|
+
let capabilities = {};
|
|
5119
|
+
try {
|
|
5120
|
+
capabilities = await loadCapabilities(workflowsDir);
|
|
5121
|
+
} catch (e) {
|
|
5122
|
+
return {
|
|
5123
|
+
results: skillNames.map((name) => ({
|
|
5124
|
+
name,
|
|
5125
|
+
skillPath: join(skillsBase, name, "SKILL.md"),
|
|
5126
|
+
rendered: false,
|
|
5127
|
+
warnings: [],
|
|
5128
|
+
error: `capabilities.yaml load failed: ${e.message}`
|
|
5129
|
+
})),
|
|
5130
|
+
aggregatedWarnings: [
|
|
5131
|
+
`capabilities.yaml unreadable \u2014 SKILL.md placeholders left verbatim (${e.message})`
|
|
5132
|
+
]
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5135
|
+
const installedPlugins = readInstalledPlugins();
|
|
5136
|
+
const results = [];
|
|
5137
|
+
const warningSet = /* @__PURE__ */ new Set();
|
|
5138
|
+
for (const name of skillNames) {
|
|
5139
|
+
const r = await renderSkillFile(name, skillsBase, capabilities, installedPlugins);
|
|
5140
|
+
results.push(r);
|
|
5141
|
+
for (const w of r.warnings) warningSet.add(w);
|
|
5142
|
+
if (r.error) warningSet.add(`${name}: ${r.error}`);
|
|
5143
|
+
}
|
|
5144
|
+
return { results, aggregatedWarnings: [...warningSet] };
|
|
5145
|
+
}
|
|
4859
5146
|
init_checkAgentTeams();
|
|
4860
5147
|
var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
|
|
4861
5148
|
var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
|
|
@@ -5026,7 +5313,10 @@ async function listBaseManifests2(pkgRoot) {
|
|
|
5026
5313
|
function registerSetup(program2) {
|
|
5027
5314
|
program2.command("setup").description(
|
|
5028
5315
|
"One-shot onboarding: install workflow skills + base manifests to ~/.claude/ (immediate by default \u2014 use --dry-run for preview)"
|
|
5029
|
-
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").
|
|
5316
|
+
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
|
|
5317
|
+
"--user-lang <code>",
|
|
5318
|
+
"override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
|
|
5319
|
+
).action(async (raw) => {
|
|
5030
5320
|
const dryRun = raw.dryRun === true;
|
|
5031
5321
|
const pkgRoot = getPackageRoot();
|
|
5032
5322
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
@@ -5036,7 +5326,7 @@ function registerSetup(program2) {
|
|
|
5036
5326
|
try {
|
|
5037
5327
|
entries = await readdir(workflowsDir);
|
|
5038
5328
|
} catch {
|
|
5039
|
-
console.error(
|
|
5329
|
+
console.error(t("setup.workflows_not_found", { path: workflowsDir }));
|
|
5040
5330
|
process.exit(1);
|
|
5041
5331
|
}
|
|
5042
5332
|
const { workflows: toInstall, deprecated } = await scanWorkflowsWithSkill(
|
|
@@ -5046,18 +5336,16 @@ function registerSetup(program2) {
|
|
|
5046
5336
|
const depBlock = renderDeprecationBlock(deprecated);
|
|
5047
5337
|
if (depBlock) console.log(depBlock);
|
|
5048
5338
|
if (toInstall.length === 0) {
|
|
5049
|
-
console.log("setup
|
|
5339
|
+
console.log(t("setup.nothing_to_install"));
|
|
5050
5340
|
process.exit(2);
|
|
5051
5341
|
}
|
|
5052
5342
|
if (dryRun) {
|
|
5053
|
-
console.log(
|
|
5054
|
-
`[dry-run] setup would install ${toInstall.length} workflow(s) to ${skillsBase}:`
|
|
5055
|
-
);
|
|
5343
|
+
console.log(t("setup.dry_run.header", { count: toInstall.length, path: skillsBase }));
|
|
5056
5344
|
for (const wf of toInstall) {
|
|
5057
5345
|
const masterTag = wf.isMaster ? " (master)" : "";
|
|
5058
5346
|
console.log(` ${wf.name} \u2192 ${join(skillsBase, wf.name)}${masterTag}`);
|
|
5059
5347
|
}
|
|
5060
|
-
console.log(
|
|
5348
|
+
console.log(t("setup.dry_run.run_hint"));
|
|
5061
5349
|
process.exit(0);
|
|
5062
5350
|
}
|
|
5063
5351
|
let skillsInstalled = 0;
|
|
@@ -5070,31 +5358,68 @@ function registerSetup(program2) {
|
|
|
5070
5358
|
console.log(` [A] installed ${wf.name} \u2192 ${dst}${masterTag}`);
|
|
5071
5359
|
skillsInstalled++;
|
|
5072
5360
|
} catch (e) {
|
|
5073
|
-
console.error(
|
|
5361
|
+
console.error(t("setup.copy_failed", { name: wf.name, message: e.message }));
|
|
5074
5362
|
process.exit(1);
|
|
5075
5363
|
}
|
|
5076
5364
|
}
|
|
5365
|
+
console.log(t("setup.step_a_complete", { count: skillsInstalled, path: skillsBase }));
|
|
5366
|
+
const skillNames = toInstall.map((wf) => wf.name);
|
|
5367
|
+
const rendered = await renderAllSkills(skillNames, skillsBase, workflowsDir);
|
|
5368
|
+
const renderedCount = rendered.results.filter((r) => r.rendered).length;
|
|
5077
5369
|
console.log(
|
|
5078
|
-
|
|
5079
|
-
|
|
5370
|
+
t("setup.step_a_render.complete", {
|
|
5371
|
+
count: renderedCount,
|
|
5372
|
+
total: skillsInstalled
|
|
5373
|
+
})
|
|
5080
5374
|
);
|
|
5375
|
+
if (rendered.aggregatedWarnings.length > 0) {
|
|
5376
|
+
console.warn(t("setup.step_a_render.warnings_header"));
|
|
5377
|
+
for (const w of rendered.aggregatedWarnings) {
|
|
5378
|
+
console.warn(` - ${w}`);
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5081
5381
|
const cResult = await enableAgentTeamsInSettings();
|
|
5082
5382
|
if (cResult.status === "created") {
|
|
5083
|
-
console.log(
|
|
5383
|
+
console.log(t("setup.step_c.created", { path: cResult.path }));
|
|
5084
5384
|
} else if (cResult.status === "already-enabled") {
|
|
5085
|
-
console.log(
|
|
5385
|
+
console.log(t("setup.step_c.already_enabled", { path: cResult.path }));
|
|
5086
5386
|
} else if (cResult.status === "enabled") {
|
|
5087
5387
|
console.log(
|
|
5088
|
-
|
|
5388
|
+
t("setup.step_c.enabled_backup", {
|
|
5389
|
+
path: cResult.path,
|
|
5390
|
+
backupPath: cResult.backupPath
|
|
5391
|
+
})
|
|
5089
5392
|
);
|
|
5090
5393
|
} else {
|
|
5091
|
-
console.warn(
|
|
5394
|
+
console.warn(t("setup.step_c.skipped", { message: cResult.message }));
|
|
5395
|
+
}
|
|
5396
|
+
const dResult = await enableUserLangInSettings(raw.userLang);
|
|
5397
|
+
if (dResult.status === "created") {
|
|
5398
|
+
console.log(t("setup.step_d.created", { path: dResult.path, lang: dResult.detected }));
|
|
5399
|
+
} else if (dResult.status === "already-set") {
|
|
5400
|
+
console.log(t("setup.step_d.already_set", { path: dResult.path, lang: dResult.existing }));
|
|
5401
|
+
} else if (dResult.status === "enabled") {
|
|
5402
|
+
console.log(
|
|
5403
|
+
t("setup.step_d.enabled_backup", {
|
|
5404
|
+
path: dResult.path,
|
|
5405
|
+
lang: dResult.detected,
|
|
5406
|
+
backupPath: dResult.backupPath
|
|
5407
|
+
})
|
|
5408
|
+
);
|
|
5409
|
+
} else {
|
|
5410
|
+
console.warn(t("setup.step_d.skipped", { message: dResult.message }));
|
|
5092
5411
|
}
|
|
5093
5412
|
const manifestPaths = await listBaseManifests2(pkgRoot);
|
|
5094
5413
|
const b = await runStepBInstall(manifestPaths);
|
|
5095
5414
|
const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
|
|
5096
5415
|
console.log(
|
|
5097
|
-
|
|
5416
|
+
t("setup.step_b_complete", {
|
|
5417
|
+
installed: b.installed.length,
|
|
5418
|
+
already: b.alreadyInstalled.length,
|
|
5419
|
+
skipped: b.skipped.length,
|
|
5420
|
+
failed: b.failed.length,
|
|
5421
|
+
seconds: stepBMs
|
|
5422
|
+
})
|
|
5098
5423
|
);
|
|
5099
5424
|
for (const n of b.installed) console.log(` [B] installed ${n}`);
|
|
5100
5425
|
for (const n of b.alreadyInstalled)
|
|
@@ -5104,41 +5429,33 @@ Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}
|
|
|
5104
5429
|
for (const n of b.skipped) console.log(` [B] skipped ${n}`);
|
|
5105
5430
|
for (const n of b.failed) console.error(` [B] failed ${n}`);
|
|
5106
5431
|
console.log(
|
|
5107
|
-
|
|
5108
|
-
|
|
5432
|
+
t("setup.complete", {
|
|
5433
|
+
skills: skillsInstalled,
|
|
5434
|
+
manifests: b.installed.length + b.alreadyInstalled.length
|
|
5435
|
+
})
|
|
5109
5436
|
);
|
|
5110
5437
|
if (b.alreadyInstalled.length > 0 || b.installed.length > 0) {
|
|
5111
|
-
console.log(
|
|
5112
|
-
`
|
|
5113
|
-
MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's connection status. If a server shows disconnected, restart Claude Code or check the MCP command spec.`
|
|
5114
|
-
);
|
|
5438
|
+
console.log(t("setup.mcp_hint"));
|
|
5115
5439
|
}
|
|
5116
|
-
console.log(
|
|
5117
|
-
|
|
5118
|
-
);
|
|
5119
|
-
console.log(
|
|
5120
|
-
" workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
|
|
5121
|
-
);
|
|
5440
|
+
console.log(t("setup.bundled_summary"));
|
|
5441
|
+
console.log(t("setup.bundled_location"));
|
|
5122
5442
|
process.exit(0);
|
|
5123
5443
|
});
|
|
5124
5444
|
}
|
|
5125
|
-
|
|
5126
|
-
// src/cli/status.ts
|
|
5127
5445
|
init_harnessedRoot();
|
|
5128
5446
|
function registerStatus(program2) {
|
|
5129
5447
|
program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").action(async () => {
|
|
5130
5448
|
const state = await readState(process.cwd());
|
|
5131
5449
|
const names = Object.keys(state.installed).sort();
|
|
5132
5450
|
if (names.length === 0) {
|
|
5133
|
-
console.log(
|
|
5451
|
+
console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
|
|
5134
5452
|
} else {
|
|
5135
5453
|
for (const n of names) {
|
|
5136
5454
|
const e = state.installed[n];
|
|
5137
5455
|
if (!e) continue;
|
|
5138
5456
|
console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
|
|
5139
5457
|
}
|
|
5140
|
-
console.log(
|
|
5141
|
-
${names.length} install${names.length === 1 ? "" : "s"} recorded`);
|
|
5458
|
+
console.log(t("status.summary_installs", { count: names.length }));
|
|
5142
5459
|
}
|
|
5143
5460
|
const lockPath = harnessedFile(".lock");
|
|
5144
5461
|
try {
|
|
@@ -5150,18 +5467,20 @@ ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
|
|
|
5150
5467
|
const s = await stat(lockPath);
|
|
5151
5468
|
const ageMs = Date.now() - s.mtime.getTime();
|
|
5152
5469
|
const stale = ageMs > 1e4;
|
|
5153
|
-
console.log(
|
|
5154
|
-
|
|
5155
|
-
|
|
5470
|
+
console.log(
|
|
5471
|
+
t("status.lock_held", {
|
|
5472
|
+
since: s.mtime.toISOString(),
|
|
5473
|
+
staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
|
|
5474
|
+
})
|
|
5475
|
+
);
|
|
5476
|
+
console.log(t("status.lock_release_hint", { path: lockPath }));
|
|
5156
5477
|
} else {
|
|
5157
|
-
console.log("
|
|
5478
|
+
console.log(t("status.lock_free"));
|
|
5158
5479
|
}
|
|
5159
5480
|
} catch {
|
|
5160
5481
|
}
|
|
5161
5482
|
});
|
|
5162
5483
|
}
|
|
5163
|
-
|
|
5164
|
-
// src/cli/uninstall.ts
|
|
5165
5484
|
init_path_guard();
|
|
5166
5485
|
|
|
5167
5486
|
// src/uninstallers/lib/runOrPreview.ts
|
|
@@ -5178,10 +5497,10 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5178
5497
|
}
|
|
5179
5498
|
const abort = dryRunGate(ctx);
|
|
5180
5499
|
if (abort) return abort;
|
|
5181
|
-
const
|
|
5500
|
+
const settingsPath3 = join(homedir(), ".claude", "settings.json");
|
|
5182
5501
|
let existing;
|
|
5183
5502
|
try {
|
|
5184
|
-
existing = await readFile(
|
|
5503
|
+
existing = await readFile(settingsPath3, "utf8");
|
|
5185
5504
|
} catch {
|
|
5186
5505
|
return { ok: true, removedPaths: [] };
|
|
5187
5506
|
}
|
|
@@ -5210,8 +5529,8 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5210
5529
|
if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
|
|
5211
5530
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
5212
5531
|
`;
|
|
5213
|
-
await writeFile(
|
|
5214
|
-
return { ok: true, removedPaths: [
|
|
5532
|
+
await writeFile(settingsPath3, newText);
|
|
5533
|
+
return { ok: true, removedPaths: [settingsPath3] };
|
|
5215
5534
|
};
|
|
5216
5535
|
|
|
5217
5536
|
// src/uninstallers/ccPluginMarketplace.ts
|
|
@@ -5253,11 +5572,11 @@ function extractCloneTarget2(cmd) {
|
|
|
5253
5572
|
const tokens = tail.split(/\s+/);
|
|
5254
5573
|
let i = 0;
|
|
5255
5574
|
while (i < tokens.length) {
|
|
5256
|
-
const
|
|
5257
|
-
if (
|
|
5258
|
-
if (
|
|
5575
|
+
const t2 = tokens[i];
|
|
5576
|
+
if (t2 === void 0 || !t2.startsWith("-")) break;
|
|
5577
|
+
if (t2 === "--depth" || t2 === "--branch" || t2 === "-b") {
|
|
5259
5578
|
i += 2;
|
|
5260
|
-
} else if (
|
|
5579
|
+
} else if (t2.includes("=")) {
|
|
5261
5580
|
i += 1;
|
|
5262
5581
|
} else {
|
|
5263
5582
|
i += 2;
|
|
@@ -5347,7 +5666,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5347
5666
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
5348
5667
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
5349
5668
|
const isWin = process.platform === "win32";
|
|
5350
|
-
const result = await new Promise((
|
|
5669
|
+
const result = await new Promise((resolve12) => {
|
|
5351
5670
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
5352
5671
|
let stderr = "";
|
|
5353
5672
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -5355,15 +5674,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5355
5674
|
});
|
|
5356
5675
|
const timer = setTimeout(() => {
|
|
5357
5676
|
child.kill("SIGKILL");
|
|
5358
|
-
|
|
5677
|
+
resolve12({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
5359
5678
|
}, 3e4);
|
|
5360
5679
|
child.on("error", (e) => {
|
|
5361
5680
|
clearTimeout(timer);
|
|
5362
|
-
|
|
5681
|
+
resolve12({ exitCode: -1, stderr: e.message });
|
|
5363
5682
|
});
|
|
5364
5683
|
child.on("close", (code) => {
|
|
5365
5684
|
clearTimeout(timer);
|
|
5366
|
-
|
|
5685
|
+
resolve12({ exitCode: code ?? -1, stderr });
|
|
5367
5686
|
});
|
|
5368
5687
|
});
|
|
5369
5688
|
if (result.exitCode !== 0) {
|
|
@@ -5417,8 +5736,8 @@ function registerUninstall(program2) {
|
|
|
5417
5736
|
const yes = raw.yes === true || raw.nonInteractive === true;
|
|
5418
5737
|
if (yes && raw.dryRun) {
|
|
5419
5738
|
console.error(
|
|
5420
|
-
|
|
5421
|
-
|
|
5739
|
+
`${t("uninstall.yes_dryrun_conflict")}
|
|
5740
|
+
${t("uninstall.yes_dryrun_conflict.fix", { name })}`
|
|
5422
5741
|
);
|
|
5423
5742
|
process.exit(2);
|
|
5424
5743
|
}
|
|
@@ -5437,8 +5756,8 @@ function registerUninstall(program2) {
|
|
|
5437
5756
|
chosenPath = skillPackPath;
|
|
5438
5757
|
} catch {
|
|
5439
5758
|
console.error(
|
|
5440
|
-
|
|
5441
|
-
|
|
5759
|
+
`${t("install.manifest_not_found", { name: resolvedName })}
|
|
5760
|
+
${t("install.manifest_not_found.fix", { name: resolvedName })}`
|
|
5442
5761
|
);
|
|
5443
5762
|
process.exit(1);
|
|
5444
5763
|
}
|
|
@@ -5451,28 +5770,28 @@ function registerUninstall(program2) {
|
|
|
5451
5770
|
const method = v.manifest.spec.install.method;
|
|
5452
5771
|
const dryRun = raw.dryRun === true;
|
|
5453
5772
|
if (dryRun) {
|
|
5454
|
-
console.log(
|
|
5455
|
-
console.log(
|
|
5773
|
+
console.log(t("uninstall.dry_run.preview", { name: resolvedName, method }));
|
|
5774
|
+
console.log(t("uninstall.dry_run.run_hint"));
|
|
5456
5775
|
process.exit(2);
|
|
5457
5776
|
}
|
|
5458
5777
|
if (!yes) {
|
|
5459
5778
|
const answer = await p.confirm({
|
|
5460
|
-
message:
|
|
5779
|
+
message: t("uninstall.confirm.prompt", { name: resolvedName }),
|
|
5461
5780
|
initialValue: false
|
|
5462
5781
|
});
|
|
5463
5782
|
if (p.isCancel(answer) || answer === false) {
|
|
5464
|
-
console.error(
|
|
5783
|
+
console.error(t("uninstall.cancelled"));
|
|
5465
5784
|
process.exit(2);
|
|
5466
5785
|
}
|
|
5467
5786
|
}
|
|
5468
5787
|
const opts = { apply: true, dryRun: false, yes };
|
|
5469
5788
|
const result = await runUninstall(v.manifest, opts);
|
|
5470
5789
|
if ("aborted" in result) {
|
|
5471
|
-
console.error(
|
|
5790
|
+
console.error(t("install.aborted", { reason: result.reason }));
|
|
5472
5791
|
process.exit(2);
|
|
5473
5792
|
}
|
|
5474
5793
|
if (result.ok) {
|
|
5475
|
-
console.log(
|
|
5794
|
+
console.log(t("uninstall.completed", { name: resolvedName }));
|
|
5476
5795
|
process.exit(0);
|
|
5477
5796
|
}
|
|
5478
5797
|
console.error(`error: ${result.error}`);
|
|
@@ -5483,8 +5802,20 @@ function registerUninstall(program2) {
|
|
|
5483
5802
|
// src/cli.ts
|
|
5484
5803
|
init_harnessedRoot();
|
|
5485
5804
|
migrateLegacyHarnessedRoot();
|
|
5805
|
+
var argv = process.argv;
|
|
5806
|
+
for (let i = 2; i < argv.length; i++) {
|
|
5807
|
+
const a = argv[i];
|
|
5808
|
+
if (a === "--lang" && i + 1 < argv.length) {
|
|
5809
|
+
setLocale(argv[i + 1]);
|
|
5810
|
+
break;
|
|
5811
|
+
}
|
|
5812
|
+
if (a?.startsWith("--lang=")) {
|
|
5813
|
+
setLocale(a.slice("--lang=".length));
|
|
5814
|
+
break;
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5486
5817
|
var program = new Command();
|
|
5487
|
-
program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version);
|
|
5818
|
+
program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version).option("--lang <code>", "output language: en | zh (auto-detect from $LANG / Intl if absent)");
|
|
5488
5819
|
registerInstall(program);
|
|
5489
5820
|
registerInstallBase(program);
|
|
5490
5821
|
registerResearch(program);
|