harnessed 4.5.0 โ†’ 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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,17 @@ 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. |
412
419
  | `harnessed install <name>` | Install an upstream manifest |
413
420
  | `harnessed uninstall [name]` | Reverse uninstall |
414
421
  | `harnessed backup` | Snapshot backup management |
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.5.1",
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: {
@@ -4816,7 +4820,7 @@ function expandTildeForWindows(cmd) {
4816
4820
  const home = homedir().replace(/\\/g, "/");
4817
4821
  return cmd.replace(/(^|[\s"'`(])~\//g, `$1${home}/`);
4818
4822
  }
4819
- async function spawnCmd(ctx, cmd, args, timeoutMs) {
4823
+ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
4820
4824
  const violation = checkCmdString(cmd);
4821
4825
  if (violation) {
4822
4826
  return {
@@ -4837,14 +4841,21 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4837
4841
  const env = { ...process.env, ...installCfg.env ?? {} };
4838
4842
  const cwd = installCfg.cwd ?? ctx.cwd;
4839
4843
  let child;
4844
+ let triedBash = false;
4840
4845
  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
- });
4846
+ if (opts?.posixShell) {
4847
+ const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
4848
+ triedBash = true;
4849
+ child = spawn("bash", ["-c", joined], { cwd, env, windowsHide: true });
4850
+ } else {
4851
+ const expandedCmd = expandTildeForWindows(cmd);
4852
+ const expandedArgs = args.map(expandTildeForWindows);
4853
+ child = spawn("cmd.exe", ["/c", expandedCmd, ...expandedArgs], {
4854
+ cwd,
4855
+ env,
4856
+ windowsHide: true
4857
+ });
4858
+ }
4848
4859
  } else {
4849
4860
  const joined = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
4850
4861
  child = spawn("/bin/sh", ["-c", joined], { cwd, env });
@@ -4875,16 +4886,17 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4875
4886
  }, effectiveTimeoutMs);
4876
4887
  child.on("error", (err2) => {
4877
4888
  clearTimeout(timer);
4889
+ const bashMissing = triedBash && err2.code === "ENOENT";
4878
4890
  resolve20({
4879
4891
  ok: false,
4880
4892
  phase: "spawn",
4881
4893
  error: {
4882
4894
  file: ctx.manifest.metadata.name,
4883
4895
  path: "/spec/install/cmd",
4884
- message: `spawn failed: ${err2.message}`,
4896
+ 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
4897
  line: null,
4886
4898
  column: null,
4887
- keyword: "spawn-error"
4899
+ keyword: bashMissing ? "bash-missing" : "spawn-error"
4888
4900
  }
4889
4901
  });
4890
4902
  });
@@ -4899,7 +4911,7 @@ var init_spawn = __esm({
4899
4911
  "src/installers/lib/spawn.ts"() {
4900
4912
  init_security();
4901
4913
  DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
4902
- DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
4914
+ DEFAULT_INSTALL_TIMEOUT_MS = 12e4;
4903
4915
  }
4904
4916
  });
4905
4917
  function extractSkillName(cmd, fallback) {
@@ -5335,7 +5347,7 @@ var init_gitCloneWithSetup2 = __esm({
5335
5347
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
5336
5348
  const bk = await backup(plan, ctx);
5337
5349
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
5338
- const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
5350
+ const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS, { posixShell: true });
5339
5351
  if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
5340
5352
  if (sp.exitCode !== 0) {
5341
5353
  return {
@@ -5378,7 +5390,9 @@ var init_gitCloneWithSetup2 = __esm({
5378
5390
  };
5379
5391
  }
5380
5392
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5381
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
5393
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
5394
+ posixShell: true
5395
+ });
5382
5396
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5383
5397
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5384
5398
  if (vr.exitCode !== expected) {
@@ -5809,7 +5823,9 @@ var init_npmCli2 = __esm({
5809
5823
  };
5810
5824
  }
5811
5825
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5812
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
5826
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
5827
+ posixShell: true
5828
+ });
5813
5829
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5814
5830
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5815
5831
  if (vr.exitCode !== expected) {
@@ -5938,7 +5954,9 @@ var init_npxSkillInstaller2 = __esm({
5938
5954
  };
5939
5955
  }
5940
5956
  const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
5941
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
5957
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs, {
5958
+ posixShell: true
5959
+ });
5942
5960
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
5943
5961
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
5944
5962
  if (vr.exitCode !== expected) {