harnessed 4.5.0 โ†’ 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  ### ๐Ÿ” The operating loop
23
23
 
24
- > **Discuss โ†’ Plan โ†’ Build โ†’ Verify โ†’ Ship โ†’ Learn** โ€” one repeatable loop, machine-executed across the three-layer stack (gstack governance ยท GSD orchestration ยท superpowers TDD ยท checkpoint evidence). Raw agent work drifts; harnessed turns it into a source-of-truth path where progress, evidence, and learnings persist instead of living in chat.
24
+ > **Discuss โ†’ Plan โ†’ Build โ†’ Verify โ†’ Ship**, closed by a **Learn** loop โ€” machine-executed across the three-layer stack (gstack governance ยท GSD orchestration ยท superpowers TDD ยท checkpoint evidence). Raw agent work drifts; harnessed turns it into a source-of-truth path where progress and evidence persist instead of living in chat. **Learning is automatic**: every completed workflow appends its failure/loop/reject signals to `.planning/LEARNINGS.md`, which are injected into the next cycle โ€” this is always-on, **not** gated on the optional Retro. Retro (`/retro`) is a separate, optional milestone summary.
25
25
 
26
26
  ```mermaid
27
27
  flowchart LR
@@ -30,9 +30,9 @@ flowchart LR
30
30
  P --> T(["โ‘ข Task<br/>TDD build + checkpoint"])
31
31
  T --> V(["โ‘ฃ Verify<br/>independent review + evidence gate"])
32
32
  V --> S(["โ‘ค Ship<br/>release-preflight โ†’ tag-ready (publish via CI)"])
33
- S --> L(["โ‘ฅ Retro<br/>capture learnings โ†’ next session smarter"])
33
+ S -. "milestone summary" .-> RT(["Retro<br/>(optional)"]):::opt
34
34
  V -. "fail / gap" .-> T
35
- L -. "next requirement" .-> D
35
+ S == "๐Ÿ” Learn โ€” learnings captured on every workflow completion โ†’ injected next cycle" ==> D
36
36
  classDef opt stroke-dasharray:5,opacity:0.8
37
37
  ```
38
38
 
@@ -405,10 +405,18 @@ planning-with-files /plan (cross-cutting tool) โ†’ write artifacts to .planning/
405
405
  | `harnessed gates <master>` | Evaluate which sub-workflows fire for a master stage (JSON: fire/skip/parallelism). Used by slash commands to orchestrate native spawns. |
406
406
  | `harnessed prompt <sub>` | Output a spawn-ready prompt (role + checklist + disciplines + completion/clarification protocols) for a sub-workflow. |
407
407
  | `harnessed checkpoint <action> <sub>` | Record sub-workflow start/complete/fail to `~/.claude/harnessed/checkpoints/`. |
408
+ | `harnessed next` | Deterministic next-step contract (`NEXT: auto\|manual\|done`) for the active workflow. |
409
+ | `harnessed reject <sub>` | Mark a sub as user-rejected (terminal, distinct from `failed`). |
410
+ | `harnessed compact [--tokens <n>]` | Summarize+evict resolved ledger entries (G6-safe: `fail_count>0` never evicted); auto-triggers on `checkpoint complete --tokens`. |
411
+ | `harnessed workflows` | List in-flight workflows (one per repo). |
412
+ | `harnessed learn "<lesson>"` | Append a prose learning to this repo's `.planning/LEARNINGS.md`. |
408
413
  | `harnessed run <name>` | Run a workflow via in-process SDK spawn (CI/headless mode). Slash commands use CC-native spawn instead. |
409
414
  | `harnessed resume` | Resume from the most recent checkpoint after a session interruption |
410
415
  | `harnessed status` | Current phase + lock holder |
