harnessed 4.7.0 → 4.9.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.
Files changed (44) hide show
  1. package/bin/harnessed-inject-state.mjs +35 -4
  2. package/dist/cli.mjs +190 -85
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/manifests/optional/perturn-inject.yaml +49 -0
  7. package/messages/zh-Hans.json +16 -2
  8. package/package.json +1 -1
  9. package/workflows/auto/SKILL.zh-Hans.md +129 -0
  10. package/workflows/disciplines/doc-discipline.zh-Hans.yaml +49 -0
  11. package/workflows/disciplines/karpathy.yaml +5 -5
  12. package/workflows/disciplines/karpathy.zh-Hans.yaml +47 -0
  13. package/workflows/disciplines/operational.yaml +6 -6
  14. package/workflows/disciplines/operational.zh-Hans.yaml +79 -0
  15. package/workflows/disciplines/output-style.yaml +7 -7
  16. package/workflows/disciplines/output-style.zh-Hans.yaml +62 -0
  17. package/workflows/disciplines/priority.yaml +2 -2
  18. package/workflows/disciplines/priority.zh-Hans.yaml +28 -0
  19. package/workflows/discuss/auto/SKILL.zh-Hans.md +75 -0
  20. package/workflows/discuss/phase/SKILL.zh-Hans.md +73 -0
  21. package/workflows/discuss/strategic/SKILL.zh-Hans.md +78 -0
  22. package/workflows/discuss/subtask/SKILL.zh-Hans.md +79 -0
  23. package/workflows/plan/architecture/SKILL.zh-Hans.md +74 -0
  24. package/workflows/plan/auto/SKILL.zh-Hans.md +75 -0
  25. package/workflows/plan/phase/SKILL.zh-Hans.md +76 -0
  26. package/workflows/research/SKILL.zh-Hans.md +81 -0
  27. package/workflows/retro/SKILL.zh-Hans.md +71 -0
  28. package/workflows/role-prompts.zh-Hans.yaml +501 -0
  29. package/workflows/ship/auto/SKILL.zh-Hans.md +46 -0
  30. package/workflows/ship/preflight/SKILL.zh-Hans.md +38 -0
  31. package/workflows/task/auto/SKILL.zh-Hans.md +80 -0
  32. package/workflows/task/clarify/SKILL.zh-Hans.md +79 -0
  33. package/workflows/task/code/SKILL.zh-Hans.md +85 -0
  34. package/workflows/task/deliver/SKILL.zh-Hans.md +113 -0
  35. package/workflows/task/test/SKILL.zh-Hans.md +90 -0
  36. package/workflows/verify/auto/SKILL.zh-Hans.md +89 -0
  37. package/workflows/verify/code-review/SKILL.zh-Hans.md +71 -0
  38. package/workflows/verify/design/SKILL.zh-Hans.md +74 -0
  39. package/workflows/verify/multispec/SKILL.zh-Hans.md +88 -0
  40. package/workflows/verify/paranoid/SKILL.zh-Hans.md +74 -0
  41. package/workflows/verify/progress/SKILL.zh-Hans.md +69 -0
  42. package/workflows/verify/qa/SKILL.zh-Hans.md +76 -0
  43. package/workflows/verify/security/SKILL.zh-Hans.md +70 -0
  44. package/workflows/verify/simplify/SKILL.zh-Hans.md +70 -0
@@ -38,11 +38,28 @@ function harnessedRoot() {
38
38
  : join(homedir(), '.claude', 'harnessed')
39
39
  }
40
40
 
