harnessed 4.5.1 → 4.7.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/dist/cli.mjs CHANGED
@@ -38,7 +38,7 @@ var init_package = __esm({
38
38
  "package.json"() {
39
39
  package_default = {
40
40
  name: "harnessed",
41
- version: "4.5.1",
41
+ version: "4.7.0",
42
42
  description: "AI coding harness package manager + composition orchestrator",
43
43
  type: "module",
44
44
  license: "Apache-2.0",
@@ -852,10 +852,92 @@ var init_origin_check = __esm({
852
852
  "src/cli/lib/origin-check.ts"() {
853
853
  }
854
854
  });
855
- function getHarnessedRoot() {
855
+ function claudeDescriptor(home = homedir()) {
856
+ const claudeHome = join(home, ".claude");
857
+ return {
858
+ id: "claude",
859
+ homeDir: claudeHome,
860
+ stateRoot: join(claudeHome, "harnessed"),
861
+ settingsPath: join(claudeHome, "settings.json"),
862
+ skillsDir: join(claudeHome, "skills"),
863
+ commandsDir: join(claudeHome, "commands"),
864
+ pluginsRegistry: join(claudeHome, "plugins", "installed_plugins.json"),
865
+ // mcpConfigPath is a SIBLING of `.claude` (`<home>/.claude.json`), based on
866
+ // the same home base — not a child of homeDir.
867
+ mcpConfigPath: join(home, ".claude.json"),
868
+ // claude writes its env keys into JSON settings.json (capability present).
869
+ supportsEnvKeyWrite: true
870
+ };
871
+ }
872
+ function codexDescriptor(home = homedir()) {
873
+ const codexHome = join(home, ".codex");
874
+ const configToml = join(codexHome, "config.toml");
875
+ return {
876
+ id: "codex",
877
+ homeDir: codexHome,
878
+ stateRoot: join(codexHome, "harnessed"),
879
+ settingsPath: configToml,
880
+ // SHARED convention dir, NOT <homeDir>/skills.
881
+ skillsDir: join(home, ".agents", "skills"),
882
+ commandsDir: join(codexHome, "prompts"),
883
+ pluginsRegistry: null,
884
+ mcpConfigPath: configToml,
885
+ supportsEnvKeyWrite: false
886
+ };
887
+ }
888
+ function descriptorById(id, home) {
889
+ if (id === "claude") return claudeDescriptor(home);
890
+ if (id === "codex") return codexDescriptor(home);
891
+ return void 0;
892
+ }
893
+ function detectPlatform(home = homedir()) {
894
+ const base = claudeDescriptor(home);
856
895
  const override = process.env.HARNESSED_ROOT_OVERRIDE;
857
- if (override !== void 0 && override !== "") return override;
858
- return join(homedir(), ".claude", "harnessed");
896
+ if (override !== void 0 && override !== "") return { ...base, stateRoot: override };
897
+ const envPlatform = process.env.HARNESSED_PLATFORM;
898
+ if (envPlatform !== void 0 && envPlatform !== "") {
899
+ const d = descriptorById(envPlatform, home);
900
+ if (d) return d;
901
+ }
902
+ try {
903
+ const pin = readFileSync(join(base.stateRoot, ".platform"), "utf8").trim();
904
+ const d = descriptorById(pin, home);
905
+ if (d) return d;
906
+ } catch {
907
+ }
908
+ try {
909
+ if (existsSync(base.homeDir)) return base;
910
+ const codex = codexDescriptor(home);
911
+ if (existsSync(codex.homeDir)) return codex;
912
+ } catch {
913
+ }
914
+ return base;
915
+ }
916
+ function getSettingsPath(home) {
917
+ return detectPlatform(home).settingsPath;
918
+ }
919
+ function getSkillsDir(home) {
920
+ return detectPlatform(home).skillsDir;
921
+ }
922
+ function getCommandsDir(home) {
923
+ return detectPlatform(home).commandsDir;
924
+ }
925
+ function getPluginsRegistry(home) {
926
+ return detectPlatform(home).pluginsRegistry;
927
+ }
928
+ function getMcpConfigPath(home) {
929
+ return detectPlatform(home).mcpConfigPath;
930
+ }
931
+ function harnessSkillsDirs(home) {
932
+ const dirs = [claudeDescriptor(home).skillsDir, codexDescriptor(home).skillsDir];
933
+ return [...new Set(dirs)];
934
+ }
935
+ var init_platform = __esm({
936
+ "src/installers/lib/platform.ts"() {
937
+ }
938
+ });
939
+ function getHarnessedRoot() {
940
+ return detectPlatform().stateRoot;
859
941
  }
860
942
  function harnessedSubdir(name) {
861
943
  return join(getHarnessedRoot(), name);
@@ -890,6 +972,7 @@ function migrateLegacyHarnessedRoot() {
890
972
  }
891
973
  var init_harnessedRoot = __esm({
892
974
  "src/installers/lib/harnessedRoot.ts"() {
975
+ init_platform();
893
976
  }
894
977
  });
895
978
  function getBackupRoot() {
@@ -1291,7 +1374,19 @@ var init_currentWorkflow_v1 = __esm({
1291
1374
  },
1292
1375
  { additionalProperties: false }
1293
1376
  )
1294
- )
1377
+ ),
1378
+ // Phase 22 — smart reminders. Additive-optional flags set by `checkpoint
1379
+ // complete` (allResolved) and read by the G4 inject twins (booleans only; the
1380
+ // bin computes neither git nor thresholds). Old files without them Value.Check-pass
1381
+ // → NO schemaVersion bump.
1382
+ // ship_ready — there are unshipped commits since the last vX.Y.Z tag (git-derived,
1383
+ // recomputed each completion; self-heals to false after a release).
1384
+ // ship_commits — commit count since that tag (feeds the SHIP-READY hint text).
1385
+ // retro_due — phases_since_retro (store sidecar) has crossed the threshold;
1386
+ // cleared by `harnessed retro --done`.
1387
+ ship_ready: Type.Optional(Type.Boolean()),
1388
+ ship_commits: Type.Optional(Type.Integer({ minimum: 0 })),
1389
+ retro_due: Type.Optional(Type.Boolean())
1295
1390
  },
1296
1391
  { additionalProperties: false }
1297
1392
  );
@@ -1309,6 +1404,7 @@ var init_schema2 = __esm({
1309
1404
  // src/checkpoint/workflowStore.ts
1310
1405
  var workflowStore_exports = {};
1311
1406
  __export(workflowStore_exports, {
1407
+ RetroMetaEntry: () => RetroMetaEntry,
1312
1408
  WorkflowStoreV1: () => WorkflowStoreV1,
1313
1409
  listWorkflows: () => listWorkflows,
1314
1410
  readStoreRaw: () => readStoreRaw,
@@ -1369,17 +1465,26 @@ async function listWorkflows() {
1369
1465
  status: wf.status
1370
1466
  }));
1371
1467
  }
1372
- var WorkflowStoreV1;
1468
+ var RetroMetaEntry, WorkflowStoreV1;
1373
1469
  var init_workflowStore = __esm({
1374
1470
  "src/checkpoint/workflowStore.ts"() {
1375
1471
  init_harnessedRoot();
1376
1472
  init_schemaVersion();
1377
1473
  init_atomicWrite();
1378
1474
  init_currentWorkflow_v1();
1475
+ RetroMetaEntry = Type.Object(
1476
+ {
1477
+ phases_since_retro: Type.Integer({ minimum: 0 }),
1478
+ last_retro_at: Type.Optional(Type.String({ minLength: 1 }))
1479
+ },
1480
+ { additionalProperties: false }
1481
+ );
1379
1482
  WorkflowStoreV1 = Type.Object(
1380
1483
  {
1381
1484
  schemaVersion: Type.Literal(SCHEMA_VERSIONS.workflowStore),
1382
- workflows: Type.Record(Type.String(), CurrentWorkflowV1)
1485
+ workflows: Type.Record(Type.String(), CurrentWorkflowV1),
1486
+ // Additive-optional (no schemaVersion bump — old stores Value.Check-pass).
1487
+ retro_meta: Type.Optional(Type.Record(Type.String(), RetroMetaEntry))
1383
1488
  },
1384
1489
  { additionalProperties: false }
1385
1490
  );
@@ -1393,6 +1498,7 @@ __export(state_exports, {
1393
1498
  WorkflowStateError: () => WorkflowStateError,
1394
1499
  activate: () => activate,
1395
1500
  complete: () => complete,
1501
+ mutateStore: () => mutateStore,
1396
1502
  mutateSubProgress: () => mutateSubProgress,
1397
1503
  pause: () => pause,
1398
1504
  readCurrentWorkflow: () => readCurrentWorkflow,
@@ -1452,6 +1558,12 @@ async function mutateSubProgress(fn) {
1452
1558
  await writeCurrentWorkflowUnlocked({ ...s, sub_progress: next });
1453
1559
  });
1454
1560
  }
1561
+ async function mutateStore(fn) {
1562
+ await withLock(async () => {
1563
+ const store = await readStoreRaw();
1564
+ await writeStoreRaw(fn(store));
1565
+ });
1566
+ }
1455
1567
  async function activate(phase, checkpointPath = null) {
1456
1568
  await writeCurrentWorkflow({
1457
1569
  schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
@@ -2027,7 +2139,7 @@ function nameToYamlHintPath(name) {
2027
2139
  function shouldOverwriteFile(content) {
2028
2140
  return HARNESSED_MARKER_RX.test(content) || V3_4_3_SIGNATURE_SUB_RX.test(content) || V3_4_3_SIGNATURE_MASTER_RX.test(content);
2029
2141
  }
2030
- async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync12 = (p5) => readFileSync(p5, "utf8")) {
2142
+ async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync13 = (p5) => readFileSync(p5, "utf8")) {
2031
2143
  const results = [];
2032
2144
  const aggregatedWarnings = /* @__PURE__ */ new Set();
2033
2145
  for (const name of slashNames) {
@@ -2046,7 +2158,7 @@ async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabiliti
2046
2158
  if (fileExists2(path)) {
2047
2159
  let existing = "";
2048
2160
  try {
2049
- existing = readFileSync12(path);
2161
+ existing = readFileSync13(path);
2050
2162
  } catch {
2051
2163
  existing = "";
2052
2164
  }
@@ -3801,6 +3913,91 @@ var init_compact = __esm({
3801
3913
  }
3802
3914
  });
3803
3915
 
3916
+ // src/checkpoint/shipReady.ts
3917
+ var shipReady_exports = {};
3918
+ __export(shipReady_exports, {
3919
+ collectShipReady: () => collectShipReady,
3920
+ defaultShipReady: () => defaultShipReady,
3921
+ pickLatestReleaseTag: () => pickLatestReleaseTag
3922
+ });
3923
+ function pickLatestReleaseTag(tags) {
3924
+ let best = null;
3925
+ for (const tag of tags) {
3926
+ const m = RELEASE_TAG.exec(tag.trim());
3927
+ if (!m) continue;
3928
+ const n = [Number(m[1]), Number(m[2]), Number(m[3])];
3929
+ if (!best || n[0] > best.n[0] || n[0] === best.n[0] && n[1] > best.n[1] || n[0] === best.n[0] && n[1] === best.n[1] && n[2] > best.n[2]) {
3930
+ best = { tag: tag.trim(), n };
3931
+ }
3932
+ }
3933
+ return best?.tag ?? null;
3934
+ }
3935
+ function collectShipReady(tags, countSince) {
3936
+ const baseTag = pickLatestReleaseTag(tags);
3937
+ const commits = countSince(baseTag);
3938
+ return { ready: commits > 0, commits, baseTag };
3939
+ }
3940
+ function defaultShipReady(cwd) {
3941
+ let tags = [];
3942
+ try {
3943
+ tags = execFileSync("git", ["tag", "-l"], { cwd, encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
3944
+ } catch {
3945
+ return { ready: false, commits: 0, baseTag: null };
3946
+ }
3947
+ const countSince = (tag) => {
3948
+ try {
3949
+ const range = tag ? `${tag}..HEAD` : "HEAD";
3950
+ const out = execFileSync("git", ["rev-list", range, "--count"], {
3951
+ cwd,
3952
+ encoding: "utf8"
3953
+ }).trim();
3954
+ const n = Number(out);
3955
+ return Number.isFinite(n) ? n : 0;
3956
+ } catch {
3957
+ return 0;
3958
+ }
3959
+ };
3960
+ return collectShipReady(tags, countSince);
3961
+ }
3962
+ var RELEASE_TAG;
3963
+ var init_shipReady = __esm({
3964
+ "src/checkpoint/shipReady.ts"() {
3965
+ RELEASE_TAG = /^v(\d+)\.(\d+)\.(\d+)$/;
3966
+ }
3967
+ });
3968
+
3969
+ // src/checkpoint/retroMeta.ts
3970
+ var retroMeta_exports = {};
3971
+ __export(retroMeta_exports, {
3972
+ DEFAULT_RETRO_THRESHOLD: () => DEFAULT_RETRO_THRESHOLD,
3973
+ incrementPhases: () => incrementPhases,
3974
+ isRetroDue: () => isRetroDue,
3975
+ resetRetro: () => resetRetro,
3976
+ retroThreshold: () => retroThreshold
3977
+ });
3978
+ function retroThreshold(env) {
3979
+ const raw = env.HARNESSED_RETRO_PHASE_THRESHOLD;
3980
+ if (raw === void 0) return DEFAULT_RETRO_THRESHOLD;
3981
+ const n = Number(raw);
3982
+ return Number.isInteger(n) && n > 0 ? n : DEFAULT_RETRO_THRESHOLD;
3983
+ }
3984
+ function incrementPhases(entry) {
3985
+ if (!entry) return { phases_since_retro: 1 };
3986
+ return entry.last_retro_at ? { phases_since_retro: entry.phases_since_retro + 1, last_retro_at: entry.last_retro_at } : { phases_since_retro: entry.phases_since_retro + 1 };
3987
+ }
3988
+ function isRetroDue(count, threshold) {
3989
+ return count >= threshold;
3990
+ }
3991
+ function resetRetro(now) {
3992
+ return { phases_since_retro: 0, last_retro_at: now };
3993
+ }
3994
+ var DEFAULT_RETRO_THRESHOLD;
3995
+ var init_retroMeta = __esm({
3996
+ "src/checkpoint/retroMeta.ts"() {
3997
+ DEFAULT_RETRO_THRESHOLD = 5;
3998
+ }
3999
+ });
4000
+
3804
4001
  // src/checkpoint/breakLoop.ts
3805
4002
  var breakLoop_exports = {};
3806
4003
  __export(breakLoop_exports, {
@@ -4259,7 +4456,7 @@ var init_check_mattpocock_skills = __esm({
4259
4456
  }
4260
4457
  });
4261
4458
  function getUserClaudeJsonPath() {
4262
- return join(homedir(), ".claude.json");
4459
+ return getMcpConfigPath();
4263
4460
  }
4264
4461
  async function readUserClaudeJson() {
4265
4462
  const path = getUserClaudeJsonPath();
@@ -4285,21 +4482,20 @@ async function isMcpServerRegistered(name) {
4285
4482
  return Object.hasOwn(servers, name);
4286
4483
  }
4287
4484
  async function isPluginRegistered(pluginName) {
4288
- try {
4289
- const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
4290
- const raw = await readFile(path, "utf8");
4291
- const parsed = JSON.parse(raw);
4292
- const plugins = parsed.plugins;
4293
- if (plugins && typeof plugins === "object") {
4294
- if (Object.hasOwn(plugins, pluginName)) return true;
4295
- if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
4485
+ const registryPath = getPluginsRegistry();
4486
+ if (registryPath !== null) {
4487
+ try {
4488
+ const raw = await readFile(registryPath, "utf8");
4489
+ const parsed = JSON.parse(raw);
4490
+ const plugins = parsed.plugins;
4491
+ if (plugins && typeof plugins === "object") {
4492
+ if (Object.hasOwn(plugins, pluginName)) return true;
4493
+ if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
4494
+ }
4495
+ } catch {
4296
4496
  }
4297
- } catch {
4298
4497
  }
4299
- for (const path of [
4300
- join(homedir(), ".claude", "settings.json"),
4301
- join(homedir(), ".claude.json")
4302
- ]) {
4498
+ for (const path of [getSettingsPath(), getMcpConfigPath()]) {
4303
4499
  try {
4304
4500
  const raw = await readFile(path, "utf8");
4305
4501
  const parsed = JSON.parse(raw);
@@ -4315,6 +4511,7 @@ async function isPluginRegistered(pluginName) {
4315
4511
  }
4316
4512
  var init_readClaudeConfig = __esm({
4317
4513
  "src/installers/lib/readClaudeConfig.ts"() {
4514
+ init_platform();
4318
4515
  }
4319
4516
  });
4320
4517
 
@@ -4713,6 +4910,7 @@ var init_ccHookAdd2 = __esm({
4713
4910
  init_confirm();
4714
4911
  init_diff();
4715
4912
  init_err();
4913
+ init_platform();
4716
4914
  init_preflight();
4717
4915
  init_state2();
4718
4916
  installCcHookAdd = async (ctx) => {
@@ -4736,10 +4934,10 @@ var init_ccHookAdd2 = __esm({
4736
4934
  const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
4737
4935
  return { ok: false, phase: "preflight", error: e };
4738
4936
  }
4739
- const settingsPath3 = join(homedir(), ".claude", "settings.json");
4937
+ const settingsPath = getSettingsPath();
4740
4938
  let existing;
4741
4939
  try {
4742
- existing = await readFile(settingsPath3, "utf8");
4940
+ existing = await readFile(settingsPath, "utf8");
4743
4941
  } catch {
4744
4942
  existing = null;
4745
4943
  }
@@ -4770,7 +4968,7 @@ var init_ccHookAdd2 = __esm({
4770
4968
  const newText = `${JSON.stringify(settings, null, 2)}
4771
4969
  `;
4772
4970
  const plan = {
4773
- files: [{ target: settingsPath3, scope: "HOME", oldText: existing ?? "", newText }]
4971
+ files: [{ target: settingsPath, scope: "HOME", oldText: existing ?? "", newText }]
4774
4972
  };
4775
4973
  if (!ctx.opts.quiet) process.stdout.write(renderDiff(plan, ctx));
4776
4974
  const conf = await confirmAt("L3", { ...ctx});
@@ -4781,10 +4979,10 @@ var init_ccHookAdd2 = __esm({
4781
4979
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
4782
4980
  const bk = await backup(plan, ctx);
4783
4981
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
4784
- await writeFile(settingsPath3, newText);
4982
+ await writeFile(settingsPath, newText);
4785
4983
  let verify;
4786
4984
  try {
4787
- verify = JSON.parse(await readFile(settingsPath3, "utf8"));
4985
+ verify = JSON.parse(await readFile(settingsPath, "utf8"));
4788
4986
  } catch (e) {
4789
4987
  return {
4790
4988
  ok: false,
@@ -4812,7 +5010,7 @@ var init_ccHookAdd2 = __esm({
4812
5010
  };
4813
5011
  }
4814
5012
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
4815
- return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath3] };
5013
+ return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
4816
5014
  };
4817
5015
  }
4818
5016
  });
@@ -4868,10 +5066,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
4868
5066
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
4869
5067
  stderr += chunk;
4870
5068
  });
4871
- return await new Promise((resolve20) => {
5069
+ return await new Promise((resolve18) => {
4872
5070
  const timer = setTimeout(() => {
4873
5071
  child.kill("SIGKILL");
4874
- resolve20({
5072
+ resolve18({
4875
5073
  ok: false,
4876
5074
  phase: "spawn",
4877
5075
  error: {
@@ -4887,7 +5085,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
4887
5085
  child.on("error", (err2) => {
4888
5086
  clearTimeout(timer);
4889
5087
  const bashMissing = triedBash && err2.code === "ENOENT";
4890
- resolve20({
5088
+ resolve18({
4891
5089
  ok: false,
4892
5090
  phase: "spawn",
4893
5091
  error: {
@@ -4902,7 +5100,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
4902
5100
  });
4903
5101
  child.on("close", (code) => {
4904
5102
  clearTimeout(timer);
4905
- resolve20({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
5103
+ resolve18({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
4906
5104
  });
4907
5105
  });
4908
5106
  }
@@ -4942,7 +5140,7 @@ async function detectNative(ctx) {
4942
5140
  const indicators = INSTALLED_INDICATORS[name];
4943
5141
  if (indicators) {
4944
5142
  for (const ind of indicators) {
4945
- const dir = join(homedir(), ".claude", "skills", ind);
5143
+ const dir = join(getSkillsDir(), ind);
4946
5144
  try {
4947
5145
  await access(dir);
4948
5146
  return true;
@@ -4961,8 +5159,8 @@ async function detectNative(ctx) {
4961
5159
  }
4962
5160
  if (method === "npx-skill-installer") {
4963
5161
  const skillName = extractSkillName(cmd, name);
4964
- for (const base of [".claude", ".agents"]) {
4965
- const skillMd = join(homedir(), base, "skills", skillName, "SKILL.md");
5162
+ for (const skillsDir of harnessSkillsDirs()) {
5163
+ const skillMd = join(skillsDir, skillName, "SKILL.md");
4966
5164
  try {
4967
5165
  await access(skillMd);
4968
5166
  return true;
@@ -4981,7 +5179,7 @@ async function detectNative(ctx) {
4981
5179
  }
4982
5180
  }
4983
5181
  if (method === "npm-cli") {
4984
- const skillDir = join(homedir(), ".claude", "skills", name);
5182
+ const skillDir = join(getSkillsDir(), name);
4985
5183
  try {
4986
5184
  await access(skillDir);
4987
5185
  return true;
@@ -5015,6 +5213,7 @@ async function isAlreadyInstalled(ctx, opts = {}) {
5015
5213
  var IDEMPOTENT_CHECK_TIMEOUT_MS, INSTALLED_INDICATORS;
5016
5214
  var init_idempotent = __esm({
5017
5215
  "src/installers/lib/idempotent.ts"() {
5216
+ init_platform();
5018
5217
  init_readClaudeConfig();
5019
5218
  init_spawn();
5020
5219
  IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
@@ -5025,7 +5224,7 @@ var init_idempotent = __esm({
5025
5224
  }
5026
5225
  });
5027
5226
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
5028
- return new Promise((resolve20) => {
5227
+ return new Promise((resolve18) => {
5029
5228
  const isWin = process.platform === "win32";
5030
5229
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
5031
5230
  let stderr = "";
@@ -5034,15 +5233,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
5034
5233
  });
5035
5234
  const timer = setTimeout(() => {
5036
5235
  child.kill("SIGKILL");
5037
- resolve20({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
5236
+ resolve18({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
5038
5237
  }, timeoutMs);
5039
5238
  child.on("error", (e) => {
5040
5239
  clearTimeout(timer);
5041
- resolve20({ exitCode: -1, stderr: `${stderr}${e.message}` });
5240
+ resolve18({ exitCode: -1, stderr: `${stderr}${e.message}` });
5042
5241
  });
5043
5242
  child.on("close", (code) => {
5044
5243
  clearTimeout(timer);
5045
- resolve20({ exitCode: code ?? -1, stderr });
5244
+ resolve18({ exitCode: code ?? -1, stderr });
5046
5245
  });
5047
5246
  });
5048
5247
  }
@@ -5212,7 +5411,7 @@ ${newEntry}
5212
5411
  }
5213
5412
  });
5214
5413
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
5215
- return new Promise((resolve20) => {
5414
+ return new Promise((resolve18) => {
5216
5415
  const isWin = process.platform === "win32";
5217
5416
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
5218
5417
  let stdout2 = "";
@@ -5221,15 +5420,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
5221
5420
  });
5222
5421
  const timer = setTimeout(() => {
5223
5422
  child.kill("SIGKILL");
5224
- resolve20({ sha: "", exit: -1 });
5423
+ resolve18({ sha: "", exit: -1 });
5225
5424
  }, timeoutMs);
5226
5425
  child.on("error", () => {
5227
5426
  clearTimeout(timer);
5228
- resolve20({ sha: "", exit: -1 });
5427
+ resolve18({ sha: "", exit: -1 });
5229
5428
  });
5230
5429
  child.on("close", (code) => {
5231
5430
  clearTimeout(timer);
5232
- resolve20({ sha: stdout2.trim(), exit: code ?? -1 });
5431
+ resolve18({ sha: stdout2.trim(), exit: code ?? -1 });
5233
5432
  });
5234
5433
  });
5235
5434
  }
@@ -6170,8 +6369,14 @@ var init_install_base = __esm({
6170
6369
  // src/checkpoint/nextStep.ts
6171
6370
  var nextStep_exports = {};
6172
6371
  __export(nextStep_exports, {
6372
+ resolveAutoTransition: () => resolveAutoTransition,
6173
6373
  resolveNext: () => resolveNext
6174
6374
  });
6375
+ function resolveAutoTransition(envelopeValue, env = process.env.HARNESSED_AUTO_TRANSITION) {
6376
+ if (env === "true") return true;
6377
+ if (env === "false") return false;
6378
+ return envelopeValue ?? true;
6379
+ }
6175
6380
  function resolveNext(ledger, autoTransition) {
6176
6381
  const sub = nextPending(ledger);
6177
6382
  if (sub === null) return { next: "done" };
@@ -6768,7 +6973,7 @@ function renderHumanTable(records) {
6768
6973
  }
6769
6974
  }
6770
6975
  function pipeToJq(filterExpr, lines) {
6771
- return new Promise((resolve20, reject) => {
6976
+ return new Promise((resolve18, reject) => {
6772
6977
  const child = spawn("jq", [filterExpr], {
6773
6978
  stdio: ["pipe", "inherit", "inherit"],
6774
6979
  windowsHide: true
@@ -6777,12 +6982,12 @@ function pipeToJq(filterExpr, lines) {
6777
6982
  const e = err2;
6778
6983
  if (e.code === "ENOENT") {
6779
6984
  console.error(t("audit_log.jq_missing"));
6780
- resolve20(1);
6985
+ resolve18(1);
6781
6986
  } else {
6782
6987
  reject(err2);
6783
6988
  }
6784
6989
  });
6785
- child.on("close", (code) => resolve20(code ?? 0));
6990
+ child.on("close", (code) => resolve18(code ?? 0));
6786
6991
  child.stdin.write(lines.join("\n"));
6787
6992
  child.stdin.end();
6788
6993
  });
@@ -6999,6 +7204,35 @@ function registerCheckpoint(program2) {
6999
7204
  );
7000
7205
  }
7001
7206
  }
7207
+ if (allResolved) {
7208
+ try {
7209
+ const { defaultShipReady: defaultShipReady2 } = await Promise.resolve().then(() => (init_shipReady(), shipReady_exports));
7210
+ const { incrementPhases: incrementPhases2, isRetroDue: isRetroDue2, retroThreshold: retroThreshold2 } = await Promise.resolve().then(() => (init_retroMeta(), retroMeta_exports));
7211
+ const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow4, writeCurrentWorkflow: writeCurrentWorkflow3 } = await Promise.resolve().then(() => (init_state(), state_exports));
7212
+ const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
7213
+ const ship = defaultShipReady2(process.cwd());
7214
+ const key = repoKey2();
7215
+ const threshold = retroThreshold2(process.env);
7216
+ let retroDue = false;
7217
+ await mutateStore2((store) => {
7218
+ const meta = { ...store.retro_meta ?? {} };
7219
+ const next = incrementPhases2(meta[key]);
7220
+ meta[key] = next;
7221
+ retroDue = isRetroDue2(next.phases_since_retro, threshold);
7222
+ return { ...store, retro_meta: meta };
7223
+ });
7224
+ const latest2 = await readCurrentWorkflow4();
7225
+ if (latest2) {
7226
+ await writeCurrentWorkflow3({
7227
+ ...latest2,
7228
+ ship_ready: ship.ready,
7229
+ ship_commits: ship.commits,
7230
+ retro_due: retroDue
7231
+ });
7232
+ }
7233
+ } catch {
7234
+ }
7235
+ }
7002
7236
  console.log(`[harnessed] checkpoint complete: ${sub} (evidence: ${evidenceStatus})`);
7003
7237
  process.exit(0);
7004
7238
  return;
@@ -7315,24 +7549,26 @@ function registerInstall(program2) {
7315
7549
  const { resolveAlias: resolveAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
7316
7550
  const resolvedName = resolveAlias2(name) ?? name;
7317
7551
  checkPathSafe(resolvedName);
7318
- const manifestPath = resolve(getPackageRoot(), `manifests/tools/${resolvedName}.yaml`);
7319
- const skillPackPath = resolve(getPackageRoot(), `manifests/skill-packs/${resolvedName}.yaml`);
7552
+ const candidatePaths = ["tools", "skill-packs", "optional"].map(
7553
+ (dir) => resolve(getPackageRoot(), `manifests/${dir}/${resolvedName}.yaml`)
7554
+ );
7320
7555
  let yamlSrc;
7321
- let chosenPath = manifestPath;
7322
- try {
7323
- yamlSrc = await readFile(manifestPath, "utf8");
7324
- } catch {
7556
+ let chosenPath;
7557
+ for (const p5 of candidatePaths) {
7325
7558
  try {
7326
- yamlSrc = await readFile(skillPackPath, "utf8");
7327
- chosenPath = skillPackPath;
7559
+ yamlSrc = await readFile(p5, "utf8");
7560
+ chosenPath = p5;
7561
+ break;
7328
7562
  } catch {
7329
- console.error(
7330
- `${t("install.manifest_not_found", { name: resolvedName })}
7331
- ${t("install.manifest_not_found.fix", { name: resolvedName })}`
7332
- );
7333
- process.exit(1);
7334
7563
  }
7335
7564
  }
7565
+ if (yamlSrc === void 0 || chosenPath === void 0) {
7566
+ console.error(
7567
+ `${t("install.manifest_not_found", { name: resolvedName })}
7568
+ ${t("install.manifest_not_found.fix", { name: resolvedName })}`
7569
+ );
7570
+ process.exit(1);
7571
+ }
7336
7572
  const v = validateManifestFile(yamlSrc, chosenPath);
7337
7573
  if (!v.ok) {
7338
7574
  for (const e of v.errors) console.error(`error: ${e.message} at ${e.path}`);
@@ -7392,6 +7628,181 @@ function registerLearn(program2) {
7392
7628
  process.exit(0);
7393
7629
  });
7394
7630
  }
7631
+
7632
+ // src/cli/lib/here.ts
7633
+ init_nextStep();
7634
+ init_harnessedRoot();
7635
+
7636
+ // src/cli/status.ts
7637
+ init_evidence();
7638
+ init_ledger();
7639
+ init_state();
7640
+ init_harnessedRoot();
7641
+ init_state2();
7642
+ function statusMarker(status) {
7643
+ switch (status) {
7644
+ case "done":
7645
+ return "\u2705 done";
7646
+ case "pending":
7647
+ return "\u23F3 pending";
7648
+ case "failed":
7649
+ return "\u2717 failed";
7650
+ case "skipped":
7651
+ return "\u2B1C skipped";
7652
+ case "rejected":
7653
+ return "\u{1F6AB} rejected";
7654
+ }
7655
+ }
7656
+ function evidenceLabel(entry) {
7657
+ switch (entry.evidence_status) {
7658
+ case "verified":
7659
+ return "evidence: verified";
7660
+ case "overridden":
7661
+ return "evidence: overridden (--force)";
7662
+ default:
7663
+ return "evidence: none_declared";
7664
+ }
7665
+ }
7666
+ async function buildRecoverLines(workflow, ledger) {
7667
+ const lines = [];
7668
+ if (!workflow || ledger.length === 0) {
7669
+ lines.push("no ledger \u2014 run gates + start");
7670
+ return lines;
7671
+ }
7672
+ lines.push(`workflow: ${workflow.phase} (${workflow.status}) started ${workflow.started_at}`);
7673
+ const next = nextPending(ledger);
7674
+ for (const e of ledger) {
7675
+ const marker = statusMarker(e.status);
7676
+ let suffix = "";
7677
+ if (e.status === "skipped") {
7678
+ suffix = e.reason ? ` (skipped: ${e.reason})` : "";
7679
+ } else if (e.status === "done") {
7680
+ suffix = ` ${evidenceLabel(e)}`;
7681
+ }
7682
+ const arrow = e.sub === next ? " \u2190 next" : "";
7683
+ lines.push(` ${e.sub} ${marker}${suffix}${arrow}`);
7684
+ }
7685
+ if (next) {
7686
+ lines.push(`\u2192 next: harnessed prompt ${next}`);
7687
+ } else {
7688
+ lines.push("\u2192 all subs resolved (no pending work)");
7689
+ }
7690
+ for (const e of ledger) {
7691
+ if (!e.evidence || e.evidence.length === 0) continue;
7692
+ const drift = await detectDrift(e.evidence);
7693
+ for (const d of drift) {
7694
+ const now = d.now === "" ? "missing" : `${d.now.slice(0, 7)}\u2026`;
7695
+ lines.push(`\u26A0 drift: ${d.path} sha256 changed (was ${d.was.slice(0, 7)}\u2026, now ${now})`);
7696
+ }
7697
+ }
7698
+ return lines;
7699
+ }
7700
+ function registerStatus(program2) {
7701
+ program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").option("--recover", "structured recovery view of the active workflow ledger (v5.0 Spec 1 B)").action(async (opts) => {
7702
+ if (opts.recover) {
7703
+ const wf = await readCurrentWorkflow();
7704
+ const ledger = wf?.sub_progress ?? [];
7705
+ const lines = await buildRecoverLines(wf, ledger);
7706
+ for (const l of lines) console.log(l);
7707
+ return;
7708
+ }
7709
+ const state = await readState(process.cwd());
7710
+ const names = Object.keys(state.installed).sort();
7711
+ if (names.length === 0) {
7712
+ console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
7713
+ } else {
7714
+ for (const n of names) {
7715
+ const e = state.installed[n];
7716
+ if (!e) continue;
7717
+ console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
7718
+ }
7719
+ console.log(t("status.summary_installs", { count: names.length }));
7720
+ }
7721
+ const lockPath = harnessedFile(".lock");
7722
+ try {
7723
+ const isLocked = await lockfile.check(getHarnessedRoot(), {
7724
+ lockfilePath: lockPath,
7725
+ stale: 1e4
7726
+ });
7727
+ if (isLocked) {
7728
+ const s = await stat(lockPath);
7729
+ const ageMs = Date.now() - s.mtime.getTime();
7730
+ const stale = ageMs > 1e4;
7731
+ console.log(
7732
+ t("status.lock_held", {
7733
+ since: s.mtime.toISOString(),
7734
+ staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
7735
+ })
7736
+ );
7737
+ console.log(t("status.lock_release_hint", { path: lockPath }));
7738
+ } else {
7739
+ console.log(t("status.lock_free"));
7740
+ }
7741
+ } catch {
7742
+ }
7743
+ });
7744
+ }
7745
+
7746
+ // src/cli/lib/here.ts
7747
+ function parseBareInvocation(argvRest) {
7748
+ const tokens = [];
7749
+ for (let i = 0; i < argvRest.length; i++) {
7750
+ const a = argvRest[i];
7751
+ if (a === "--lang") {
7752
+ i++;
7753
+ continue;
7754
+ }
7755
+ if (a?.startsWith("--lang=")) continue;
7756
+ if (a !== void 0) tokens.push(a);
7757
+ }
7758
+ if (tokens.length === 0) return { here: true, json: false };
7759
+ if (tokens.length === 1 && tokens[0] === "--json") return { here: true, json: true };
7760
+ return { here: false, json: false };
7761
+ }
7762
+ async function buildHereView(wf, ledger, auto) {
7763
+ if (!wf) {
7764
+ return {
7765
+ lines: [
7766
+ t("here.no_workflow", { path: harnessedFile("workflows.json") }),
7767
+ t("here.no_workflow.hint")
7768
+ ],
7769
+ json: { active: false }
7770
+ };
7771
+ }
7772
+ const body = await buildRecoverLines(wf, ledger);
7773
+ const step = resolveNext(ledger, auto);
7774
+ const lines = [
7775
+ ...body,
7776
+ `NEXT: ${step.next}`,
7777
+ ...step.sub ? [`\u2192 run: harnessed prompt ${step.sub}`] : []
7778
+ ];
7779
+ return {
7780
+ lines,
7781
+ json: {
7782
+ active: true,
7783
+ phase: wf.phase,
7784
+ status: wf.status,
7785
+ started_at: wf.started_at,
7786
+ next: step.next,
7787
+ sub: step.sub,
7788
+ hint: step.hint,
7789
+ sub_progress: ledger
7790
+ }
7791
+ };
7792
+ }
7793
+ async function runHere(opts) {
7794
+ const { readCurrentWorkflow: readCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
7795
+ const wf = await readCurrentWorkflow2();
7796
+ const ledger = wf?.sub_progress ?? [];
7797
+ const auto = resolveAutoTransition(wf?.auto_transition);
7798
+ const view = await buildHereView(wf, ledger, auto);
7799
+ if (opts.json) {
7800
+ console.log(JSON.stringify(view.json, null, 2));
7801
+ } else {
7802
+ for (const l of view.lines) console.log(l);
7803
+ }
7804
+ process.exit(0);
7805
+ }
7395
7806
  var QA = [
7396
7807
  { q: "\u2460 \u662F\u771F reusable surface \u8FD8\u662F\u4E34\u65F6 wrapper?", f: "q1_reusable_surface" },
7397
7808
  { q: "\u2461 \u4E0A\u6E38\u540D\u5B57 fit \u9879\u76EE shape \u5417? \u6709\u73B0\u6709\u547D\u540D\u51B2\u7A81\u5417?", f: "q2_name_fit" },
@@ -7445,21 +7856,15 @@ function registerManifestAdd(program2) {
7445
7856
  }
7446
7857
 
7447
7858
  // src/cli/next.ts
7448
- function resolveAutoTransition(envelopeValue) {
7449
- const env = process.env.HARNESSED_AUTO_TRANSITION;
7450
- if (env === "true") return true;
7451
- if (env === "false") return false;
7452
- return envelopeValue ?? true;
7453
- }
7454
7859
  function registerNext(program2) {
7455
7860
  program2.command("next").description(
7456
7861
  "Print the deterministic next-step contract (NEXT: auto|manual|done) for the active workflow"
7457
7862
  ).action(async () => {
7458
7863
  const { readCurrentWorkflow: readCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
7459
- const { resolveNext: resolveNext2 } = await Promise.resolve().then(() => (init_nextStep(), nextStep_exports));
7864
+ const { resolveNext: resolveNext2, resolveAutoTransition: resolveAutoTransition2 } = await Promise.resolve().then(() => (init_nextStep(), nextStep_exports));
7460
7865
  const current = await readCurrentWorkflow2();
7461
7866
  const ledger = current?.sub_progress ?? [];
7462
- const auto = resolveAutoTransition(current?.auto_transition);
7867
+ const auto = resolveAutoTransition2(current?.auto_transition);
7463
7868
  const step = resolveNext2(ledger, auto);
7464
7869
  console.log(`NEXT: ${step.next}`);
7465
7870
  if (step.sub) console.log(`SUB: ${step.sub}`);
@@ -7743,6 +8148,37 @@ function registerResume(program2) {
7743
8148
  process.exit(0);
7744
8149
  });
7745
8150
  }
8151
+
8152
+ // src/cli/retro.ts
8153
+ init_retroMeta();
8154
+ async function runRetroDone(now) {
8155
+ const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow2, writeCurrentWorkflow: writeCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
8156
+ const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
8157
+ const key = repoKey2();
8158
+ await mutateStore2((store) => {
8159
+ const meta = { ...store.retro_meta ?? {} };
8160
+ meta[key] = resetRetro(now);
8161
+ return { ...store, retro_meta: meta };
8162
+ });
8163
+ const wf = await readCurrentWorkflow2();
8164
+ if (wf?.retro_due) await writeCurrentWorkflow2({ ...wf, retro_due: false });
8165
+ return { reset: true };
8166
+ }
8167
+ function registerRetro(program2) {
8168
+ program2.command("retro").description("Retro cadence: --done resets the phase counter after running /retro").option(
8169
+ "--done",
8170
+ "mark a retro as done \u2014 reset the phase counter + clear the RETRO-DUE reminder"
8171
+ ).action(async (opts) => {
8172
+ if (!opts.done) {
8173
+ console.error("[harnessed] retro: nothing to do \u2014 pass --done after running /retro");
8174
+ process.exit(1);
8175
+ return;
8176
+ }
8177
+ await runRetroDone((/* @__PURE__ */ new Date()).toISOString());
8178
+ console.log("[harnessed] retro recorded \u2014 phase counter reset, RETRO-DUE reminder cleared");
8179
+ process.exit(0);
8180
+ });
8181
+ }
7746
8182
  init_backup();
7747
8183
  function normalizeEol(buf, eol) {
7748
8184
  const lf = buf.toString("utf8").replace(/\r\n/g, "\n");
@@ -7814,9 +8250,13 @@ ${t("rollback.metadata_unreadable.fix")}`
7814
8250
 
7815
8251
  // src/cli.ts
7816
8252
  init_run2();
8253
+ init_platform();
8254
+
8255
+ // src/cli/lib/capabilityResolver.ts
8256
+ init_platform();
7817
8257
  function readInstalledPlugins(homedirOverride) {
7818
- const home = homedir();
7819
- const path = join(home, ".claude", "plugins", "installed_plugins.json");
8258
+ const path = getPluginsRegistry(homedirOverride);
8259
+ if (path === null) return /* @__PURE__ */ new Set();
7820
8260
  let raw;
7821
8261
  try {
7822
8262
  raw = readFileSync(path, "utf8");
@@ -7841,8 +8281,7 @@ function readInstalledPlugins(homedirOverride) {
7841
8281
  return out;
7842
8282
  }
7843
8283
  function readInstalledUserSkills(homedirOverride) {
7844
- const home = homedir();
7845
- const skillsRoot = join(home, ".claude", "skills");
8284
+ const skillsRoot = getSkillsDir(homedirOverride);
7846
8285
  try {
7847
8286
  const entries = readdirSync(skillsRoot, { withFileTypes: true });
7848
8287
  const out = /* @__PURE__ */ new Set();
@@ -7919,55 +8358,57 @@ function renderSkillBody(body, capabilities, installedPlugins, installedUserSkil
7919
8358
  }
7920
8359
 
7921
8360
  // src/cli/lib/enableAgentTeamsInSettings.ts
8361
+ init_platform();
8362
+
8363
+ // src/cli/lib/settingsWriter.ts
7922
8364
  init_harnessedRoot();
7923
- function settingsPath() {
7924
- return resolve(homedir(), ".claude", "settings.json");
7925
- }
7926
- async function enableAgentTeamsInSettings() {
7927
- const path = settingsPath();
8365
+ init_platform();
8366
+ async function mergeSettingsEnvKey(key, value, opts = {}) {
8367
+ const path = getSettingsPath();
7928
8368
  let raw;
7929
8369
  try {
7930
8370
  raw = await readFile(path, "utf8");
7931
8371
  } catch (err2) {
7932
8372
  const code = err2.code;
7933
8373
  if (code !== "ENOENT") {
7934
- return { status: "warn", message: `read ${path} failed: ${err2.message}` };
8374
+ return { outcome: "warn", message: `read ${path} failed: ${err2.message}` };
7935
8375
  }
7936
- return createFreshSettings(path);
8376
+ return createFresh(path, key, value);
7937
8377
  }
7938
8378
  let data;
7939
8379
  try {
7940
8380
  const parsed = JSON.parse(raw);
7941
8381
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
7942
- return { status: "warn", message: `${path} is not a JSON object` };
8382
+ return { outcome: "warn", message: `${path} is not a JSON object` };
7943
8383
  }
7944
8384
  data = parsed;
7945
8385
  } catch (err2) {
7946
- return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
8386
+ return { outcome: "warn", message: `${path} malformed JSON: ${err2.message}` };
7947
8387
  }
7948
8388
  const env = data.env ?? {};
7949
- if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1") {
7950
- return { status: "already-enabled", path };
8389
+ const existing = env[key];
8390
+ if (typeof existing === "string" && existing.length > 0 && opts.skipIfPresent?.(existing) === true) {
8391
+ return { outcome: "already", path, existing };
7951
8392
  }
7952
- const backupPath = await backupOriginal(raw);
7953
- if (backupPath.status === "warn") return backupPath;
7954
- data.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
8393
+ const backup2 = await backupOriginal(raw);
8394
+ if (backup2.status === "warn") return { outcome: "warn", message: backup2.message };
8395
+ data.env = { ...env, [key]: value };
7955
8396
  const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
7956
8397
  `);
7957
- if (writeErr) return { status: "warn", message: writeErr };
7958
- return { status: "enabled", path, backupPath: backupPath.path };
8398
+ if (writeErr) return { outcome: "warn", message: writeErr };
8399
+ return { outcome: "merged", path, backupPath: backup2.path };
7959
8400
  }
7960
- async function createFreshSettings(path) {
7961
- const data = { env: { CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" } };
8401
+ async function createFresh(path, key, value) {
8402
+ const data = { env: { [key]: value } };
7962
8403
  try {
7963
- await mkdir(join(homedir(), ".claude"), { recursive: true });
8404
+ await mkdir(dirname(path), { recursive: true });
7964
8405
  } catch (err2) {
7965
- return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
8406
+ return { outcome: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
7966
8407
  }
7967
8408
  const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
7968
8409
  `);
7969
- if (writeErr) return { status: "warn", message: writeErr };
7970
- return { status: "created", path };
8410
+ if (writeErr) return { outcome: "warn", message: writeErr };
8411
+ return { outcome: "created", path };
7971
8412
  }
7972
8413
  async function backupOriginal(raw) {
7973
8414
  const backupRoot = harnessedSubdir("backups");
@@ -7992,11 +8433,34 @@ async function atomicWrite(path, content) {
7992
8433
  }
7993
8434
  }
7994
8435
 
7995
- // src/cli/lib/enableUserLangInSettings.ts
7996
- init_harnessedRoot();
7997
- function settingsPath2() {
7998
- return resolve(homedir(), ".claude", "settings.json");
8436
+ // src/cli/lib/enableAgentTeamsInSettings.ts
8437
+ var ENV_KEY = "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS";
8438
+ async function enableAgentTeamsInSettings() {
8439
+ const platform = detectPlatform();
8440
+ if (!platform.supportsEnvKeyWrite) {
8441
+ return {
8442
+ status: "warn",
8443
+ message: `platform '${platform.id}' does not support env-key settings writes (capability-absent) \u2014 ${ENV_KEY} skipped`
8444
+ };
8445
+ }
8446
+ const r = await mergeSettingsEnvKey(ENV_KEY, "1", {
8447
+ skipIfPresent: (existing) => existing === "1"
8448
+ });
8449
+ switch (r.outcome) {
8450
+ case "created":
8451
+ return { status: "created", path: r.path };
8452
+ case "already":
8453
+ return { status: "already-enabled", path: r.path };
8454
+ case "merged":
8455
+ return { status: "enabled", path: r.path, backupPath: r.backupPath };
8456
+ case "warn":
8457
+ return { status: "warn", message: r.message };
8458
+ }
7999
8459
  }
8460
+
8461
+ // src/cli/lib/enableUserLangInSettings.ts
8462
+ init_platform();
8463
+ var ENV_KEY2 = "HARNESSED_USER_LANG";
8000
8464
  function detectUserLang(override) {
8001
8465
  if (override) {
8002
8466
  if (/^zh([^a-z]|$)/i.test(override)) return "zh-Hans";
@@ -8014,73 +8478,26 @@ function safeIntlLocale2() {
8014
8478
  }
8015
8479
  }
8016
8480
  async function enableUserLangInSettings(override) {
8017
- const path = settingsPath2();
8018
- const detected = detectUserLang(override);
8019
- let raw;
8020
- try {
8021
- raw = await readFile(path, "utf8");
8022
- } catch (err2) {
8023
- const code = err2.code;
8024
- if (code !== "ENOENT") {
8025
- return { status: "warn", message: `read ${path} failed: ${err2.message}` };
8026
- }
8027
- return createFreshSettings2(path, detected);
8028
- }
8029
- let data;
8030
- try {
8031
- const parsed = JSON.parse(raw);
8032
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8033
- return { status: "warn", message: `${path} is not a JSON object` };
8034
- }
8035
- data = parsed;
8036
- } catch (err2) {
8037
- return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
8038
- }
8039
- const env = data.env ?? {};
8040
- const existing = env.HARNESSED_USER_LANG;
8041
- if (typeof existing === "string" && existing.length > 0 && override === void 0) {
8042
- return { status: "already-set", path, existing };
8043
- }
8044
- const backupPath = await backupOriginal2(raw);
8045
- if (backupPath.status === "warn") return backupPath;
8046
- data.env = { ...env, HARNESSED_USER_LANG: detected };
8047
- const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
8048
- `);
8049
- if (writeErr) return { status: "warn", message: writeErr };
8050
- return { status: "enabled", path, backupPath: backupPath.path, detected };
8051
- }
8052
- async function createFreshSettings2(path, detected) {
8053
- const data = { env: { HARNESSED_USER_LANG: detected } };
8054
- try {
8055
- await mkdir(join(homedir(), ".claude"), { recursive: true });
8056
- } catch (err2) {
8057
- return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
8058
- }
8059
- const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
8060
- `);
8061
- if (writeErr) return { status: "warn", message: writeErr };
8062
- return { status: "created", path, detected };
8063
- }
8064
- async function backupOriginal2(raw) {
8065
- const backupRoot = harnessedSubdir("backups");
8066
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
8067
- const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
8068
- try {
8069
- await mkdir(backupRoot, { recursive: true });
8070
- await writeFile(backupPath, raw, "utf8");
8071
- return { status: "ok", path: backupPath };
8072
- } catch (err2) {
8073
- return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
8481
+ const platform = detectPlatform();
8482
+ if (!platform.supportsEnvKeyWrite) {
8483
+ return {
8484
+ status: "warn",
8485
+ message: `platform '${platform.id}' does not support env-key settings writes (capability-absent) \u2014 ${ENV_KEY2} skipped`
8486
+ };
8074
8487
  }
8075
- }
8076
- async function atomicWrite2(path, content) {
8077
- const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
8078
- try {
8079
- await writeFile(tmpPath, content, "utf8");
8080
- await rename(tmpPath, path);
8081
- return void 0;
8082
- } catch (err2) {
8083
- return `write ${path} failed: ${err2.message}`;
8488
+ const detected = detectUserLang(override);
8489
+ const r = await mergeSettingsEnvKey(ENV_KEY2, detected, {
8490
+ skipIfPresent: () => override === void 0
8491
+ });
8492
+ switch (r.outcome) {
8493
+ case "created":
8494
+ return { status: "created", path: r.path, detected };
8495
+ case "already":
8496
+ return { status: "already-set", path: r.path, existing: r.existing };
8497
+ case "merged":
8498
+ return { status: "enabled", path: r.path, backupPath: r.backupPath, detected };
8499
+ case "warn":
8500
+ return { status: "warn", message: r.message };
8084
8501
  }
8085
8502
  }
8086
8503
 
@@ -8140,8 +8557,8 @@ async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOver
8140
8557
  ]
8141
8558
  };
8142
8559
  }
8143
- const installedPlugins = readInstalledPlugins();
8144
- const installedUserSkills = readInstalledUserSkills();
8560
+ const installedPlugins = readInstalledPlugins(homedirOverride);
8561
+ const installedUserSkills = readInstalledUserSkills(homedirOverride);
8145
8562
  const results = [];
8146
8563
  const warningSet = /* @__PURE__ */ new Set();
8147
8564
  for (const name of skillNames) {
@@ -8304,6 +8721,23 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
8304
8721
  }
8305
8722
 
8306
8723
  // src/cli/setup.ts
8724
+ var KNOWN_PLATFORMS = ["claude", "codex"];
8725
+ async function applyPlatformOption(platform) {
8726
+ if (!KNOWN_PLATFORMS.includes(platform)) {
8727
+ console.error(
8728
+ `--platform: unknown id '${platform}' (expected one of: ${KNOWN_PLATFORMS.join(" | ")})`
8729
+ );
8730
+ process.exit(1);
8731
+ }
8732
+ process.env.HARNESSED_PLATFORM = platform;
8733
+ const stateRoot = detectPlatform().stateRoot;
8734
+ try {
8735
+ await mkdir(stateRoot, { recursive: true });
8736
+ await writeFile(join(stateRoot, ".platform"), platform, "utf8");
8737
+ } catch (e) {
8738
+ console.warn(` [--platform] could not persist .platform pin (${e.message})`);
8739
+ }
8740
+ }
8307
8741
  async function listBaseManifests2(pkgRoot) {
8308
8742
  const out = [];
8309
8743
  for (const d of ["manifests/tools", "manifests/skill-packs"]) {
@@ -8356,14 +8790,15 @@ function registerSetup(program2) {
8356
8790
  ).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
8357
8791
  "--user-lang <code>",
8358
8792
  "override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
8359
- ).option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").option(
8793
+ ).option("--platform <id>", "target harness platform (claude | codex); default: auto-detect").option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").option(
8360
8794
  "--update-installed",
8361
8795
  "force re-install already-installed plugins (excludes MCP servers); default: skip if installed"
8362
8796
  ).action(async (raw) => {
8363
8797
  const dryRun = raw.dryRun === true;
8798
+ if (raw.platform !== void 0) await applyPlatformOption(raw.platform);
8364
8799
  const pkgRoot = getPackageRoot();
8365
8800
  const workflowsDir = resolve(pkgRoot, "workflows");
8366
- const skillsBase = resolve(homedir(), ".claude", "skills");
8801
+ const skillsBase = getSkillsDir();
8367
8802
  await warnIfAgentTeamsMissing();
8368
8803
  let entries;
8369
8804
  try {
@@ -8407,7 +8842,7 @@ function registerSetup(program2) {
8407
8842
  console.warn(` - ${w}`);
8408
8843
  }
8409
8844
  }
8410
- const commandsBase = resolve(homedir(), ".claude", "commands");
8845
+ const commandsBase = getCommandsDir();
8411
8846
  try {
8412
8847
  await mkdir(commandsBase, { recursive: true });
8413
8848
  } catch (e) {
@@ -8502,116 +8937,6 @@ Force-update pass complete: ${b2.installed.length} installed / ${b2.alreadyInsta
8502
8937
  process.exit(0);
8503
8938
  });
8504
8939
  }
8505
-
8506
- // src/cli/status.ts
8507
- init_evidence();
8508
- init_ledger();
8509
- init_state();
8510
- init_harnessedRoot();
8511
- init_state2();
8512
- function statusMarker(status) {
8513
- switch (status) {
8514
- case "done":
8515
- return "\u2705 done";
8516
- case "pending":
8517
- return "\u23F3 pending";
8518
- case "failed":
8519
- return "\u2717 failed";
8520
- case "skipped":
8521
- return "\u2B1C skipped";
8522
- case "rejected":
8523
- return "\u{1F6AB} rejected";
8524
- }
8525
- }
8526
- function evidenceLabel(entry) {
8527
- switch (entry.evidence_status) {
8528
- case "verified":
8529
- return "evidence: verified";
8530
- case "overridden":
8531
- return "evidence: overridden (--force)";
8532
- default:
8533
- return "evidence: none_declared";
8534
- }
8535
- }
8536
- async function buildRecoverLines(workflow, ledger) {
8537
- const lines = [];
8538
- if (!workflow || ledger.length === 0) {
8539
- lines.push("no ledger \u2014 run gates + start");
8540
- return lines;
8541
- }
8542
- lines.push(`workflow: ${workflow.phase} (${workflow.status}) started ${workflow.started_at}`);
8543
- const next = nextPending(ledger);
8544
- for (const e of ledger) {
8545
- const marker = statusMarker(e.status);
8546
- let suffix = "";
8547
- if (e.status === "skipped") {
8548
- suffix = e.reason ? ` (skipped: ${e.reason})` : "";
8549
- } else if (e.status === "done") {
8550
- suffix = ` ${evidenceLabel(e)}`;
8551
- }
8552
- const arrow = e.sub === next ? " \u2190 next" : "";
8553
- lines.push(` ${e.sub} ${marker}${suffix}${arrow}`);
8554
- }
8555
- if (next) {
8556
- lines.push(`\u2192 next: harnessed prompt ${next}`);
8557
- } else {
8558
- lines.push("\u2192 all subs resolved (no pending work)");
8559
- }
8560
- for (const e of ledger) {
8561
- if (!e.evidence || e.evidence.length === 0) continue;
8562
- const drift = await detectDrift(e.evidence);
8563
- for (const d of drift) {
8564
- const now = d.now === "" ? "missing" : `${d.now.slice(0, 7)}\u2026`;
8565
- lines.push(`\u26A0 drift: ${d.path} sha256 changed (was ${d.was.slice(0, 7)}\u2026, now ${now})`);
8566
- }
8567
- }
8568
- return lines;
8569
- }
8570
- function registerStatus(program2) {
8571
- program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").option("--recover", "structured recovery view of the active workflow ledger (v5.0 Spec 1 B)").action(async (opts) => {
8572
- if (opts.recover) {
8573
- const wf = await readCurrentWorkflow();
8574
- const ledger = wf?.sub_progress ?? [];
8575
- const lines = await buildRecoverLines(wf, ledger);
8576
- for (const l of lines) console.log(l);
8577
- return;
8578
- }
8579
- const state = await readState(process.cwd());
8580
- const names = Object.keys(state.installed).sort();
8581
- if (names.length === 0) {
8582
- console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
8583
- } else {
8584
- for (const n of names) {
8585
- const e = state.installed[n];
8586
- if (!e) continue;
8587
- console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
8588
- }
8589
- console.log(t("status.summary_installs", { count: names.length }));
8590
- }
8591
- const lockPath = harnessedFile(".lock");
8592
- try {
8593
- const isLocked = await lockfile.check(getHarnessedRoot(), {
8594
- lockfilePath: lockPath,
8595
- stale: 1e4
8596
- });
8597
- if (isLocked) {
8598
- const s = await stat(lockPath);
8599
- const ageMs = Date.now() - s.mtime.getTime();
8600
- const stale = ageMs > 1e4;
8601
- console.log(
8602
- t("status.lock_held", {
8603
- since: s.mtime.toISOString(),
8604
- staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
8605
- })
8606
- );
8607
- console.log(t("status.lock_release_hint", { path: lockPath }));
8608
- } else {
8609
- console.log(t("status.lock_free"));
8610
- }
8611
- } catch {
8612
- }
8613
- });
8614
- }
8615
8940
  init_harnessedRoot();
8616
8941
  init_path_guard();
8617
8942
  init_validate();
@@ -8630,10 +8955,10 @@ var uninstallCcHookAdd = async (ctx) => {
8630
8955
  }
8631
8956
  const abort = dryRunGate(ctx);
8632
8957
  if (abort) return abort;
8633
- const settingsPath3 = join(homedir(), ".claude", "settings.json");
8958
+ const settingsPath = join(homedir(), ".claude", "settings.json");
8634
8959
  let existing;
8635
8960
  try {
8636
- existing = await readFile(settingsPath3, "utf8");
8961
+ existing = await readFile(settingsPath, "utf8");
8637
8962
  } catch {
8638
8963
  return { ok: true, removedPaths: [] };
8639
8964
  }
@@ -8662,8 +8987,8 @@ var uninstallCcHookAdd = async (ctx) => {
8662
8987
  if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
8663
8988
  const newText = `${JSON.stringify(settings, null, 2)}
8664
8989
  `;
8665
- await writeFile(settingsPath3, newText);
8666
- return { ok: true, removedPaths: [settingsPath3] };
8990
+ await writeFile(settingsPath, newText);
8991
+ return { ok: true, removedPaths: [settingsPath] };
8667
8992
  };
8668
8993
 
8669
8994
  // src/uninstallers/ccPluginMarketplace.ts
@@ -8802,7 +9127,7 @@ var uninstallNpmCli = async (ctx) => {
8802
9127
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
8803
9128
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
8804
9129
  const isWin = process.platform === "win32";
8805
- const result = await new Promise((resolve20) => {
9130
+ const result = await new Promise((resolve18) => {
8806
9131
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
8807
9132
  let stderr = "";
8808
9133
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -8810,15 +9135,15 @@ var uninstallNpmCli = async (ctx) => {
8810
9135
  });
8811
9136
  const timer = setTimeout(() => {
8812
9137
  child.kill("SIGKILL");
8813
- resolve20({ exitCode: -1, stderr: `${stderr}[timeout]` });
9138
+ resolve18({ exitCode: -1, stderr: `${stderr}[timeout]` });
8814
9139
  }, 3e4);
8815
9140
  child.on("error", (e) => {
8816
9141
  clearTimeout(timer);
8817
- resolve20({ exitCode: -1, stderr: e.message });
9142
+ resolve18({ exitCode: -1, stderr: e.message });
8818
9143
  });
8819
9144
  child.on("close", (code) => {
8820
9145
  clearTimeout(timer);
8821
- resolve20({ exitCode: code ?? -1, stderr });
9146
+ resolve18({ exitCode: code ?? -1, stderr });
8822
9147
  });
8823
9148
  });
8824
9149
  if (result.exitCode !== 0) {
@@ -8884,9 +9209,9 @@ async function discoverCommandFiles(commandsDir) {
8884
9209
  }
8885
9210
  return owned;
8886
9211
  }
8887
- async function checkSettingsEnv(settingsPath3) {
9212
+ async function checkSettingsEnv(settingsPath) {
8888
9213
  try {
8889
- const raw = await readFile(settingsPath3, "utf8");
9214
+ const raw = await readFile(settingsPath, "utf8");
8890
9215
  const data = JSON.parse(raw);
8891
9216
  const env = data.env ?? {};
8892
9217
  return {
@@ -8897,8 +9222,8 @@ async function checkSettingsEnv(settingsPath3) {
8897
9222
  return { hasAgentTeams: false, hasUserLang: false };
8898
9223
  }
8899
9224
  }
8900
- async function removeSettingsEnv(settingsPath3) {
8901
- const raw = await readFile(settingsPath3, "utf8");
9225
+ async function removeSettingsEnv(settingsPath) {
9226
+ const raw = await readFile(settingsPath, "utf8");
8902
9227
  const data = JSON.parse(raw);
8903
9228
  const env = data.env ?? {};
8904
9229
  let changed = false;
@@ -8913,14 +9238,14 @@ async function removeSettingsEnv(settingsPath3) {
8913
9238
  if (!changed) return false;
8914
9239
  if (Object.keys(env).length === 0) delete data.env;
8915
9240
  else data.env = env;
8916
- await writeFile(settingsPath3, `${JSON.stringify(data, null, 2)}
9241
+ await writeFile(settingsPath, `${JSON.stringify(data, null, 2)}
8917
9242
  `, "utf8");
8918
9243
  return true;
8919
9244
  }
8920
9245
  async function runUnifiedUninstall(home, dryRun) {
8921
9246
  const commandsDir = join(home, ".claude", "commands");
8922
9247
  const skillsDir = join(home, ".claude", "skills");
8923
- const settingsPath3 = join(home, ".claude", "settings.json");
9248
+ const settingsPath = join(home, ".claude", "settings.json");
8924
9249
  const harnessedRoot = getHarnessedRoot();
8925
9250
  const pkgRoot = getPackageRoot();
8926
9251
  let workflowNames = [];
@@ -8931,7 +9256,7 @@ async function runUnifiedUninstall(home, dryRun) {
8931
9256
  } catch {
8932
9257
  }
8933
9258
  const commandFiles = await discoverCommandFiles(commandsDir);
8934
- const settingsEnv = await checkSettingsEnv(settingsPath3);
9259
+ const settingsEnv = await checkSettingsEnv(settingsPath);
8935
9260
  const hasSettingsChanges = settingsEnv.hasAgentTeams || settingsEnv.hasUserLang;
8936
9261
  const skillDirs = [];
8937
9262
  for (const name of workflowNames) {
@@ -8989,9 +9314,9 @@ async function runUnifiedUninstall(home, dryRun) {
8989
9314
  let removedSettings = false;
8990
9315
  if (hasSettingsChanges) {
8991
9316
  try {
8992
- removedSettings = await removeSettingsEnv(settingsPath3);
9317
+ removedSettings = await removeSettingsEnv(settingsPath);
8993
9318
  } catch (e) {
8994
- failures.push(`${settingsPath3}: ${e.message}`);
9319
+ failures.push(`${settingsPath}: ${e.message}`);
8995
9320
  }
8996
9321
  }
8997
9322
  const normalizedRoot = resolve(harnessedRoot);
@@ -9238,6 +9563,12 @@ registerWorkflows(program);
9238
9563
  registerLearn(program);
9239
9564
  registerUpdate(program);
9240
9565
  registerReleasePreflight(program);
9241
- program.parse(process.argv);
9566
+ registerRetro(program);
9567
+ var bare = parseBareInvocation(process.argv.slice(2));
9568
+ if (bare.here) {
9569
+ void runHere({ json: bare.json });
9570
+ } else {
9571
+ program.parse(process.argv);
9572
+ }
9242
9573
  //# sourceMappingURL=cli.mjs.map
9243
9574
  //# sourceMappingURL=cli.mjs.map