411
- | `harnessed doctor` | 8-check health check (Node / MCP / jq / Win bash / routing / token budget, etc.) |
416
+ | `harnessed doctor` | 14-check health check (Node / MCP / jq / Win bash / routing / token budget / mattpocock / CodeGraph / update-available, etc.) |
417
+ | `harnessed update [--check\|--upstreams\|--migration-report]` | Self-update (`npm i -g harnessed@latest`); `--check` reports the latest version; `--upstreams` re-runs the base manifests; `--migration-report` is a read-only stale-state inventory |
418
+ | `harnessed release-preflight` | Read-only release-readiness gate (CHANGELOG `[Unreleased]` / version / git-clean / tag-absent); exits 1 if not ready. The Ship-stage gate. |
419
+ | `harnessed retro --done` | Reset the retro-reminder phase counter after running `/retro` (clears the per-turn RETRO-DUE nudge). |
412
420
  | `harnessed install <name>` | Install an upstream manifest |
413
421
  | `harnessed uninstall [name]` | Reverse uninstall |
414
422
  | `harnessed backup` | Snapshot backup management |
@@ -65,6 +65,17 @@ function workflowStateBlock(wf) {
65
65
  `BREAK-LOOP: sub '${e.sub}' failed ${e.fail_count}x โ€” stop retrying, run break-loop skill`,
66
66
  )
67
67
  }
68
+ // Phase 22 โ€” smart-reminder lines from envelope flags (parity with injectState.ts).
69
+ if (wf.ship_ready) {
70
+ lines.push(
71
+ `SHIP-READY: ${wf.ship_commits ?? 0} commit(s) since the last release tag โ€” consider shipping (harnessed release-preflight, then /ship)`,
72
+ )
73
+ }
74
+ if (wf.retro_due) {
75
+ lines.push(
76
+ 'RETRO-DUE: enough phases completed since the last retro โ€” run /retro, then `harnessed retro --done`',
77
+ )
78
+ }
68
79
  lines.push('</workflow-state>')
69
80
  return lines.join('\n')
70
81
  }
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.0",
41
+ version: "4.6.0",
42
42
  description: "AI coding harness package manager + composition orchestrator",
43
43
  type: "module",
44
44
  license: "Apache-2.0",
@@ -944,6 +944,10 @@ async function backup(plan, ctx) {
944
944
  });
945
945
  continue;
946
946
  }
947
+ if (code === "EISDIR") {
948
+ entries.push({ target: file.target, backup: "", sha1: "", eol: "lf" });
949
+ continue;
950
+ }
947
951
  return {
948
952
  ok: false,
949
953
  error: {
@@ -1287,7 +1291,19 @@ var init_currentWorkflow_v1 = __esm({
1287
1291
  },
1288
1292
  { additionalProperties: false }
1289
1293
  )
1290
- )
1294
+ ),
1295
+ // Phase 22 โ€” smart reminders. Additive-optional flags set by `checkpoint
1296
+ // complete` (allResolved) and read by the G4 inject twins (booleans only; the
1297
+ // bin computes neither git nor thresholds). Old files without them Value.Check-pass
1298
+ // โ†’ NO schemaVersion bump.
1299
+ // ship_ready โ€” there are unshipped commits since the last vX.Y.Z tag (git-derived,
1300
+ // recomputed each completion; self-heals to false after a release).
1301
+ // ship_commits โ€” commit count since that tag (feeds the SHIP-READY hint text).
1302
+ // retro_due โ€” phases_since_retro (store sidecar) has crossed the threshold;
1303
+ // cleared by `harnessed retro --done`.
1304
+ ship_ready: Type.Optional(Type.Boolean()),
1305
+ ship_commits: Type.Optional(Type.Integer({ minimum: 0 })),
1306
+ retro_due: Type.Optional(Type.Boolean())
1291
1307
  },
1292
1308
  { additionalProperties: false }
1293
1309
  );