41
- // workflows.json[repoKey] first, then the legacy singleton (dual-write anchor).
42
- function readWorkflow(root, key) {
41
+ // Phase 35 mirror PlatformDescriptor.sessionIdEnv (src/installers/lib/platform.ts)
42
+ // for the hot path: which env var carries the active session id. Minimal replica —
43
+ // HARNESSED_PLATFORM selects (default claude); codex has none. State-root selection
44
+ // (.platform pin / auto-probe) is orthogonal and already handled via
45
+ // HARNESSED_ROOT_OVERRIDE, so it is not re-derived here. The TS `activeKey` uses the
46
+ // full descriptor; the parity test sets matching signals.
47
+ function sessionIdEnvName() {
48
+ const platform = (process.env.HARNESSED_PLATFORM || 'claude').trim()
49
+ if (platform === 'codex') return null
50
+ return 'CLAUDE_CODE_SESSION_ID' // claude (default)
51
+ }
52
+
53
+ // Phase 35 — try the session-scoped slot first, then the bare repoKey slot, then
54
+ // the legacy singleton (dual-write anchor). `keys` is ordered most→least specific.
55
+ function readWorkflow(root, keys) {
43
56
  try {
44
57
  const store = JSON.parse(readFileSync(join(root, 'workflows.json'), 'utf8'))
45
- if (store && store.workflows && store.workflows[key]) return store.workflows[key]
58
+ if (store?.workflows) {
59
+ for (const k of keys) {
60
+ if (store.workflows[k]) return store.workflows[k]
61
+ }
62
+ }
46
63
  } catch {}
47
64
  try {
48
65
  return JSON.parse(readFileSync(join(root, 'current-workflow.json'), 'utf8'))
@@ -76,6 +93,14 @@ function workflowStateBlock(wf) {
76
93
  'RETRO-DUE: enough phases completed since the last retro — run /retro, then `harnessed retro --done`',
77
94
  )
78
95
  }
96
+ // Phase 36 — scale-adaptive verify_mode directive (parity with injectState.ts).
97
+ if (wf.verify_mode === 'full') {
98
+ lines.push(
99
+ 'VERIFY-MODE: full — run full verification (large/risky change: >5 files / >4 subs / >3 reqs)',
100
+ )
101
+ } else if (wf.verify_mode === 'light') {
102
+ lines.push('VERIFY-MODE: light — scope verification to the changed surface (small change)')
103
+ }
79
104
  lines.push('</workflow-state>')
80
105
  return lines.join('\n')
81
106
  }
@@ -140,8 +165,14 @@ function projectContextBlock(learnings, contextExcerpt) {
140
165
 
141
166
  try {
142
167
  const root = harnessedRoot()
168
+ // `key` is the repo ROOT directory (used for .planning/ fs paths below). The
169
+ // workflow LOOKUP uses the session-scoped composite key first (Phase 34/35),
170
+ // falling back to the bare repoKey, then the legacy singleton. The composite
171
+ // key is NOT a real directory — only the bare repoKey is.
143
172
  const key = repoKey(process.cwd())
144
- const wf = readWorkflow(root, key)
173
+ const envName = sessionIdEnvName()
174
+ const sid = envName ? process.env[envName]?.trim() : undefined
175
+ const wf = readWorkflow(root, sid ? [`${key}::${sid}`, key] : [key])
145
176
  if (!wf) process.exit(0)
146
177
 
147
178
  const budget = Number(process.env.HARNESSED_INJECT_BUDGET) || DEFAULT_INJECT_BUDGET
package/dist/cli.mjs CHANGED
@@ -6,6 +6,7 @@ import * as ajvFormatsNs from 'ajv-formats';
6
6
  import { execFileSync, spawnSync, spawn, exec, execFile, execSync } from 'child_process';
7
7
  import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, readdirSync } from 'fs';
8
8
  import { join, dirname, resolve, sep, relative } from 'path';
9
+ import { fileURLToPath } from 'url';
9
10
  import { homedir } from 'os';
10
11
  import { createHash } from 'crypto';
11
12
  import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, rename, appendFile, access } from 'fs/promises';
@@ -13,7 +14,6 @@ import { Value } from '@sinclair/typebox/value';
13
14
  import lockfile from 'proper-lockfile';
14
15
  import { Parser } from 'expr-eval';
15
16
  import { query } from '@anthropic-ai/claude-agent-sdk';
16
- import { fileURLToPath } from 'url';
17
17
  import { promisify } from 'util';
18
18
  import * as p from '@clack/prompts';
19
19
  import { createPatch } from 'diff';
@@ -38,7 +38,7 @@ var init_package = __esm({
38
38
  "package.json"() {
39
39
  package_default = {
40
40
  name: "harnessed",
41
- version: "4.7.0",
41
+ version: "4.9.0",
42
42
  description: "AI coding harness package manager + composition orchestrator",
43
43
  type: "module",
44
44
  license: "Apache-2.0",
@@ -852,6 +852,76 @@ var init_origin_check = __esm({
852
852
  "src/cli/lib/origin-check.ts"() {
853
853
  }
854
854
  });
855
+ function mapToSupported(raw) {
856
+ if (!raw) return "en";
857
+ if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
858
+ return "en";
859
+ }
860
+ function detectLocale() {
861
+ const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale();
862
+ return mapToSupported(raw);
863
+ }
864
+ function safeIntlLocale() {
865
+ try {
866
+ return Intl.DateTimeFormat().resolvedOptions().locale;
867
+ } catch {
868
+ return void 0;
869
+ }
870
+ }
871
+ function messagesDir() {
872
+ const here = dirname(fileURLToPath(import.meta.url));
873
+ const candidates = [resolve(here, "..", "..", "messages"), resolve(here, "..", "messages")];
874
+ for (const c of candidates) {
875
+ try {
876
+ readFileSync(join(c, "en.json"), "utf8");
877
+ return c;
878
+ } catch {
879
+ }
880
+ }
881
+ return resolve(process.cwd(), "messages");
882
+ }
883
+ function loadLocale(locale) {
884
+ if (cache[locale]) return cache[locale];
885
+ const path = join(messagesDir(), `${locale}.json`);
886
+ try {
887
+ const raw = readFileSync(path, "utf8");
888
+ cache[locale] = JSON.parse(raw);
889
+ } catch {
890
+ cache[locale] = {};
891
+ }
892
+ return cache[locale];
893
+ }
894
+ function setLocale(locale) {
895
+ if (!locale) return;
896
+ const mapped = mapToSupported(locale);
897
+ if (SUPPORTED.has(mapped)) currentLocale = mapped;
898
+ }
899
+ function getLocale() {
900
+ if (currentLocale === null) currentLocale = detectLocale();
901
+ return currentLocale;
902
+ }
903
+ function t(key, params) {
904
+ const locale = getLocale();
905
+ const primary = loadLocale(locale);
906
+ let template = primary[key];
907
+ if (template === void 0 && locale !== "en") {
908
+ template = loadLocale("en")[key];
909
+ }
910
+ if (template === void 0) template = key;
911
+ if (!params) return template;
912
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
913
+ const v = params[name];
914
+ return v === void 0 ? `{{${name}}}` : String(v);
915
+ });
916
+ }
917
+ var SUPPORTED, currentLocale, cache;
918
+ var init_i18n = __esm({
919
+ "src/i18n/index.ts"() {
920
+ SUPPORTED = /* @__PURE__ */ new Set(["en", "zh-Hans"]);
921
+ currentLocale = null;
922
+ cache = {};
923
+ }
924
+ });
855
925
  function claudeDescriptor(home = homedir()) {
856
926
  const claudeHome = join(home, ".claude");
857
927
  return {
@@ -866,7 +936,9 @@ function claudeDescriptor(home = homedir()) {
866
936
  // the same home base — not a child of homeDir.
867
937
  mcpConfigPath: join(home, ".claude.json"),
868
938
  // claude writes its env keys into JSON settings.json (capability present).
869
- supportsEnvKeyWrite: true
939
+ supportsEnvKeyWrite: true,
940
+ // Claude Code exposes the active session id to Bash-invoked CLI + hooks.
941
+ sessionIdEnv: "CLAUDE_CODE_SESSION_ID"
870
942
  };
871
943
  }
872
944
  function codexDescriptor(home = homedir()) {
@@ -882,7 +954,9 @@ function codexDescriptor(home = homedir()) {
882
954
  commandsDir: join(codexHome, "prompts"),
883
955
  pluginsRegistry: null,
884
956
  mcpConfigPath: configToml,
885
- supportsEnvKeyWrite: false
957
+ supportsEnvKeyWrite: false,
958
+ // No verified codex session-id env → single-session fallback (anti-stale).
959
+ sessionIdEnv: null
886
960
  };
887
961
  }
888
962
  function descriptorById(id, home) {
@@ -1406,6 +1480,7 @@ var workflowStore_exports = {};
1406
1480
  __export(workflowStore_exports, {
1407
1481
  RetroMetaEntry: () => RetroMetaEntry,
1408
1482
  WorkflowStoreV1: () => WorkflowStoreV1,
1483
+ activeKey: () => activeKey,
1409
1484
  listWorkflows: () => listWorkflows,
1410
1485
  readStoreRaw: () => readStoreRaw,
1411
1486
  repoKey: () => repoKey,
@@ -1430,6 +1505,12 @@ function repoKey(cwd = process.cwd()) {
1430
1505
  }
1431
1506
  return resolve(cwd);
1432
1507
  }
1508
+ function activeKey(cwd = process.cwd()) {
1509
+ const base = repoKey(cwd);
1510
+ const envName = detectPlatform().sessionIdEnv;
1511
+ const sid = envName ? process.env[envName]?.trim() : void 0;
1512
+ return sid ? `${base}::${sid}` : base;
1513
+ }
1433
1514
  async function readStoreRaw() {
1434
1515
  try {
1435
1516
  const parsed = JSON.parse(await readFile(storePath(), "utf8"));
@@ -1469,6 +1550,7 @@ var RetroMetaEntry, WorkflowStoreV1;
1469
1550
  var init_workflowStore = __esm({
1470
1551
  "src/checkpoint/workflowStore.ts"() {
1471
1552
  init_harnessedRoot();
1553
+ init_platform();
1472
1554
  init_schemaVersion();
1473
1555
  init_atomicWrite();
1474
1556
  init_currentWorkflow_v1();
@@ -1535,7 +1617,7 @@ async function withLock(fn) {
1535
1617
  }
1536
1618
  async function readCurrentWorkflow() {
1537
1619
  const store = await readStoreRaw();
1538
- return store.workflows[repoKey()] ?? null;
1620
+ return store.workflows[activeKey()] ?? store.workflows[repoKey()] ?? null;
1539
1621
  }
1540
1622
  async function writeCurrentWorkflowUnlocked(s) {
1541
1623
  if (!Value.Check(CurrentWorkflowV1, s)) {
@@ -1543,7 +1625,7 @@ async function writeCurrentWorkflowUnlocked(s) {
1543
1625
  throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
1544
1626
  }
1545
1627
  const store = await readStoreRaw();
1546
- store.workflows[repoKey()] = s;
1628
+ store.workflows[activeKey()] = s;
1547
1629
  await writeStoreRaw(store);
1548
1630
  await writeFileAtomic(statePath(), JSON.stringify(s, null, 2));
1549
1631
  }
@@ -1978,8 +2060,20 @@ var init_loadPhases = __esm({
1978
2060
  };
1979
2061
  }
1980
2062
  });
1981
- async function loadRolePrompts(workflowsDir) {
1982
- const path = join(workflowsDir, "role-prompts.yaml");
2063
+ function resolveLocaleYaml(dir, baseName, locale = getLocale()) {
2064
+ if (locale !== "en") {
2065
+ const sibling = join(dir, `${baseName}.${locale}.yaml`);
2066
+ if (existsSync(sibling)) return sibling;
2067
+ }
2068
+ return join(dir, `${baseName}.yaml`);
2069
+ }
2070
+ var init_localeYaml = __esm({
2071
+ "src/i18n/localeYaml.ts"() {
2072
+ init_i18n();
2073
+ }
2074
+ });
2075
+ async function loadRolePrompts(workflowsDir, locale = getLocale()) {
2076
+ const path = resolveLocaleYaml(workflowsDir, "role-prompts", locale);
1983
2077
  let raw;
1984
2078
  try {
1985
2079
  raw = await readFile(path, "utf8");
@@ -2193,6 +2287,8 @@ async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabiliti
2193
2287
  var INTERACTIVE_COMMANDS, ORCHESTRATOR_COMMANDS, MARKER, LANG_DIRECTIVE, HARNESSED_MARKER_RX, V3_4_3_SIGNATURE_SUB_RX, V3_4_3_SIGNATURE_MASTER_RX;
2194
2288
  var init_generateCommands = __esm({
2195
2289
  "src/cli/lib/generateCommands.ts"() {
2290
+ init_i18n();
2291
+ init_localeYaml();
2196
2292
  INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
2197
2293
  "discuss",
2198
2294
  "discuss-strategic",
@@ -6875,73 +6971,9 @@ audited ${yamls.length} manifest${yamls.length === 1 ? "" : "s"} \u2014 ${findin
6875
6971
  process.exit(errorCount > 0 ? 1 : 0);
6876
6972
  });
6877
6973
  }
6878
- var SUPPORTED = /* @__PURE__ */ new Set(["en", "zh-Hans"]);
6879
- var currentLocale = null;
6880
- var cache = {};
6881
- function mapToSupported(raw) {
6882
- if (!raw) return "en";
6883
- if (/^zh([^a-z]|$)/i.test(raw)) return "zh-Hans";
6884
- return "en";
6885
- }
6886
- function detectLocale() {
6887
- const raw = process.env.HARNESSED_LANG || process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || safeIntlLocale();
6888
- return mapToSupported(raw);
6889
- }
6890
- function safeIntlLocale() {
6891
- try {
6892
- return Intl.DateTimeFormat().resolvedOptions().locale;
6893
- } catch {
6894
- return void 0;
6895
- }
6896
- }
6897
- function messagesDir() {
6898
- const here = dirname(fileURLToPath(import.meta.url));
6899
- const candidates = [resolve(here, "..", "..", "messages"), resolve(here, "..", "messages")];
6900
- for (const c of candidates) {
6901
- try {
6902
- readFileSync(join(c, "en.json"), "utf8");
6903
- return c;
6904
- } catch {
6905
- }
6906
- }
6907
- return resolve(process.cwd(), "messages");
6908
- }
6909
- function loadLocale(locale) {
6910
- if (cache[locale]) return cache[locale];
6911
- const path = join(messagesDir(), `${locale}.json`);
6912
- try {
6913
- const raw = readFileSync(path, "utf8");
6914
- cache[locale] = JSON.parse(raw);
6915
- } catch {
6916
- cache[locale] = {};
6917
- }
6918
- return cache[locale];
6919
- }
6920
- function setLocale(locale) {
6921
- if (!locale) return;
6922
- const mapped = mapToSupported(locale);
6923
- if (SUPPORTED.has(mapped)) currentLocale = mapped;
6924
- }
6925
- function getLocale() {
6926
- if (currentLocale === null) currentLocale = detectLocale();
6927
- return currentLocale;
6928
- }
6929
- function t(key, params) {
6930
- const locale = getLocale();
6931
- const primary = loadLocale(locale);
6932
- let template = primary[key];
6933
- if (template === void 0 && locale !== "en") {
6934
- template = loadLocale("en")[key];
6935
- }
6936
- if (template === void 0) template = key;
6937
- if (!params) return template;
6938
- return template.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
6939
- const v = params[name];
6940
- return v === void 0 ? `{{${name}}}` : String(v);
6941
- });
6942
- }
6943
6974
 
6944
6975
  // src/cli/audit-log.ts
6976
+ init_i18n();
6945
6977
  init_harnessedRoot();
6946
6978
  function auditPath() {
6947
6979
  return harnessedFile("audit.log");
@@ -7048,6 +7080,9 @@ function registerAuditLog(program2) {
7048
7080
  }
7049
7081
  );
7050
7082
  }
7083
+
7084
+ // src/cli/backup-list.ts
7085
+ init_i18n();
7051
7086
  init_backup();
7052
7087
  function registerBackupList(program2) {
7053
7088
  const backup2 = program2.command("backup").description("Backup snapshot operations");
@@ -7283,6 +7318,7 @@ function registerCompact(program2) {
7283
7318
  }
7284
7319
 
7285
7320
  // src/cli/doctor.ts
7321
+ init_i18n();
7286
7322
  init_doctor_registry();
7287
7323
  function registerDoctor(program2) {
7288
7324
  program2.command("doctor").description(
@@ -7439,6 +7475,9 @@ function fireEntry(clause, master) {
7439
7475
  if (isMaster) entry.is_master = true;
7440
7476
  return entry;
7441
7477
  }
7478
+
7479
+ // src/cli/gc.ts
7480
+ init_i18n();
7442
7481
  init_backup();
7443
7482
  var DURATION_RE = /^(\d+)([dhmw])$/;
7444
7483
  function parseDuration(s) {
@@ -7529,6 +7568,7 @@ ${t("gc.invalid_duration.fix")}`
7529
7568
 
7530
7569
  // src/cli/install.ts
7531
7570
  init_package();
7571
+ init_i18n();
7532
7572
  init_installers();
7533
7573
  init_path_guard();
7534
7574
  init_validate();
@@ -7631,12 +7671,14 @@ function registerLearn(program2) {
7631
7671
 
7632
7672
  // src/cli/lib/here.ts
7633
7673
  init_nextStep();
7674
+ init_i18n();
7634
7675
  init_harnessedRoot();
7635
7676
 
7636
7677
  // src/cli/status.ts
7637
7678
  init_evidence();
7638
7679
  init_ledger();
7639
7680
  init_state();
7681
+ init_i18n();
7640
7682
  init_harnessedRoot();
7641
7683
  init_state2();
7642
7684
  function statusMarker(status) {
@@ -7803,6 +7845,9 @@ async function runHere(opts) {
7803
7845
  }
7804
7846
  process.exit(0);
7805
7847
  }
7848
+
7849
+ // src/cli/manifest-add.ts
7850
+ init_i18n();
7806
7851
  var QA = [
7807
7852
  { q: "\u2460 \u662F\u771F reusable surface \u8FD8\u662F\u4E34\u65F6 wrapper?", f: "q1_reusable_surface" },
7808
7853
  { q: "\u2461 \u4E0A\u6E38\u540D\u5B57 fit \u9879\u76EE shape \u5417? \u6709\u73B0\u6709\u547D\u540D\u51B2\u7A81\u5417?", f: "q2_name_fit" },
@@ -7874,6 +7919,8 @@ function registerNext(program2) {
7874
7919
  }
7875
7920
 
7876
7921
  // src/cli/prompt.ts
7922
+ init_i18n();
7923
+ init_localeYaml();
7877
7924
  init_run();
7878
7925
  init_generateCommands();
7879
7926
  init_packagePath();
@@ -7919,16 +7966,17 @@ ${lines.join("\n")}
7919
7966
  return "";
7920
7967
  }
7921
7968
  }
7922
- async function buildDisciplinesSection(sub, packageRoot) {
7969
+ async function buildDisciplinesSection(sub, packageRoot, locale = getLocale()) {
7923
7970
  try {
7924
7971
  const workflowsDir = resolve(packageRoot, "workflows");
7972
+ const disciplinesDir = resolve(workflowsDir, "disciplines");
7925
7973
  const applied = await loadSubArrayField(sub, packageRoot, "disciplines_applied");
7926
7974
  const names = applied.filter((d) => d !== "language");
7927
7975
  if (names.length === 0) return "";
7928
7976
  const blocks = [];
7929
7977
  for (const name of names) {
7930
7978
  try {
7931
- const dRaw = await readFile(resolve(workflowsDir, "disciplines", `${name}.yaml`), "utf8");
7979
+ const dRaw = await readFile(resolveLocaleYaml(disciplinesDir, name, locale), "utf8");
7932
7980
  const dDoc = parse(dRaw);
7933
7981
  const rules = Array.isArray(dDoc?.rules) ? dDoc.rules : [];
7934
7982
  const descs = rules.map((r) => typeof r.description === "string" ? r.description.trim() : "").filter((s) => s.length > 0).map((s) => ` - ${s.replace(/\s+/g, " ")}`);
@@ -8074,6 +8122,9 @@ function registerReleasePreflight(program2) {
8074
8122
  process.exit(failed ? 1 : 0);
8075
8123
  });
8076
8124
  }
8125
+
8126
+ // src/cli/research.ts
8127
+ init_i18n();
8077
8128
  init_run();
8078
8129
  init_packagePath();
8079
8130
  init_run2();
@@ -8115,6 +8166,7 @@ function registerResearch(program2) {
8115
8166
  }
8116
8167
 
8117
8168
  // src/cli/resume.ts
8169
+ init_i18n();
8118
8170
  function registerResume(program2) {
8119
8171
  program2.command("resume").description(
8120
8172
  "Reload checkpoint from paused workflow + print resume hint (D-03 \u2014 user invokes phase command manually)"
@@ -8179,6 +8231,9 @@ function registerRetro(program2) {
8179
8231
  process.exit(0);
8180
8232
  });
8181
8233
  }
8234
+
8235
+ // src/cli/rollback.ts
8236
+ init_i18n();
8182
8237
  init_backup();
8183
8238
  function normalizeEol(buf, eol) {
8184
8239
  const lf = buf.toString("utf8").replace(/\r\n/g, "\n");
@@ -8250,6 +8305,9 @@ ${t("rollback.metadata_unreadable.fix")}`
8250
8305
 
8251
8306
  // src/cli.ts
8252
8307
  init_run2();
8308
+
8309
+ // src/cli/setup.ts
8310
+ init_i18n();
8253
8311
  init_platform();
8254
8312
 
8255
8313
  // src/cli/lib/capabilityResolver.ts
@@ -8504,42 +8562,78 @@ async function enableUserLangInSettings(override) {
8504
8562
  // src/cli/setup.ts
8505
8563
  init_generateCommands();
8506
8564
  init_packagePath();
8565
+
8566
+ // src/cli/lib/renderSkillTemplates.ts
8567
+ init_i18n();
8568
+
8569
+ // src/cli/lib/resolveSkillBody.ts
8570
+ init_i18n();
8571
+ function skillBodyFilename(locale) {
8572
+ return locale === "en" ? "SKILL.md" : `SKILL.${locale}.md`;
8573
+ }
8574
+ function resolveSkillBodyFilename(skillDir, locale) {
8575
+ const resolved = locale ?? getLocale();
8576
+ const candidate = skillBodyFilename(resolved);
8577
+ if (candidate === "SKILL.md") return "SKILL.md";
8578
+ return existsSync(join(skillDir, candidate)) ? candidate : "SKILL.md";
8579
+ }
8580
+
8581
+ // src/cli/lib/renderSkillTemplates.ts
8582
+ var LOCALE_SIBLINGS = ["zh-Hans"];
8507
8583
  async function loadCapabilities(workflowsDir) {
8508
8584
  const path = join(workflowsDir, "capabilities.yaml");
8509
8585
  const raw = await readFile(path, "utf8");
8510
8586
  const doc = parse(raw);
8511
8587
  return doc?.capabilities ?? {};
8512
8588
  }
8513
- async function renderSkillFile(skillName, skillsBase, capabilities, installedPlugins, installedUserSkills) {
8514
- const skillPath = join(skillsBase, skillName, "SKILL.md");
8589
+ async function renderSkillFile(skillName, skillsBase, capabilities, installedPlugins, installedUserSkills, locale) {
8590
+ const dir = join(skillsBase, skillName);
8591
+ const resolved = locale ?? getLocale();
8592
+ const srcName = resolveSkillBodyFilename(dir, resolved);
8593
+ const srcPath = join(dir, srcName);
8594
+ const destPath = join(dir, "SKILL.md");
8515
8595
  const result = {
8516
8596
  name: skillName,
8517
- skillPath,
8597
+ skillPath: destPath,
8518
8598
  rendered: false,
8519
8599
  warnings: []
8520
8600
  };
8521
8601
  let body;
8522
8602
  try {
8523
- body = await readFile(skillPath, "utf8");
8603
+ body = await readFile(srcPath, "utf8");
8524
8604
  } catch (e) {
8525
8605
  result.error = `read failed: ${e.message}`;
8526
8606
  return result;
8527
8607
  }
8528
8608
  const rendered = renderSkillBody(body, capabilities, installedPlugins, installedUserSkills);
8529
- if (rendered.body === body) {
8609
+ const localeBodySelected = srcName !== "SKILL.md";
8610
+ const needsWrite = localeBodySelected || rendered.body !== body;
8611
+ if (!needsWrite) {
8530
8612
  result.warnings = rendered.warnings;
8531
8613
  return result;
8532
8614
  }
8533
8615
  try {
8534
- await writeFile(skillPath, rendered.body, "utf8");
8535
- result.rendered = true;
8616
+ await writeFile(destPath, rendered.body, "utf8");
8617
+ result.rendered = rendered.body !== body;
8536
8618
  result.warnings = rendered.warnings;
8537
8619
  } catch (e) {
8538
8620
  result.error = `write failed: ${e.message}`;
8621
+ return result;
8622
+ }
8623
+ if (localeBodySelected) {
8624
+ for (const loc of LOCALE_SIBLINGS) {
8625
+ const sibling = skillBodyFilename(loc);
8626
+ if (sibling === "SKILL.md") continue;
8627
+ try {
8628
+ await rm(join(dir, sibling), { force: true });
8629
+ } catch {
8630
+ }
8631
+ }
8539
8632
  }
8540
8633
  return result;
8541
8634
  }
8542
- async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOverride) {
8635
+ async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOverride, locale) {
8636
+ const resolvedLocale = locale ?? getLocale();
8543
8637
  let capabilities = {};
8544
8638
  try {
8545
8639
  capabilities = await loadCapabilities(workflowsDir);
@@ -8567,7 +8661,8 @@ async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOver
8567
8661
  skillsBase,
8568
8662
  capabilities,
8569
8663
  installedPlugins,
8570
- installedUserSkills
8664
+ installedUserSkills,
8665
+ resolvedLocale
8571
8666
  );
8572
8667
  results.push(r);
8573
8668
  for (const w of r.warnings) warningSet.add(w);
@@ -8835,7 +8930,13 @@ function registerSetup(program2) {
8835
8930
  }
8836
8931
  console.log(t("setup.step_a_complete", { count: skillsInstalled, path: skillsBase }));
8837
8932
  const skillNames = toInstall.map((wf) => wf.name);
8838
- const rendered = await renderAllSkills(skillNames, skillsBase, workflowsDir);
8933
+ const rendered = await renderAllSkills(
8934
+ skillNames,
8935
+ skillsBase,
8936
+ workflowsDir,
8937
+ void 0,
8938
+ getLocale()
8939
+ );
8839
8940
  if (rendered.aggregatedWarnings.length > 0) {
8840
8941
  console.warn(t("setup.step_a_render.warnings_header"));
8841
8942
  for (const w of rendered.aggregatedWarnings) {
@@ -8937,6 +9038,9 @@ Force-update pass complete: ${b2.installed.length} installed / ${b2.alreadyInsta
8937
9038
  process.exit(0);
8938
9039
  });
8939
9040
  }
9041
+
9042
+ // src/cli/uninstall.ts
9043
+ init_i18n();
8940
9044
  init_harnessedRoot();
8941
9045
  init_path_guard();
8942
9046
  init_validate();
@@ -9513,6 +9617,7 @@ function registerWorkflows(program2) {
9513
9617
  }
9514
9618
 
9515
9619
  // src/cli.ts
9620
+ init_i18n();
9516
9621
  init_harnessedRoot();
9517
9622
  migrateLegacyHarnessedRoot();
9518
9623
  var argv = process.argv;