@@ -1305,6 +1321,7 @@ var init_schema2 = __esm({
1305
1321
  // src/checkpoint/workflowStore.ts
1306
1322
  var workflowStore_exports = {};
1307
1323
  __export(workflowStore_exports, {
1324
+ RetroMetaEntry: () => RetroMetaEntry,
1308
1325
  WorkflowStoreV1: () => WorkflowStoreV1,
1309
1326
  listWorkflows: () => listWorkflows,
1310
1327
  readStoreRaw: () => readStoreRaw,
@@ -1365,17 +1382,26 @@ async function listWorkflows() {
1365
1382
  status: wf.status
1366
1383
  }));
1367
1384
  }
1368
- var WorkflowStoreV1;
1385
+ var RetroMetaEntry, WorkflowStoreV1;
1369
1386
  var init_workflowStore = __esm({
1370
1387
  "src/checkpoint/workflowStore.ts"() {
1371
1388
  init_harnessedRoot();
1372
1389
  init_schemaVersion();
1373
1390
  init_atomicWrite();
1374
1391
  init_currentWorkflow_v1();
1392
+ RetroMetaEntry = Type.Object(
1393
+ {
1394
+ phases_since_retro: Type.Integer({ minimum: 0 }),
1395
+ last_retro_at: Type.Optional(Type.String({ minLength: 1 }))
1396
+ },
1397
+ { additionalProperties: false }
1398
+ );
1375
1399
  WorkflowStoreV1 = Type.Object(
1376
1400
  {
1377
1401
  schemaVersion: Type.Literal(SCHEMA_VERSIONS.workflowStore),
1378
- workflows: Type.Record(Type.String(), CurrentWorkflowV1)
1402
+ workflows: Type.Record(Type.String(), CurrentWorkflowV1),
1403
+ // Additive-optional (no schemaVersion bump โ€” old stores Value.Check-pass).
1404
+ retro_meta: Type.Optional(Type.Record(Type.String(), RetroMetaEntry))
1379
1405
  },
1380
1406
  { additionalProperties: false }
1381
1407
  );
@@ -1389,6 +1415,7 @@ __export(state_exports, {
1389
1415
  WorkflowStateError: () => WorkflowStateError,
1390
1416
  activate: () => activate,
1391
1417
  complete: () => complete,
1418
+ mutateStore: () => mutateStore,
1392
1419
  mutateSubProgress: () => mutateSubProgress,
1393
1420
  pause: () => pause,
1394
1421
  readCurrentWorkflow: () => readCurrentWorkflow,
@@ -1448,6 +1475,12 @@ async function mutateSubProgress(fn) {
1448
1475
  await writeCurrentWorkflowUnlocked({ ...s, sub_progress: next });
1449
1476
  });
1450
1477
  }
1478
+ async function mutateStore(fn) {
1479
+ await withLock(async () => {
1480
+ const store = await readStoreRaw();
1481
+ await writeStoreRaw(fn(store));
1482
+ });
1483
+ }
1451
1484
  async function activate(phase, checkpointPath = null) {
1452
1485
  await writeCurrentWorkflow({
1453
1486
  schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
@@ -3797,6 +3830,91 @@ var init_compact = __esm({
3797
3830
  }
3798
3831
  });
3799
3832
 
3833
+ // src/checkpoint/shipReady.ts
3834
+ var shipReady_exports = {};
3835
+ __export(shipReady_exports, {
3836
+ collectShipReady: () => collectShipReady,
3837
+ defaultShipReady: () => defaultShipReady,
3838
+ pickLatestReleaseTag: () => pickLatestReleaseTag
3839
+ });
3840
+ function pickLatestReleaseTag(tags) {
3841
+ let best = null;
3842
+ for (const tag of tags) {
3843
+ const m = RELEASE_TAG.exec(tag.trim());
3844
+ if (!m) continue;
3845
+ const n = [Number(m[1]), Number(m[2]), Number(m[3])];
3846
+ 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]) {
3847
+ best = { tag: tag.trim(), n };
3848
+ }
3849
+ }
3850
+ return best?.tag ?? null;
3851
+ }
3852
+ function collectShipReady(tags, countSince) {
3853
+ const baseTag = pickLatestReleaseTag(tags);
3854
+ const commits = countSince(baseTag);
3855
+ return { ready: commits > 0, commits, baseTag };
3856
+ }
3857
+ function defaultShipReady(cwd) {
3858
+ let tags = [];
3859
+ try {
3860
+ tags = execFileSync("git", ["tag", "-l"], { cwd, encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
3861
+ } catch {
3862
+ return { ready: false, commits: 0, baseTag: null };
3863
+ }
3864
+ const countSince = (tag) => {
3865
+ try {
3866
+ const range = tag ? `${tag}..HEAD` : "HEAD";
3867
+ const out = execFileSync("git", ["rev-list", range, "--count"], {
3868
+ cwd,
3869
+ encoding: "utf8"
3870
+ }).trim();
3871
+ const n = Number(out);
3872
+ return Number.isFinite(n) ? n : 0;
3873
+ } catch {
3874
+ return 0;
3875
+ }
3876
+ };
3877
+ return collectShipReady(tags, countSince);
3878
+ }
3879
+ var RELEASE_TAG;
3880
+ var init_shipReady = __esm({
3881
+ "src/checkpoint/shipReady.ts"() {
3882
+ RELEASE_TAG = /^v(\d+)\.(\d+)\.(\d+)$/;
3883
+ }
3884
+ });
3885
+
3886
+ // src/checkpoint/retroMeta.ts
3887
+ var retroMeta_exports = {};
3888
+ __export(retroMeta_exports, {
3889
+ DEFAULT_RETRO_THRESHOLD: () => DEFAULT_RETRO_THRESHOLD,
3890
+ incrementPhases: () => incrementPhases,
3891
+ isRetroDue: () => isRetroDue,
3892
+ resetRetro: () => resetRetro,
3893
+ retroThreshold: () => retroThreshold
3894
+ });
3895
+ function retroThreshold(env) {
3896
+ const raw = env.HARNESSED_RETRO_PHASE_THRESHOLD;
3897
+ if (raw === void 0) return DEFAULT_RETRO_THRESHOLD;
3898
+ const n = Number(raw);
3899
+ return Number.isInteger(n) && n > 0 ? n : DEFAULT_RETRO_THRESHOLD;
3900
+ }
3901
+ function incrementPhases(entry) {
3902
+ if (!entry) return { phases_since_retro: 1 };
3903
+ 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 };
3904
+ }
3905
+ function isRetroDue(count, threshold) {
3906
+ return count >= threshold;
3907
+ }
3908
+ function resetRetro(now) {
3909
+ return { phases_since_retro: 0, last_retro_at: now };
3910
+ }
3911
+ var DEFAULT_RETRO_THRESHOLD;
3912
+ var init_retroMeta = __esm({
3913
+ "src/checkpoint/retroMeta.ts"() {
3914
+ DEFAULT_RETRO_THRESHOLD = 5;
3915
+ }
3916
+ });
3917
+
3800
3918
  // src/checkpoint/breakLoop.ts
3801
3919
  var breakLoop_exports = {};
3802
3920
  __export(breakLoop_exports, {
@@ -4816,7 +4934,7 @@ function expandTildeForWindows(cmd) {
4816
4934
  const home = homedir().replace(/\\/g, "/");
4817
4935
  return cmd.replace(/(^|[\s"'`(])~\//g, `$1${home}/`);
4818
4936
  }
4819
- async function spawnCmd(ctx, cmd, args, timeoutMs) {
4937
+ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
4820
4938
  const violation = checkCmdString(cmd);
4821
4939
  if (violation) {
4822
4940
  return {
@@ -4837,14 +4955,21 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4837
4955
  const env = { ...process.env, ...installCfg.env ?? {} };
4838
4956
  const cwd = installCfg.cwd ?? ctx.cwd;
4839
4957
  let child;
4958
+ let triedBash = false;
4840
4959
  if (process.platform === "win32") {
4841
- const expandedCmd = expandTildeForWindows(cmd);
4842
- const expandedArgs = args.map(expandTildeForWindows);
4843
- child = spawn("cmd.exe", ["/c", expandedCmd, ...expandedArgs], {
4844
- cwd,
4845
- env,
4846
- windowsHide: true
4847
- });
4960
+ if (opts?.posixShell) {
4961
+ const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
4962
+ triedBash = true;
4963
+ child = spawn("bash", ["-c", joined], { cwd, env, windowsHide: true });
4964
+ } else {
4965
+ const expandedCmd = expandTildeForWindows(cmd);
4966
+ const expandedArgs = args.map(expandTildeForWindows);
4967
+ child = spawn("cmd.exe", ["/c", expandedCmd, ...expandedArgs], {
4968
+ cwd,
4969
+ env,
4970
+ windowsHide: true
4971
+ });
4972
+ }
4848
4973
  } else {
4849
4974
  const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
4850
4975
  child = spawn("/bin/sh", ["-c", joined], { cwd, env });
@@ -4875,16 +5000,17 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4875
5000
  }, effectiveTimeoutMs);
4876
5001
  child.on("error", (err2) => {
4877
5002
  clearTimeout(timer);
5003
+ const bashMissing = triedBash && err2.code === "ENOENT";
4878
5004
  resolve20({
4879
5005
  ok: false,
4880
5006
  phase: "spawn",
4881
5007
  error: {
4882
5008
  file: ctx.manifest.metadata.name,
4883
5009
  path: "/spec/install/cmd",
4884
- message: `spawn failed: ${err2.message}`,
5010
+ message: bashMissing ? "Git Bash is required for this component on Windows, but `bash` was not found on PATH. Install Git for Windows (https://git-scm.com/download/win) and re-run `harnessed setup`." : `spawn failed: ${err2.message}`,
4885
5011
  line: null,
4886
5012
  column: null,
4887
- keyword: "spawn-error"
5013
+ keyword: bashMissing ? "bash-missing" : "spawn-error"
4888
5014
  }
4889
5015
  });
4890
5016
  });
@@ -4899,7 +5025,7 @@ var init_spawn = __esm({
4899
5025
  "src/installers/lib/spawn.ts"() {
4900
5026
  init_security();
4901
5027
  DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
4902
- DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
5028
+ DEFAULT_INSTALL_TIMEOUT_MS = 12e4;
4903
5029
  }
4904
5030
  });
4905
5031
  function extractSkillName(cmd, fallback) {
@@ -5335,7 +5461,7 @@ var init_gitCloneWithSetup2 = __esm({
5335
5461
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
5336
5462
  const bk = await backup(plan, ctx);
5337
5463
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
5338
- const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
5464
+ const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS, { posixShell: true });
5339
5465
  if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
5340
5466
  if (sp.exitCode !== 0) {
5341
5467
  return {
@@ -5378,7 +5504,9 @@ var init_gitCloneWithSetup2 = __esm({
5378
5504
  };
5379
5505
  }
5380
5506
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5381
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
5507
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
5508
+ posixShell: true
5509
+ });
5382
5510
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5383
5511
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5384
5512
  if (vr.exitCode !== expected) {
@@ -5809,7 +5937,9 @@ var init_npmCli2 = __esm({
5809
5937
  };
5810
5938
  }
5811
5939
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5812
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
5940
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
5941
+ posixShell: true
5942
+ });
5813
5943
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5814
5944
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5815
5945
  if (vr.exitCode !== expected) {
@@ -5938,7 +6068,9 @@ var init_npxSkillInstaller2 = __esm({
5938
6068
  };
5939
6069
  }
5940
6070
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5941
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
6071
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
6072
+ posixShell: true
6073
+ });
5942
6074
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5943
6075
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5944
6076
  if (vr.exitCode !== expected) {
@@ -6981,6 +7113,35 @@ function registerCheckpoint(program2) {
6981
7113
  );
6982
7114
  }
6983
7115
  }
7116
+ if (allResolved) {
7117
+ try {
7118
+ const { defaultShipReady: defaultShipReady2 } = await Promise.resolve().then(() => (init_shipReady(), shipReady_exports));
7119
+ const { incrementPhases: incrementPhases2, isRetroDue: isRetroDue2, retroThreshold: retroThreshold2 } = await Promise.resolve().then(() => (init_retroMeta(), retroMeta_exports));
7120
+ const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow4, writeCurrentWorkflow: writeCurrentWorkflow3 } = await Promise.resolve().then(() => (init_state(), state_exports));
7121
+ const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
7122
+ const ship = defaultShipReady2(process.cwd());
7123
+ const key = repoKey2();
7124
+ const threshold = retroThreshold2(process.env);
7125
+ let retroDue = false;
7126
+ await mutateStore2((store) => {
7127
+ const meta = { ...store.retro_meta ?? {} };
7128
+ const next = incrementPhases2(meta[key]);
7129
+ meta[key] = next;
7130
+ retroDue = isRetroDue2(next.phases_since_retro, threshold);
7131
+ return { ...store, retro_meta: meta };
7132
+ });
7133
+ const latest2 = await readCurrentWorkflow4();
7134
+ if (latest2) {
7135
+ await writeCurrentWorkflow3({
7136
+ ...latest2,
7137
+ ship_ready: ship.ready,
7138
+ ship_commits: ship.commits,
7139
+ retro_due: retroDue
7140
+ });
7141
+ }
7142
+ } catch {
7143
+ }
7144
+ }
6984
7145
  console.log(`[harnessed] checkpoint complete: ${sub} (evidence: ${evidenceStatus})`);
6985
7146
  process.exit(0);
6986
7147
  return;
@@ -7725,6 +7886,37 @@ function registerResume(program2) {
7725
7886
  process.exit(0);
7726
7887
  });
7727
7888
  }
7889
+
7890
+ // src/cli/retro.ts
7891
+ init_retroMeta();
7892
+ async function runRetroDone(now) {
7893
+ const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow2, writeCurrentWorkflow: writeCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
7894
+ const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
7895
+ const key = repoKey2();
7896
+ await mutateStore2((store) => {
7897
+ const meta = { ...store.retro_meta ?? {} };
7898
+ meta[key] = resetRetro(now);
7899
+ return { ...store, retro_meta: meta };
7900
+ });
7901
+ const wf = await readCurrentWorkflow2();
7902
+ if (wf?.retro_due) await writeCurrentWorkflow2({ ...wf, retro_due: false });
7903
+ return { reset: true };
7904
+ }
7905
+ function registerRetro(program2) {
7906
+ program2.command("retro").description("Retro cadence: --done resets the phase counter after running /retro").option(
7907
+ "--done",
7908
+ "mark a retro as done \u2014 reset the phase counter + clear the RETRO-DUE reminder"
7909
+ ).action(async (opts) => {
7910
+ if (!opts.done) {
7911
+ console.error("[harnessed] retro: nothing to do \u2014 pass --done after running /retro");
7912
+ process.exit(1);
7913
+ return;
7914
+ }
7915
+ await runRetroDone((/* @__PURE__ */ new Date()).toISOString());
7916
+ console.log("[harnessed] retro recorded \u2014 phase counter reset, RETRO-DUE reminder cleared");
7917
+ process.exit(0);
7918
+ });
7919
+ }
7728
7920
  init_backup();
7729
7921
  function normalizeEol(buf, eol) {
7730
7922
  const lf = buf.toString("utf8").replace(/\r\n/g, "\n");
@@ -9220,6 +9412,7 @@ registerWorkflows(program);
9220
9412
  registerLearn(program);
9221
9413
  registerUpdate(program);
9222
9414
  registerReleasePreflight(program);
9415
+ registerRetro(program);
9223
9416
  program.parse(process.argv);
9224
9417
  //# sourceMappingURL=cli.mjs.map
9225
9418
  //# sourceMappingURL=cli.mjs.map