@ai-substrate/engineering-harness 0.2.0-canary.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/harness/cli/bin/harness.js +12 -0
  4. package/harness/cli/dist/acts/docs.d.ts +17 -0
  5. package/harness/cli/dist/acts/docs.js +73 -0
  6. package/harness/cli/dist/acts/docs.js.map +1 -0
  7. package/harness/cli/dist/acts/doctor.d.ts +14 -0
  8. package/harness/cli/dist/acts/doctor.js +43 -0
  9. package/harness/cli/dist/acts/doctor.js.map +1 -0
  10. package/harness/cli/dist/acts/help.d.ts +14 -0
  11. package/harness/cli/dist/acts/help.js +29 -0
  12. package/harness/cli/dist/acts/help.js.map +1 -0
  13. package/harness/cli/dist/acts/init.d.ts +22 -0
  14. package/harness/cli/dist/acts/init.js +61 -0
  15. package/harness/cli/dist/acts/init.js.map +1 -0
  16. package/harness/cli/dist/acts/instructions.d.ts +21 -0
  17. package/harness/cli/dist/acts/instructions.js +75 -0
  18. package/harness/cli/dist/acts/instructions.js.map +1 -0
  19. package/harness/cli/dist/acts/new.d.ts +19 -0
  20. package/harness/cli/dist/acts/new.js +66 -0
  21. package/harness/cli/dist/acts/new.js.map +1 -0
  22. package/harness/cli/dist/acts/observe.d.ts +23 -0
  23. package/harness/cli/dist/acts/observe.js +129 -0
  24. package/harness/cli/dist/acts/observe.js.map +1 -0
  25. package/harness/cli/dist/acts/record.d.ts +24 -0
  26. package/harness/cli/dist/acts/record.js +93 -0
  27. package/harness/cli/dist/acts/record.js.map +1 -0
  28. package/harness/cli/dist/acts/skills.d.ts +32 -0
  29. package/harness/cli/dist/acts/skills.js +256 -0
  30. package/harness/cli/dist/acts/skills.js.map +1 -0
  31. package/harness/cli/dist/acts/update.d.ts +23 -0
  32. package/harness/cli/dist/acts/update.js +297 -0
  33. package/harness/cli/dist/acts/update.js.map +1 -0
  34. package/harness/cli/dist/acts/verb.d.ts +27 -0
  35. package/harness/cli/dist/acts/verb.js +56 -0
  36. package/harness/cli/dist/acts/verb.js.map +1 -0
  37. package/harness/cli/dist/adapters/clock/clock-port.d.ts +10 -0
  38. package/harness/cli/dist/adapters/clock/clock-port.js +2 -0
  39. package/harness/cli/dist/adapters/clock/clock-port.js.map +1 -0
  40. package/harness/cli/dist/adapters/clock/fake-clock.d.ts +15 -0
  41. package/harness/cli/dist/adapters/clock/fake-clock.js +25 -0
  42. package/harness/cli/dist/adapters/clock/fake-clock.js.map +1 -0
  43. package/harness/cli/dist/adapters/clock/system-clock.d.ts +5 -0
  44. package/harness/cli/dist/adapters/clock/system-clock.js +7 -0
  45. package/harness/cli/dist/adapters/clock/system-clock.js.map +1 -0
  46. package/harness/cli/dist/adapters/env/env-port.d.ts +17 -0
  47. package/harness/cli/dist/adapters/env/env-port.js +2 -0
  48. package/harness/cli/dist/adapters/env/env-port.js.map +1 -0
  49. package/harness/cli/dist/adapters/env/fake-env.d.ts +15 -0
  50. package/harness/cli/dist/adapters/env/fake-env.js +24 -0
  51. package/harness/cli/dist/adapters/env/fake-env.js.map +1 -0
  52. package/harness/cli/dist/adapters/env/node-env.d.ts +6 -0
  53. package/harness/cli/dist/adapters/env/node-env.js +16 -0
  54. package/harness/cli/dist/adapters/env/node-env.js.map +1 -0
  55. package/harness/cli/dist/adapters/exec/exec-port.d.ts +22 -0
  56. package/harness/cli/dist/adapters/exec/exec-port.js +2 -0
  57. package/harness/cli/dist/adapters/exec/exec-port.js.map +1 -0
  58. package/harness/cli/dist/adapters/exec/fake-exec.d.ts +25 -0
  59. package/harness/cli/dist/adapters/exec/fake-exec.js +25 -0
  60. package/harness/cli/dist/adapters/exec/fake-exec.js.map +1 -0
  61. package/harness/cli/dist/adapters/exec/node-exec.d.ts +14 -0
  62. package/harness/cli/dist/adapters/exec/node-exec.js +38 -0
  63. package/harness/cli/dist/adapters/exec/node-exec.js.map +1 -0
  64. package/harness/cli/dist/adapters/fs/fake-fs.d.ts +22 -0
  65. package/harness/cli/dist/adapters/fs/fake-fs.js +63 -0
  66. package/harness/cli/dist/adapters/fs/fake-fs.js.map +1 -0
  67. package/harness/cli/dist/adapters/fs/fs-port.d.ts +20 -0
  68. package/harness/cli/dist/adapters/fs/fs-port.js +2 -0
  69. package/harness/cli/dist/adapters/fs/fs-port.js.map +1 -0
  70. package/harness/cli/dist/adapters/fs/node-fs.d.ts +9 -0
  71. package/harness/cli/dist/adapters/fs/node-fs.js +30 -0
  72. package/harness/cli/dist/adapters/fs/node-fs.js.map +1 -0
  73. package/harness/cli/dist/adapters/git/exec-git.d.ts +6 -0
  74. package/harness/cli/dist/adapters/git/exec-git.js +21 -0
  75. package/harness/cli/dist/adapters/git/exec-git.js.map +1 -0
  76. package/harness/cli/dist/adapters/git/fake-git.d.ts +15 -0
  77. package/harness/cli/dist/adapters/git/fake-git.js +20 -0
  78. package/harness/cli/dist/adapters/git/fake-git.js.map +1 -0
  79. package/harness/cli/dist/adapters/git/git-port.d.ts +12 -0
  80. package/harness/cli/dist/adapters/git/git-port.js +2 -0
  81. package/harness/cli/dist/adapters/git/git-port.js.map +1 -0
  82. package/harness/cli/dist/adapters/loader/fake-loader.d.ts +13 -0
  83. package/harness/cli/dist/adapters/loader/fake-loader.js +25 -0
  84. package/harness/cli/dist/adapters/loader/fake-loader.js.map +1 -0
  85. package/harness/cli/dist/adapters/loader/jiti-loader.d.ts +15 -0
  86. package/harness/cli/dist/adapters/loader/jiti-loader.js +29 -0
  87. package/harness/cli/dist/adapters/loader/jiti-loader.js.map +1 -0
  88. package/harness/cli/dist/adapters/loader/module-loader-port.d.ts +12 -0
  89. package/harness/cli/dist/adapters/loader/module-loader-port.js +2 -0
  90. package/harness/cli/dist/adapters/loader/module-loader-port.js.map +1 -0
  91. package/harness/cli/dist/adapters/process/fake-process.d.ts +13 -0
  92. package/harness/cli/dist/adapters/process/fake-process.js +21 -0
  93. package/harness/cli/dist/adapters/process/fake-process.js.map +1 -0
  94. package/harness/cli/dist/adapters/process/node-process.d.ts +6 -0
  95. package/harness/cli/dist/adapters/process/node-process.js +17 -0
  96. package/harness/cli/dist/adapters/process/node-process.js.map +1 -0
  97. package/harness/cli/dist/adapters/process/process-port.d.ts +13 -0
  98. package/harness/cli/dist/adapters/process/process-port.js +2 -0
  99. package/harness/cli/dist/adapters/process/process-port.js.map +1 -0
  100. package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.d.ts +13 -0
  101. package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js +21 -0
  102. package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js.map +1 -0
  103. package/harness/cli/dist/adapters/version-lookup/node-version-lookup.d.ts +18 -0
  104. package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js +51 -0
  105. package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js.map +1 -0
  106. package/harness/cli/dist/adapters/version-lookup/version-lookup-port.d.ts +19 -0
  107. package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js +2 -0
  108. package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js.map +1 -0
  109. package/harness/cli/dist/app.d.ts +70 -0
  110. package/harness/cli/dist/app.js +221 -0
  111. package/harness/cli/dist/app.js.map +1 -0
  112. package/harness/cli/dist/index.d.ts +2 -0
  113. package/harness/cli/dist/index.js +26 -0
  114. package/harness/cli/dist/index.js.map +1 -0
  115. package/harness/cli/dist/output/envelope.d.ts +68 -0
  116. package/harness/cli/dist/output/envelope.js +56 -0
  117. package/harness/cli/dist/output/envelope.js.map +1 -0
  118. package/harness/cli/dist/output/error-codes.d.ts +57 -0
  119. package/harness/cli/dist/output/error-codes.js +57 -0
  120. package/harness/cli/dist/output/error-codes.js.map +1 -0
  121. package/harness/cli/dist/output/exit.d.ts +29 -0
  122. package/harness/cli/dist/output/exit.js +36 -0
  123. package/harness/cli/dist/output/exit.js.map +1 -0
  124. package/harness/cli/dist/output/output-port.d.ts +54 -0
  125. package/harness/cli/dist/output/output-port.js +55 -0
  126. package/harness/cli/dist/output/output-port.js.map +1 -0
  127. package/harness/cli/dist/output/style.d.ts +33 -0
  128. package/harness/cli/dist/output/style.js +68 -0
  129. package/harness/cli/dist/output/style.js.map +1 -0
  130. package/harness/cli/dist/services/config/load-config.d.ts +27 -0
  131. package/harness/cli/dist/services/config/load-config.js +114 -0
  132. package/harness/cli/dist/services/config/load-config.js.map +1 -0
  133. package/harness/cli/dist/services/docs/contract.d.ts +41 -0
  134. package/harness/cli/dist/services/docs/contract.js +14 -0
  135. package/harness/cli/dist/services/docs/contract.js.map +1 -0
  136. package/harness/cli/dist/services/docs/docs-content.d.ts +37 -0
  137. package/harness/cli/dist/services/docs/docs-content.js +48 -0
  138. package/harness/cli/dist/services/docs/docs-content.js.map +1 -0
  139. package/harness/cli/dist/services/docs/docs-service.d.ts +26 -0
  140. package/harness/cli/dist/services/docs/docs-service.js +25 -0
  141. package/harness/cli/dist/services/docs/docs-service.js.map +1 -0
  142. package/harness/cli/dist/services/doctor/doctor-service.d.ts +69 -0
  143. package/harness/cli/dist/services/doctor/doctor-service.js +237 -0
  144. package/harness/cli/dist/services/doctor/doctor-service.js.map +1 -0
  145. package/harness/cli/dist/services/extensions/contract.d.ts +138 -0
  146. package/harness/cli/dist/services/extensions/contract.js +17 -0
  147. package/harness/cli/dist/services/extensions/contract.js.map +1 -0
  148. package/harness/cli/dist/services/extensions/discovery.d.ts +53 -0
  149. package/harness/cli/dist/services/extensions/discovery.js +116 -0
  150. package/harness/cli/dist/services/extensions/discovery.js.map +1 -0
  151. package/harness/cli/dist/services/extensions/registry.d.ts +63 -0
  152. package/harness/cli/dist/services/extensions/registry.js +165 -0
  153. package/harness/cli/dist/services/extensions/registry.js.map +1 -0
  154. package/harness/cli/dist/services/extensions/verb-context.d.ts +44 -0
  155. package/harness/cli/dist/services/extensions/verb-context.js +97 -0
  156. package/harness/cli/dist/services/extensions/verb-context.js.map +1 -0
  157. package/harness/cli/dist/services/help/help-service.d.ts +42 -0
  158. package/harness/cli/dist/services/help/help-service.js +108 -0
  159. package/harness/cli/dist/services/help/help-service.js.map +1 -0
  160. package/harness/cli/dist/services/init/governance-template.d.ts +27 -0
  161. package/harness/cli/dist/services/init/governance-template.js +72 -0
  162. package/harness/cli/dist/services/init/governance-template.js.map +1 -0
  163. package/harness/cli/dist/services/init/init-service.d.ts +38 -0
  164. package/harness/cli/dist/services/init/init-service.js +44 -0
  165. package/harness/cli/dist/services/init/init-service.js.map +1 -0
  166. package/harness/cli/dist/services/instructions/core-instructions.d.ts +11 -0
  167. package/harness/cli/dist/services/instructions/core-instructions.js +80 -0
  168. package/harness/cli/dist/services/instructions/core-instructions.js.map +1 -0
  169. package/harness/cli/dist/services/instructions/instructions-service.d.ts +52 -0
  170. package/harness/cli/dist/services/instructions/instructions-service.js +53 -0
  171. package/harness/cli/dist/services/instructions/instructions-service.js.map +1 -0
  172. package/harness/cli/dist/services/observe/buffer-codec.d.ts +51 -0
  173. package/harness/cli/dist/services/observe/buffer-codec.js +139 -0
  174. package/harness/cli/dist/services/observe/buffer-codec.js.map +1 -0
  175. package/harness/cli/dist/services/observe/observe-service.d.ts +87 -0
  176. package/harness/cli/dist/services/observe/observe-service.js +221 -0
  177. package/harness/cli/dist/services/observe/observe-service.js.map +1 -0
  178. package/harness/cli/dist/services/record/contract.d.ts +32 -0
  179. package/harness/cli/dist/services/record/contract.js +17 -0
  180. package/harness/cli/dist/services/record/contract.js.map +1 -0
  181. package/harness/cli/dist/services/record/core-types/retro.d.ts +20 -0
  182. package/harness/cli/dist/services/record/core-types/retro.js +55 -0
  183. package/harness/cli/dist/services/record/core-types/retro.js.map +1 -0
  184. package/harness/cli/dist/services/record/record-service.d.ts +38 -0
  185. package/harness/cli/dist/services/record/record-service.js +144 -0
  186. package/harness/cli/dist/services/record/record-service.js.map +1 -0
  187. package/harness/cli/dist/services/record/registry.d.ts +46 -0
  188. package/harness/cli/dist/services/record/registry.js +71 -0
  189. package/harness/cli/dist/services/record/registry.js.map +1 -0
  190. package/harness/cli/dist/services/scaffold/scaffold-service.d.ts +29 -0
  191. package/harness/cli/dist/services/scaffold/scaffold-service.js +88 -0
  192. package/harness/cli/dist/services/scaffold/scaffold-service.js.map +1 -0
  193. package/harness/cli/dist/services/scaffold/templates.d.ts +42 -0
  194. package/harness/cli/dist/services/scaffold/templates.js +178 -0
  195. package/harness/cli/dist/services/scaffold/templates.js.map +1 -0
  196. package/harness/cli/dist/services/shared/posix-path.d.ts +54 -0
  197. package/harness/cli/dist/services/shared/posix-path.js +94 -0
  198. package/harness/cli/dist/services/shared/posix-path.js.map +1 -0
  199. package/harness/cli/dist/services/shared/temp.d.ts +24 -0
  200. package/harness/cli/dist/services/shared/temp.js +29 -0
  201. package/harness/cli/dist/services/shared/temp.js.map +1 -0
  202. package/harness/cli/dist/services/skills/contract.d.ts +52 -0
  203. package/harness/cli/dist/services/skills/contract.js +55 -0
  204. package/harness/cli/dist/services/skills/contract.js.map +1 -0
  205. package/harness/cli/dist/services/skills/skills-service.d.ts +73 -0
  206. package/harness/cli/dist/services/skills/skills-service.js +132 -0
  207. package/harness/cli/dist/services/skills/skills-service.js.map +1 -0
  208. package/harness/cli/dist/services/update/banner.d.ts +26 -0
  209. package/harness/cli/dist/services/update/banner.js +28 -0
  210. package/harness/cli/dist/services/update/banner.js.map +1 -0
  211. package/harness/cli/dist/services/update/cache.d.ts +21 -0
  212. package/harness/cli/dist/services/update/cache.js +61 -0
  213. package/harness/cli/dist/services/update/cache.js.map +1 -0
  214. package/harness/cli/dist/services/update/constants.d.ts +9 -0
  215. package/harness/cli/dist/services/update/constants.js +10 -0
  216. package/harness/cli/dist/services/update/constants.js.map +1 -0
  217. package/harness/cli/dist/services/update/install.d.ts +26 -0
  218. package/harness/cli/dist/services/update/install.js +78 -0
  219. package/harness/cli/dist/services/update/install.js.map +1 -0
  220. package/harness/cli/dist/services/update/semver.d.ts +16 -0
  221. package/harness/cli/dist/services/update/semver.js +108 -0
  222. package/harness/cli/dist/services/update/semver.js.map +1 -0
  223. package/harness/cli/dist/services/update/update-service.d.ts +46 -0
  224. package/harness/cli/dist/services/update/update-service.js +91 -0
  225. package/harness/cli/dist/services/update/update-service.js.map +1 -0
  226. package/harness/cli/dist/version.d.ts +8 -0
  227. package/harness/cli/dist/version.js +15 -0
  228. package/harness/cli/dist/version.js.map +1 -0
  229. package/package.json +56 -0
@@ -0,0 +1,48 @@
1
+ // @generated by scripts/gen-docs.mjs — DO NOT EDIT.
2
+ // Source of truth: harness/cli/src/services/docs/docs-manifest.json + the curated .md files.
3
+ // Regenerate with `npm run gen:docs`.
4
+ export const DOCS = [
5
+ {
6
+ id: 'extend-the-harness',
7
+ title: 'Extend the harness',
8
+ summary: 'Add a new `harness <verb>` command — the fast path (`harness new`) and the guided `eng-harness-0-add-extension` skill.',
9
+ audience: 'both',
10
+ content: '# Extend the harness\n\nHow to add a new `harness <verb>` command to your repo — the fast path\n(`harness new`) and the guided path (the `eng-harness-0-add-extension` skill).\n\n> **Where docs live (for now):** user guides live under `docs/how/`. Documentation\n> is planned to become a first-class, CLI-surfaced concept later; this guide is\n> written standalone so it can be promoted/indexed without moving.\n\n---\n\n## The model in one minute\n\nThe harness core (installed via `npx`) ships a few built-in commands —\n`help`, `doctor`, `instructions`, `new`, `docs`, `skills`, and `record`.\n**Every other command is an extension** you add to your own repo. An extension\nis a **little package**: a folder under `.harness/extensions/` with two\nconvention-required files and any internals it wants:\n\n```\n<your repo>/\n└── .harness/\n └── extensions/\n └── greet/\n ├── extension.ts ← default-exports a HarnessVerb → `harness greet` (required)\n ├── instructions.md ← the agent briefing for this verb (required)\n └── lib/… ← free-form internals, imported relatively (optional)\n```\n\nThe core discovers `.harness/extensions/` at runtime and turns each folder\'s\nverb into a top-level `harness <verb>` command with its own `--help`, options,\nstructured output, and exit code. Within a folder the entry resolves in this\norder: `package.json` `harness.extensions[]` manifest → `extension.ts` →\n`extension.js` → `index.ts` → `index.js` (`.tsx`/`.mjs`/`.cjs` entries are\nreachable only via the manifest). Internals (`lib/*.ts`, subfolders) are\nimported with ordinary relative imports — the loader resolves them.\n\n**Flat files are not supported.** A loose `.harness/extensions/<name>.ts` is\nrejected at discovery (`E143`) and `harness doctor` tells you exactly what to\ndo: move it to `<name>/extension.ts`.\n\n---\n\n## Agent instructions: the `instructions.md` briefing\n\nEach extension carries an `instructions.md` beside its entry — a **briefing\nfor the calling agent**, not a human README. The split it encodes: *the verb\nbrings the determinism, the agent brings the inference.* The file states what\nthe verb computes deterministically, what role the agent plays around that\noutput, and what judgment is expected back.\n\n- **Audience**: the agent about to run the verb. Write it second-person,\n operational.\n- **Convention name**: exactly `instructions.md`, one per extension folder.\n Multi-verb extensions share their folder\'s single briefing.\n- **Served by**: `harness instructions <verb>` — the whole file, verbatim,\n **read from disk at every invocation**. Edit it any time; the next call\n serves the new content, no rebuild.\n- **Discoverable via**: `harness help --json` (per-verb `has_instructions`)\n and bare `harness instructions` (the baked core briefing +\n `verbs_with_instructions[]`).\n- **Enforced by**: `harness doctor` — a loaded extension without\n `instructions.md` still runs, but doctor wails: a per-extension complaint\n (`E144`), an author-this `next_action`, and an overall `degraded` envelope\n (still exit 0).\n\n> **Not minih.** If you also run [minih](https://github.com/AI-Substrate/minih)\n> workers, note the distinction once and keep it: a minih agent folder\'s\n> `prompt.md`/`instructions.md` configure the **worker inside the minih\n> runtime**. A harness extension\'s `instructions.md` briefs the **calling\n> agent operating the CLI from the outside**. Same filename, different\n> audience — never copy one into the other.\n\nA good starter shape (what `harness new` scaffolds for you):\n\n```markdown\n# `harness <verb>` — agent briefing\n\n## What this verb computes (the deterministic part)\n## Your role (the inference part)\n## Watch out for\n```\n\n---\n\n## Fast path: `harness new`\n\n`harness new` scaffolds a new extension **package** that is immediately\nloadable — it shows up in `harness help`/`harness doctor` right away and\nhonestly reports `unconfigured` ("not built yet") until you implement it.\n\n```bash\nharness new <name> # minimal TypeScript stub\nharness new <name> --wrap "<command>" # a stub that wraps a real repo command\nharness new <name> --js # a plain-JavaScript stub (JSDoc contract)\nharness new <name> --record # a record-type extension instead of a verb\nharness new <name> --force # overwrite an existing entry file\n```\n\n### What gets created, and where\n\n| Command | Entry written | Body |\n|---------|--------------|------|\n| `harness new greet` | `.harness/extensions/greet/extension.ts` | minimal stub → `ctx.unconfigured(...)` |\n| `harness new greet --js` | `.harness/extensions/greet/extension.js` | minimal stub, JSDoc contract, no runtime import |\n| `harness new test --wrap "npm test"` | `.harness/extensions/test/extension.ts` | wraps `npm test` via `ctx.exec(\'npm\', [\'test\'])` |\n| `harness new survey --record` | `.harness/extensions/survey/extension.ts` | a `kind:\'record\'` HarnessRecordType stub |\n\nEvery variant ALSO writes a starter `.harness/extensions/<name>/instructions.md`\n(a guided TODO addressed to the calling agent). `--force` replaces the entry\nfile but never clobbers an authored `instructions.md`. The success Envelope\nreports both paths:\n\n```bash\n$ harness new greet\n{"command":"new","status":"ok","timestamp":"…","data":{"path":".harness/extensions/greet/extension.ts","instructionsPath":".harness/extensions/greet/instructions.md","verb":"greet","variant":"minimal-ts"}}\n```\n\n### Naming rules\n\n- Lowercase, hyphenated, starting with a letter: `greet`, `ci-smoke`. (Invalid\n names — uppercase, leading digit, spaces, path separators — are rejected, `E150`.)\n- `help`, `doctor`, `new`, `docs`, `skills`, `record`, and `instructions` are\n reserved core commands and can\'t be used (`E151`).\n- `harness new` won\'t overwrite an existing entry file (`E152`) unless you pass `--force`.\n\n### Then: implement it (and author the briefing)\n\nOpen `extension.ts` and fill `run(ctx)`. The minimal stub:\n\n```ts\nimport type { HarnessVerb } from \'@ai-substrate/engineering-harness/contract\';\n\nconst greet: HarnessVerb = {\n name: \'greet\',\n summary: \'TODO: one-line summary of what `harness greet` does.\',\n run(ctx) {\n return ctx.unconfigured(\'Implement run() in .harness/extensions/greet/extension.ts\');\n },\n};\n\nexport default greet;\n```\n\nReplace the `ctx.unconfigured(...)` line with real logic using the `ctx` helpers\n(`ctx.ok`, `ctx.degraded`, `ctx.unconfigured`, `ctx.error`, `ctx.exec`, `ctx.fs`,\n`ctx.git`, `ctx.env`). The full contract is in\n[`harness/cli/docs/authoring-verbs.md`](../../harness/cli/docs/authoring-verbs.md).\n\nThen replace the TODOs in `instructions.md` with the real briefing — what the\nverb computes, the judgment expected back, the traps. `harness instructions\n<name>` is how you (or any agent) check it reads well.\n\nAs the extension grows, split internals into the folder freely:\n\n```\n.harness/extensions/ci-smoke/\n├── extension.ts # entry — imports \'./lib/report.ts\'\n├── instructions.md\n└── lib/\n └── report.ts\n```\n\n### Wrap, don\'t rebuild\n\nThe most valuable extensions wrap a command your repo already has. `--wrap`\nwrites that for you:\n\n```bash\n$ harness new ci-smoke --wrap "just ci-smoke"\n# → .harness/extensions/ci-smoke/extension.ts whose run() calls ctx.exec(\'just\', [\'ci-smoke\'])\n```\n\n`--wrap` supports a simple `cmd arg arg` line. For commands with quotes, pipes,\nor shell operators, scaffold without `--wrap` and write the `ctx.exec(...)` calls\nby hand.\n\n---\n\n## Guided path: the `eng-harness-0-add-extension` skill\n\nIf you\'re working with an agent, the **`eng-harness-0-add-extension` skill** does the whole\nflow for you: it reuses any intent already gathered (a spec/plan/workshop or the\nconversation), runs `harness new`, fills the handler AND the briefing, and\nverifies. It only asks when something is genuinely unclear. See\n[`skills/eng-harness-setup/eng-harness-0-add-extension/`](../../skills/eng-harness-setup/eng-harness-0-add-extension/).\n\n---\n\n## Verify it loaded\n\nAfter creating (and filling) an extension:\n\n```bash\nharness doctor # the package shows `loaded` + convention checks (instructions.md present?)\nharness help # the verb appears in the command list (📖 = briefing authored)\nharness instructions <verb> # the briefing serves verbatim\nharness <verb> --help # usage for the verb\nharness <verb> # run it — `ok` once filled, or `unconfigured` (exit 2) while it\'s a stub\n```\n\nExit codes are part of the contract: `0` ok/degraded, `1` error, `2`\nunconfigured.\n\n---\n\n## Safety\n\nExtensions are arbitrary code with full Node privileges — the same trust model as\nESLint/Vite plugins (and their `instructions.md` briefings share that trust\ndomain — repo-trusted content, served verbatim). A broken extension is isolated\n(reported by `doctor` as `failed`/`E140`, never crashing the others). To skip\nextensions entirely, use `harness --no-extensions <cmd>` or\n`HARNESS_NO_EXTENSIONS=1`.\n',
11
+ },
12
+ {
13
+ id: 'record-and-record-types',
14
+ title: 'Record and record types',
15
+ summary: 'Use `harness record <type>` to scaffold a templated record file, and add a new record type (core or extension) with the 4-field contract.',
16
+ audience: 'both',
17
+ content: '# Record and record types\n\nHow to use `harness record <type>` to scaffold a structured record file your\nagent fills, and how to add a new **record type** — bundled in core or dropped in\nas an extension.\n\n> **Where docs live (for now):** user guides live under `docs/how/`. Documentation\n> is planned to become a first-class, CLI-surfaced concept later; this guide is\n> written standalone so it can be promoted/indexed without moving.\n\n---\n\n## The model in one minute\n\nA **record** is a structured markdown file an agent (or human) fills — a\nretrospective, a developer survey, a handover. A **record type** is the template\nthat file is scaffolded from. `harness record <type>` does the boring part —\nplacement, naming, never clobbering — and hands you back a path to fill:\n\n```\n<your repo>/\n└── .harness/\n ├── records/\n │ └── retro/\n │ └── 2026-06-09-harness-flow-skill.md ← committed record you fill\n └── temp/ ← gitignored agent scratch (see below)\n```\n\nThe CLI is deliberately **schema-agnostic**: it knows only four fields about a\ntype (`kind`, `type`, `description`, `template`) and writes the `template` to a\npath. The record\'s *actual* schema lives inside the template body (frontmatter\nkeys + commented guidance). So adding the next record type is **a template and\nfour fields**, never a change to the `record` command.\n\n---\n\n## Create a record: `harness record <type>`\n\n```bash\nharness record retro --slug "harness-flow-skill" # → .harness/records/retro/<date>-harness-flow-skill.md\nharness record retro # no slug → .harness/records/<date>.md\nharness record retro --slug x --json # machine-readable envelope\n```\n\nThe success Envelope reports the path — that\'s where your agent "hops to" next:\n\n```bash\n$ harness record retro --slug "harness-flow-skill" --json\n{"command":"record","status":"ok","timestamp":"…",\n "data":{"type":"retro","path":".harness/records/retro/2026-06-09/001-harness-flow-skill.md","source":"core"},\n "evidence":[{"label":"retro record","path":".harness/records/retro/2026-06-09/001-harness-flow-skill.md"}],\n "next_action":"Open and fill .harness/records/retro/2026-06-09/001-harness-flow-skill.md, then save."}\n```\n\nFire-and-fill: the CLI does placement + creation; the agent reads the returned\nfile and fills the values.\n\n### Placement & ordinal (the locked rule)\n\n```\n.harness/records/<type>/<YYYY-MM-DD>/001-<slug>.md ← first record that day\n.harness/records/<type>/<YYYY-MM-DD>/002-<slug>.md ← next → 002\n.harness/records/<type>/<YYYY-MM-DD>/003-<other>.md ← a different slug shares the per-day sequence\n```\n\n- The date comes from the system clock (UTC) and is the **directory**; `<NNN>`\n is a per-day, per-type **ordinal** (`001`, `002`, …) = 1 + the highest already\n present, so records sort chronologically within the day.\n- `--slug` is optional and is slugified to `[a-z0-9-]`; absent → an ordinal-only\n filename (`<NNN>.md`).\n- The `<type>/<date>/` directories are created if missing. `.harness/` itself is\n **not** scaffolded — if there\'s no `.harness/`, `record` reports `unconfigured`\n (exit 2) with a `next_action`, and writes nothing.\n- It **never clobbers**: the ordinal always yields a fresh path.\n\n### Status & exit codes\n\n| Path | Status | Exit | Code |\n|------|--------|------|------|\n| created OK | `ok` | 0 | — |\n| no `.harness/` here | `unconfigured` | 2 | — |\n| unknown `<type>` | `error` | 1 | `E180` (lists known types) |\n| invalid/empty `--slug` | `error` | 1 | `E108` |\n| dir create / write failed | `error` | 1 | `E181` |\n\n---\n\n## Discover types: `harness record --list`\n\n```bash\nharness record --list # human listing (bare `harness record` shows the same)\nharness record --list --json # data.types[]: { type, description, source, entryPath? }\n```\n\n`harness doctor` also reports a `record-types` line enumerating the same\ncore ∪ extension types.\n\n---\n\n## Author a new record type\n\nA record type needs only four fields:\n\n```ts\nimport type { HarnessRecordType } from \'@ai-substrate/engineering-harness/contract\';\n\nconst devSurvey: HarnessRecordType = {\n kind: \'record\', // discriminator (vs a verb)\n type: \'dev-survey\', // the <type> arg + dir name; ^[a-z][a-z0-9-]*$\n description: \'Developer-experience survey.\', // shown by --list + doctor\n template: `---\\nrecord_type: dev-survey\\n---\\n…`, // the body the agent fills (this IS the schema)\n};\n\nexport default devSurvey;\n```\n\n### Fast path: `harness new <name> --record`\n\n```bash\n$ harness new dev-survey --record\n# → .harness/extensions/dev-survey.record.ts (a loadable record-type stub)\n```\n\nEdit the template body, then `harness record dev-survey` works and `harness\ndoctor` shows it under record-types. (Routing is by the `kind:\'record\'` field —\nthe `.record.ts` suffix is just a human hint.)\n\n### Core vs extension\n\n- **Core types** (bundled, e.g. `retro`) are always present, even under\n `--no-extensions`.\n- **Extension types** are discovered from `.harness/extensions/` by the same\n loader that loads verbs. Isolation is identical: a load failure is non-fatal\n (`E140`, reported by `doctor`); a malformed type is skipped + recorded.\n- **Conflicts are deterministic**: an extension declaring an existing core `type`\n → core wins, the conflict is recorded by `doctor` (never fatal); two extensions\n with the same `type` → first-loaded wins, the other is recorded and skipped.\n\n### Naming rules\n\n- Lowercase, hyphenated, starting with a letter: `retro`, `dev-survey`.\n- `record` is a reserved core command — `harness new record` is rejected (`E151`).\n\n---\n\n## Two storage classes: committed records vs transient observations\n\n| | `.harness/records/` | `.harness/temp/` |\n|---|---|---|\n| What | Committed records you fill (`harness record <type>` output) | Gitignored crash-resilient agent scratch — incl. observation buffers (`harness observe` output) |\n| Git | **Tracked** — team memory | **Ignored** — never committed |\n| Lifetime | Durable | Survives `/compact`, then drained |\n\n### Capturing observations: `harness observe`\n\nIn-flight friction is captured with one command — the CLI owns the buffer path,\nper-kind sequential IDs, ISO timestamps, schema validation, and the gitignore\nguarantee; the agent supplies only the noticing:\n\n```bash\nharness observe "grep on src/ took 47s — should use ripgrep" \\\n --kind difficulty --target tooling --severity degrading --json\n# → data: { "bucket":"agent", "id":"DL-001", "kind":"difficulty",\n# "path":".harness/temp/agent/session-buffer.md" }\n```\n\n- Kinds: `difficulty | magic-wand | gift | insight | coordination |\n improvement-suggestion | confusion`; severities `blocking | degrading | annoying`.\n- Identity is optional provenance: `--agent <slug>` → `HARNESS_AGENT` env → a\n shared `agent` bucket. Capture never fails on identity.\n- Bad input (`unconfigured`, exit 2) names the allowed values and leaves the\n buffer untouched; an unreadable buffer is `error` exit 1 (`E146`) — never\n silent data loss.\n\n### Draining observations into a committed record\n\n```bash\nharness observe --list --json # all buckets by default; --agent <slug> scopes\nharness record retro --slug "<label>" --json # scaffold the committed record\n# … write the drained entries into the returned data.path …\nharness observe --clear # remove the drained entries (files kept)\n```\n\n`--list` returns entries bucket-annotated plus a `malformed_skipped` count\n(deviant hand-written blocks are skipped and counted, never silently dropped —\n`--clear` removes only valid entries and leaves deviant text in place for\nmanual review). Capture (`harness observe "<desc>" …`) and `harness record\n<type>` self-heal the `.harness/temp/` nested `.gitignore`; `--list`/`--clear`\nonly read and rewrite buffers. `harness doctor` reports a convention complaint\n(degraded, exit 0) if the temp dir ever exists unprotected.\n\n---\n\n## Verify\n\n```bash\nharness record --list # the type appears (core or extension)\nharness doctor # the record-types line shows it (with provenance)\nharness record <type> # creates a file under .harness/records/<type>/ — `ok` (exit 0)\n```\n\nTo skip extensions entirely (core types only), use `harness --no-extensions\nrecord …` or `HARNESS_NO_EXTENSIONS=1`.\n',
18
+ },
19
+ {
20
+ id: 'using-harness-docs',
21
+ title: 'Using harness docs',
22
+ summary: "Discover and read the harness's bundled docs offline with `harness docs` (and the future MCP `docs_*` seam).",
23
+ audience: 'both',
24
+ content: '# Using harness docs\n\nHow to discover and read the harness\'s curated documentation straight from the\nCLI — **offline**, no network, no `cat`-ing files — with `harness docs`.\n\n> **Where docs live (for now):** user guides live under `docs/how/`. The\n> `harness docs` command surfaces a **curated subset** of them, bundled into the\n> CLI so they ship with `npx` and read the same on any machine. This guide is\n> itself one of those bundled docs.\n\n---\n\n## The model in one minute\n\nThe harness bundles a small, **curated** set of human/agent-facing guides. The\nCLI is the front door to them, mirroring the dogfood rule the rest of the\nharness follows: ask the CLI, don\'t go spelunking through files.\n\n```bash\nharness docs # list the curated docs (id + title + summary)\nharness docs <id> # print one doc\'s full markdown to stdout\n```\n\nThe set is **explicitly curated**, not a blind directory glob — governance and\ninternal documents (`AGENTS.md`, plan artifacts, `scratch/`) are deliberately\nexcluded so nothing half-baked or private leaks into the published surface.\n\n---\n\n## List the docs\n\n`harness docs` lists every bundled doc. In a human terminal it prints a table;\npiped or with `--json` it emits the structured Envelope.\n\n```bash\n$ harness docs\nextend-the-harness Extend the harness Add a new `harness <verb>` command …\nusing-harness-docs Using harness docs Discover and read the bundled docs …\nauthoring-verbs Authoring a harness verb The extension-author contract …\ncli-readme harness CLI README Overview of the harness CLI …\n```\n\n```bash\n$ harness docs --json\n{"command":"docs","status":"ok","timestamp":"…","data":{"docs":[\n {"id":"extend-the-harness","title":"Extend the harness","summary":"…","audience":"both"},\n …\n]}}\n```\n\nEach entry carries an `id` (the argument you pass to read it), a `title`, a\none-line `summary`, and an `audience` (`human` / `agent` / `both`).\n\n---\n\n## Read a doc\n\n`harness docs <id>` prints that document\'s **full markdown to stdout**, exactly\nas authored — ready to pipe into a pager, a file, or an agent\'s context window.\n\n```bash\nharness docs extend-the-harness # print the guide\nharness docs extend-the-harness | less # page it\nharness docs authoring-verbs > verbs.md # save it\n```\n\nBecause the body is written straight to stdout (not wrapped in an Envelope),\nit composes cleanly with Unix tools and is safe to pipe into a `head`/`less`\nthat closes the pipe early.\n\n---\n\n## Exit codes\n\nExit codes are part of the contract:\n\n| Code | Meaning |\n|------|---------|\n| `0` | ok — the list was emitted, or the requested doc was printed |\n| `1` | error — no doc is registered under that id (`E160 DOC_NOT_FOUND`) |\n\n```bash\n$ harness docs no-such-doc\nharness docs: no curated doc with id "no-such-doc" # → E160, exit 1\n → Run `harness docs` to see the available ids.\n```\n\n---\n\n## Offline by design\n\nThe docs are **bundled into the CLI build**, not read from your working tree at\nruntime. A consumer running `harness docs` (from the registry-installed\n`@ai-substrate/engineering-harness`) gets the same content with no repo checkout\nand no network — the docs travel with the binary.\n\nUnder the hood, a build step inlines each curated `.md` into a generated module\nthat `tsc` compiles into `dist`. You never edit that generated module; you edit\nthe source `.md`, and a drift check keeps the two in lock-step.\n\n---\n\n## For tool authors: the `DocsService` seam\n\n`harness docs` is a thin CLI act over a **pure** `DocsService`\n(`listDocs()` / `getDoc(id)`) that operates only over the bundled doc data —\nno filesystem, no `cwd`, no CLI coupling. That purity is deliberate: a future\n**MCP server** for the harness can expose `docs_list` / `docs_get` tools by\nimporting the *same* `DocsService`, with zero changes to the doc surface. The\nCLI and a future MCP server are two front doors onto one curated corpus.\n',
25
+ },
26
+ {
27
+ id: 'authoring-verbs',
28
+ title: 'Authoring a harness verb',
29
+ summary: 'The extension-author contract: `HarnessVerb`, `VerbContext`, structured output, exit codes, and safety rules.',
30
+ audience: 'both',
31
+ content: "# Authoring a harness verb\n\nA **harness extension** is a TypeScript (or JavaScript) file in your repo's\n`.harness/extensions/<name>/` package folder whose entry default-exports one or more **verbs**. Each\nverb becomes a top-level `harness <verb>` command with its own `--help`,\noptions, structured Envelope output, and exit code.\n\n> The core ships no built-in verbs. Everything you can run beyond `help`,\n> `doctor`, `new`, and `docs` is something an extension contributed.\n\n> **Start here:** the fastest way to create one is `harness new <name>` — it\n> scaffolds a loadable stub for you (see [`docs/how/extend-the-harness.md`](../../../docs/how/extend-the-harness.md)).\n> Add `--wrap \"<command>\"` to wrap a real repo command, or `--js` for a plain-JS\n> starter. This page documents the contract that scaffolded file follows.\n\n## 1. Where extensions live\n\nDiscovery scans `<cwd>/.harness/extensions/` **one level deep**, in sorted name\norder. Every extension is a **folder** (a little package); within it the entry\nresolves manifest → `extension.ts` → `extension.js` → `index.ts` → `index.js`:\n\n```\n.harness/extensions/\n├── hello/\n│ ├── extension.ts # the canonical entry → loaded\n│ └── instructions.md # agent briefing (`harness instructions hello`)\n├── build/\n│ └── extension.js # a .js entry → loaded (no transpile)\n├── seed/\n│ └── index.ts # index fallback → loaded\n├── lint/\n│ ├── package.json # { \"harness\": { \"extensions\": [\"main.ts\"] } }\n│ ├── main.ts # resolved via the manifest → loaded\n│ └── lib/extra.ts # free-form internals, imported relatively\n└── legacy.ts # a FLAT file → rejected (E143; move to legacy/extension.ts)\n```\n\n- `.ts` / `.tsx` entries are loaded with [jiti](https://github.com/unjs/jiti)\n (full transpile — enums, etc.); `.js` / `.mjs` / `.cjs` load via native\n `import()` (no transpile, fastest). `.tsx`/`.mjs`/`.cjs` entries are reachable\n only via the manifest. Package-internal relative imports\n (`./lib/extra.ts`) resolve through the same loader.\n- Beside the entry, the convention requires an `instructions.md` — the briefing\n for the calling agent, served verbatim by `harness instructions <verb>`.\n Missing it never blocks the verb, but `harness doctor` wails (`E144`).\n- If two extensions declare the same verb name, the **first (sorted) wins**; the\n duplicate is reported by `doctor` as a conflict (never silently dropped).\n- `help`, `doctor`, `new`, `docs`, `skills`, `record`, and `instructions` are reserved core commands — an extension can't shadow them.\n- Absent / empty folder is **not** an error: `help` says \"no extensions\n installed yet\".\n\n## 2. The contract\n\nImport the types from the published package (they're erased at runtime, so even\na plain `.js` extension can reference them via JSDoc with no runtime dependency):\n\n```ts\nimport type { HarnessVerb, VerbContext, VerbResult } from '@ai-substrate/engineering-harness/contract';\n```\n\nA verb is a declarative object:\n\n```ts\nexport interface HarnessVerb {\n name: string; // 'build' → `harness build`\n summary: string; // one line — shown in `help` + `doctor`\n description?: string; // longer body shown by `harness <verb> --help`\n options?: VerbOption[]; // commander-style flags: { flags: '--name <name>', description, defaultValue? }\n args?: VerbArg[]; // commander-style: { name: '<target>', description } — no variadics in v1\n run(ctx: VerbContext): VerbResult | Promise<VerbResult>;\n}\n```\n\n> **v1 limitation:** positional args are single-valued — a variadic arg\n> (`<files...>`) is rejected at load time (the extension is reported as `failed`\n> by `doctor`) because `ctx.args` values are `string | undefined`. Use a\n> repeatable option or a comma-separated value instead.\n\nYour `run` handler receives a `VerbContext` and returns a `VerbResult`. You\nnever build the Envelope or call `process.exit` yourself — the kernel finalizes\nyour result (adds `command` + `timestamp`, maps status → exit code).\n\n## 3. The context (`ctx`)\n\n```ts\ninterface VerbContext {\n cwd: string; // the repo cwd\n args: Record<string, string | undefined>; // parsed positionals, by arg name\n options: Record<string, unknown>; // parsed flags, by camelCased name\n\n // Wrap a REAL repo command (the point of a verb — \"wrap, don't rebuild\"):\n exec(command: string, args?: string[], opts?: { cwd?: string }): Promise<ExecResult>;\n // → { code, stdout, stderr, ok } (cwd defaults to ctx.cwd; never throws)\n\n fs: { exists(p): boolean; readText(p): string | null; readdir(p): string[] };\n env: { get(name): string | undefined };\n git: { isRepo(): boolean; currentBranch(): string | null };\n clock:{ nowIso(): string };\n\n // Envelope helpers — one of these is your return value:\n ok<T>(data: T, opts?): VerbResult; // → exit 0\n degraded<T>(data: T, next_action: string, opts?): VerbResult; // → exit 0 (with caveats)\n unconfigured(next_action: string, opts?): VerbResult; // → exit 2 (honest \"not built yet\")\n error(code: string, message: string, opts?): VerbResult; // → exit 1\n}\n```\n\n`next_action` is **required** for any non-`ok` result — the contract guarantees a\nmachine-readable \"what to do next\" on every failure.\n\n## 4. Two worked examples\n\n### `hello/extension.ts` — the minimal verb\n\n```ts\nimport type { HarnessVerb } from '@ai-substrate/engineering-harness/contract';\n\nconst hello: HarnessVerb = {\n name: 'hello',\n summary: 'Say hello.',\n options: [{ flags: '--name <name>', description: 'who to greet', defaultValue: 'world' }],\n run(ctx) {\n return ctx.ok({ greeting: `hello, ${ctx.options.name}` });\n },\n};\nexport default hello;\n```\n\n```bash\n$ harness hello --name pi\n{\"command\":\"hello\",\"status\":\"ok\",\"timestamp\":\"…\",\"data\":{\"greeting\":\"hello, pi\"}} # exit 0\n```\n\n### `build/extension.ts` — wrapping a real command (the point)\n\n```ts\nimport type { HarnessVerb } from '@ai-substrate/engineering-harness/contract';\n\nconst build: HarnessVerb = {\n name: 'build',\n summary: 'Build the project (wraps `npm run build`).',\n async run(ctx) {\n const r = await ctx.exec('npm', ['run', 'build']);\n return r.ok\n ? ctx.ok({ command: 'npm run build' }, { evidence: [{ label: 'build log', none: true }] })\n : ctx.error('E1', `build failed (exit ${r.code})`, {\n details: r.stderr,\n next_action: 'Fix the build error above.',\n });\n },\n};\nexport default build;\n```\n\n`harness build` runs `npm run build`; the exit code mirrors the child (0 on\nsuccess, 1 on failure), and the failure carries the stderr + a next action.\n\n## 5. Trust & safety\n\nExtensions are **arbitrary code with full Node privileges** — the same trust\nmodel as ESLint/Prettier/Vite plugins (\"if you run this repo, you already trust\nits code\"). There is no sandbox. Two safety affordances:\n\n- **Per-extension isolation** — a broken extension is reported by `doctor`\n (`E140`) and skipped; the others still load. A handler that throws becomes an\n `E141` error Envelope (no raw stack), never a crash.\n- **Safe mode** — `harness --no-extensions <cmd>` or `HARNESS_NO_EXTENSIONS=1`\n skips discovery entirely (core commands only).\n\n## 6. Checking your work\n\n```bash\nharness doctor # lists every extension: loaded / failed / conflict, with paths + errors\nharness help # lists the verbs your extensions contributed\nharness <verb> --help # the commander-generated usage for one verb\n```\n\nThe fastest way to get a starter is `harness new <name>` (see\n[`docs/how/extend-the-harness.md`](../../../docs/how/extend-the-harness.md)).\nCopyable static starters also live in [`../examples/extensions/`](../examples/extensions/).\n",
32
+ },
33
+ {
34
+ id: 'cli-readme',
35
+ title: 'harness CLI README',
36
+ summary: 'Overview of the harness CLI: install/run via npx, the command surface, output modes, and exit codes.',
37
+ audience: 'both',
38
+ content: '# harness — engineering harness CLI\n\nThe agent-friendly **front door** to this repo\'s engineering harness. A small, well-structured Node + TypeScript (ESM) CLI whose verbs are **owned by extensions**: each is a little package at `.harness/extensions/<name>/` (entry `extension.ts`, agent briefing `instructions.md`) and becomes a `harness <verb>` command with its own `--help`, options, structured output, and exit codes. A few commands are always built in (`help`, `doctor`, `instructions`, `new`, `docs`, `skills`, `record`); everything else is contributed by extensions you add.\n\n> This is the **engineering harness** (the project\'s development loop), not an agent runtime. Its job is to turn the repo\'s **deterministic layer** — build, run, test, proof, sensors, evidence — from diffuse scripts and tribal knowledge into a **first-class, discoverable artifact** with one focal point: this CLI. When something had to be inferred twice, it gets encoded here as a command; `--help` and `doctor` make the layer enumerable instead of remembered.\n\n## Install / run\n\nThe CLI is published to the **public npm registry** as `@ai-substrate/engineering-harness` (bin `harness`) — install it like any public package, **no auth or `.npmrc` setup** (Node `>= 22`):\n\n```bash\nnpm install -g @ai-substrate/engineering-harness\nharness doctor\n```\n\n…or run it without installing: `npx @ai-substrate/engineering-harness doctor`.\n\nThe published tarball bakes in the built `dist` and declares `commander` + `jiti` as runtime dependencies, so an `--omit=dev` install resolves everything — no install-time build, no git-clone fragility. Pin a version the usual way (`@ai-substrate/engineering-harness@0.3.0`). Every release is published with **npm provenance**, so each version links back to the GitHub Actions run + commit that built it.\n\nFor local development in this repo:\n\n```bash\nnpm install\nnpm run build\nnode harness/cli/dist/index.js doctor\n```\n\n### Keeping it current\n\nOnce installed globally, the CLI keeps **itself** fresh from the same registry:\n\n```bash\nharness update # upgrade the global install to @latest (no-op if already current)\nharness update --check # report installed vs latest — exit 0, installs nothing\nharness update --pin v0.3.0 # install one exact version (not persisted)\nharness self-install # first-time global bootstrap from the registry\n```\n\n`harness update` announces, then runs, `npm i -g @ai-substrate/engineering-harness@latest`. A failed install maps to an actionable error: the registry rejected it (the package is public — check `npm config get registry` and that the version is published), a denied global install (use a Node version manager such as nvm/Volta, or a prefix-writable npm), or npm absent.\n\n**Staleness is hard to miss.** The registry lookup is **throttled to once per 24h** and **cached** under `~/.harness/` (failure-silent). `harness update` and `harness update --check` perform that lookup; once a newer version is cached, **every** command surfaces it from a single fast cache read (no per-command network call) until you update — a top-level `update_available` field in JSON output, and one `update available to <latest> from <installed> — run: harness update` line on **stderr** in human mode (never stdout or the JSON payload). Run `harness update --check` periodically — a daily cron, a CI step, or your agent\'s session-start — to keep the signal fresh. (Ordinary commands deliberately never touch the network, so the CLI stays instant.)\n\n**Skills too.** The harness binary (this registry package) and its **skills** (installed via `npx skills`) are two channels; `harness update --target <cli> [--global]` reconciles both — it upgrades the CLI *and* refreshes + prunes this harness\'s skills for that CLI (reusing the `harness skills update` path). Without `--target` it *reports* the skills situation (what would refresh/prune) without changing anything. Full guide: [keeping the harness up to date](../../docs/how/keeping-the-harness-up-to-date.md).\n\n## Extensions: the focal point\n\nThe core ships **no** built-in verb list. In your *own* repo, each extension is a little **package folder**:\n\n```\n<your-repo>/\n└── .harness/\n └── extensions/\n ├── hello/\n │ ├── extension.ts ← the entry (default-exports a HarnessVerb)\n │ └── instructions.md ← the agent briefing (`harness instructions hello`)\n └── build/\n ├── extension.ts\n └── instructions.md\n```\n\nEach entry **default-exports** a `HarnessVerb` (or an array of them). The installed core discovers `.harness/extensions/` at runtime, resolves each folder\'s entry (manifest → `extension.ts` → `extension.js` → `index.ts` → `index.js`), loads it (`.ts`/`.tsx` via jiti, `.js` natively — package-internal relative imports like `./lib/helper.ts` just work), and registers one `harness <verb>` command per declared verb. Flat files directly under `extensions/` are rejected (`E143`) with doctor guidance.\n\n**Quick start — install an extension** (in your repo):\n\n```bash\nmkdir -p .harness/extensions/hello\ncat > .harness/extensions/hello/extension.ts <<\'TS\'\nimport type { HarnessVerb } from \'@ai-substrate/engineering-harness/contract\';\n\nconst hello: HarnessVerb = {\n name: \'hello\',\n summary: \'Say hello.\',\n options: [{ flags: \'--name <name>\', description: \'who to greet\', defaultValue: \'world\' }],\n run(ctx) {\n return ctx.ok({ greeting: `hello, ${ctx.options.name}` });\n },\n};\nexport default hello;\nTS\n\nharness hello --name pi # → {"command":"hello","status":"ok","data":{"greeting":"hello, pi"}}\nharness hello --help # commander-generated usage from the verb\'s options\nharness help # lists hello among the installed verbs\nharness doctor # enumerates which extensions loaded / failed (and wails if instructions.md is missing)\n```\n\nSee [`docs/authoring-verbs.md`](./docs/authoring-verbs.md) for the full contract, and copyable starters in [`examples/extensions/`](./examples/extensions/).\n\n## Command surface\n\n| Command | What it does | Status |\n|---------|--------------|--------|\n| `harness help` | Explain purpose, the **dynamic verb list**, output modes, safe first actions. `help --json` is machine-readable (`data.verbs[]`). | ✅ core |\n| `harness doctor` | Report readiness (toolchain, cli-build) **and enumerate the installed extensions** (loaded / failed / conflict, with paths + errors) — without invoking any verb. Safe at session start. | ✅ core |\n| `harness new <name>` | Scaffold a new, immediately-loadable extension package into `./.harness/extensions/<name>/` (entry + starter `instructions.md`). | ✅ core |\n| `harness docs [id]` | List the bundled, curated docs (`harness docs`), or print one verbatim to stdout (`harness docs <id>`). Offline; ships with the CLI. | ✅ core |\n| `harness skills install` | Install **this harness\'s own skills** into a CLI — a transparent pass-through to Vercel\'s [`npx skills add`](https://github.com/vercel-labs/skills). Picks target(s) (`--target claude-code\\|codex\\|cursor\\|github-copilot\\|opencode\\|pi`, repeatable) and scope (`--global` or project-local). **Announces the exact `npx` line before running** and always passes `-y` (the blocking picker never appears). Missing `--target` → `E108` (non-blocking). | ✅ core |\n| `harness skills update` | Refresh this harness\'s skills to latest **and prune** renamed/removed ones. Same target/scope flags as `install`; wraps `npx skills add` (refresh + pull new) **then** `npx skills remove` of the renamed-away slugs (the installer has no native prune, so a rename would otherwise leave a stale twin). Announces both commands; refresh failure aborts before pruning (no regression). | ✅ core |\n| `harness update` | Keep the globally-installed CLI current from the registry. Bare: upgrade to `@latest` (no-op if already current, not an error). `--check`: report installed vs latest (exit 0, no install). `--pin vX.Y.Z`: one exact version. `--target <cli> [--global]`: **also** reconcile this harness\'s skills (refresh + prune). Every `update` envelope carries a `skills` sub-object (report-only without `--target`). Announces the `npm i -g` line; failures map to actionable errors (registry `E201` / permission `E202` / npm-missing `E203` / pinned-not-found `E204`). | ✅ core |\n| `harness self-install` | First-time global bootstrap — installs `@ai-substrate/engineering-harness@latest` from the public npm registry (no auth needed); a registry/transport failure returns an actionable `next_action`. | ✅ core |\n| `harness <verb> […]` | Any verb a discovered extension contributes, with its own `--help`, options, args, Envelope, and exit code. | 🧩 extension |\n\n`help`, `doctor`, `new`, `docs`, `skills`, `update`, and `self-install` are **reserved** core commands — no extension can shadow them (doctor is the diagnostic that *checks* the extension system). Safe mode: `--no-extensions` or `HARNESS_NO_EXTENSIONS=1` skips discovery entirely (core commands only).\n\n```bash\nharness help --json # machine-readable verb map (data.verbs[])\nharness doctor # readiness + extension enumeration\nharness docs # list the bundled docs\nharness docs extend-the-harness # print one doc\'s markdown to stdout\nharness skills install --target github-copilot --global # install this harness\'s skills (wraps npx skills add)\nharness skills update --target github-copilot --global # refresh to latest + prune renamed/removed\nharness --no-extensions help # core-only (skip discovery)\n```\n\n## Output modes\n\n**Most** commands emit a stable **envelope** in one of two renderings:\n\n- **JSON** — one parseable line on stdout (for agents / pipes).\n- **Human** — a readable summary on stdout, diagnostics + next action on stderr.\n\nThe one deliberate exception is `harness docs <id>`, which writes the doc\'s **raw\nmarkdown** to stdout (no envelope, in both modes) so it pipes/redirects cleanly —\nmirroring how a doc dump should behave. The doc **list** (`harness docs`) still\nuses the envelope.\n\nSelection precedence (highest wins):\n\n1. `--json` / `--no-json` flag\n2. `HARNESS_JSON=1` environment variable (handy in CI)\n3. TTY detection — piped output → JSON, an interactive terminal → human\n\n## Exit codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | `ok` or `degraded` — the command reported successfully. |\n| `1` | `error` — something failed; see `error.code` + `next_action`. No raw stack traces. |\n| `2` | `unconfigured` — a verb reported it has no behaviour mapped yet. |\n| `E140/E141/E142` | (in `error.code`) extension load failure / runtime throw / verb-name conflict. |\n| `E160` | (in `error.code`) `harness docs <id>` — no curated doc with that id (exit 1). |\n| `E200`–`E204` | (in `error.code`) `harness update`/`self-install` — generic failure / registry rejected (unexpected auth, or not-yet-published / unreachable) / global-install permission denied / npm not on PATH / pinned version not in registry. |\n\n`unconfigured → 2` is deliberate: a script or agent can distinguish "not built yet" (2) from "broke" (1), and `doctor` still exits `0` because it succeeded at *reporting*.\n\n## Envelope shape\n\n```jsonc\n{\n "command": "doctor",\n "status": "ok | error | degraded | unconfigured",\n "timestamp": "2026-06-08T07:20:00.000Z",\n "data": { /* command-specific */ },\n "error": { "code": "E120", "message": "...", "details": [/* ... */] },\n "evidence": [{ "label": "doctor report", "none": true }],\n "next_action": "Always present when status is not ok."\n}\n```\n\n## Architecture\n\nPorts & Adapters (Hexagonal): a thin commander **entrypoint** (`index.ts` → `app.ts`\'s async `main`) discovers + loads extensions, then registers per-command **acts** → adapter-agnostic **services** → injected **adapters** (`fs` / `process` / `git` / `env` / `clock` / **`exec`** for wrapping real commands / a **module loader** for jiti). Extension verbs receive a `VerbContext` of those ports + envelope helpers and return a `VerbResult` the kernel finalizes. Business logic lives in services and is unit-tested through fakes with zero real I/O. See the authoritative design in [`docs/plans/005-harness-extension-system/workshops/001-extension-contract-and-loader.md`](../../docs/plans/005-harness-extension-system/workshops/001-extension-contract-and-loader.md), the output/exit contract in [`docs/plans/004-harness-core/workshops/001-output-envelope-and-exit-codes.md`](../../docs/plans/004-harness-core/workshops/001-output-envelope-and-exit-codes.md), and the authoring guide in [`docs/authoring-verbs.md`](./docs/authoring-verbs.md).\n\n## Continuous Integration & Release\n\nCI runs on every pull request and on pushes to `main` (`.github/workflows/ci.yml`):\n\n- **`build-test`** — Node 22 & 24 matrix: `npm ci` → Biome check → build → `tsc --noEmit` → `vitest run --coverage` → `npm audit` (advisory). Coverage prints a text summary and uploads `harness/cli/coverage/lcov.info` as an artifact.\n- **`package-smoke`** — packs the tarball, installs it into a clean temp project with `--omit=dev`, drops a real `.harness/extensions/hello/extension.ts` package fixture, and asserts the installed `harness` bin discovers + jiti-loads the verb and runs it (proving jiti resolves as a runtime dependency), plus a flat `legacy.ts` file is rejected with `E143` — the npx/bin-symlink + extension contract end-to-end.\n- **`ci-required`** — a stable aggregation job that fails if any required job failed. Branch protection requires this one matrix-independent check.\n\n**Releases** are automated with `release-please` (`.github/workflows/release.yml`, `release-please-config.json`, `.release-please-manifest.json`): conventional commits on `main` open a Release PR that bumps the version and updates `CHANGELOG.md`; merging it tags a semver release **and the `publish` job pushes `@ai-substrate/engineering-harness` to the public npm registry with provenance** (gated on release-please actually cutting a release). A branch-dispatchable **canary** job proves the publish + **anonymous-install** path before merge (push a `canary/**` branch, or `workflow_dispatch` once the workflow is on `main`). Install from npm — see *Install / run* above.\n\n**Branch protection** on `main` requires the `ci-required` check to pass before merge. Applied with (requires repo admin):\n\n```bash\ngh api -X PUT repos/AI-Substrate/harness-engineering/branches/main/protection --input - <<\'JSON\'\n{\n "required_status_checks": { "strict": false, "contexts": ["ci-required"] },\n "enforce_admins": false,\n "required_pull_request_reviews": null,\n "restrictions": null\n}\nJSON\n```\n\nVerify with `gh api repos/AI-Substrate/harness-engineering/branches/main/protection`.\n',
39
+ },
40
+ {
41
+ id: 'agents-readme',
42
+ title: 'Agent onboarding (the drop-file)',
43
+ summary: 'Self-contained bootstrap guide: point an agent at a repo and it installs the harness CLI + skills, orients itself, and enters the loop.',
44
+ audience: 'both',
45
+ content: "# Set up and run an engineering harness — agent onboarding\n\n> **For AI agents reading this**: this is the canonical self-contained guide for standing up — or operating — a repo-local engineering harness using the harness CLI and skills published from [`AI-Substrate/harness-engineering`](https://github.com/AI-Substrate/harness-engineering). If a human gave you this document (or its URL) and pointed you at a repository, your mission is below. You need nothing else: no access to the harness-engineering repo, no prior knowledge of the product. **This file is re-entrant** — you may be reading it for the first time, or again after a restart wiped your context. Either way, start at *Where are you?* below; it self-locates.\n>\n> **For humans**: paste this into your agent — *\"Read https://raw.githubusercontent.com/AI-Substrate/harness-engineering/main/AGENTS_README.md and follow it in this repo.\"* If your agent loses context partway (including the restart in Stage 4), feed it the same line again — the guide picks up where it left off.\n\n**Audience note**: this guide is for agents working in **consumer repos** (any codebase that wants a harness). If you are contributing to the harness-engineering repo itself, read its `AGENTS.md` instead — that is a different job (probe row 0 below catches this).\n\n---\n\n## Where are you? (run this first)\n\nCheck the rows **top-to-bottom**; the **first row whose condition passes** tells you where to go. A row fails when its condition is not met — fall through to the next.\n\n| # | Probe (passes if…) | You are | Go to |\n|---|---|---|---|\n| 0 | The root `package.json` declares `\"name\": \"@ai-substrate/engineering-harness\"` (this repo IS the package, not a dependency) | In the harness's own home repo | **Stop** — this file is for consumer repos. Read `AGENTS.md` instead; never run `eng-harness-0-adopt` here. |\n| 1 | `eng-harness-flow` appears in **your own list of invocable skills/commands** (introspect your skill registry — not `harness help`, which lists CLI verbs, not agent skills) | Skilled — ready to go | [Stage 5 — Go](#stage-5--go-enter-the-harness-loop) |\n| 2 | `ls ./.claude/skills ./.agents/skills 2>/dev/null \\| grep eng-harness` is non-empty (skill **files** on disk) but row 1 failed | Pre-restart — skills installed but not loaded | [Stage 4 — Restart](#stage-4--restart-load-the-skills) |\n| 3 | `test -f .harness/engineering-harness.md && test -d .harness/extensions/boot` (harness substrate exists; no skills loaded) | Run 2+, unskilled | [Operating an existing harness](#operating-an-existing-harness-run-2) |\n| 4 | `npx --no-install harness --version` exits 0 (the repo-local CLI resolves) | CLI installed, no skills | [Stage 3 — Install the skills](#stage-3--install-the-harness-skills-project-local) |\n| 5 | None of the above passed | Fresh repo | [Stage 1 — Install the CLI](#stage-1--install-the-harness-cli-into-the-repo) |\n\nTwo of these probes only **you** can answer:\n\n- **Row 1** is introspection of your own loaded skills — no file check can answer it.\n- **Which CLI you are** (needed in Stage 3): you know your own runtime — Claude Code → `claude-code` · Copilot CLI → `github-copilot` · Codex → `codex` · Cursor → `cursor` · OpenCode → `opencode` · pi → `pi`. If genuinely unsure, ask your human; never guess.\n\n---\n\n## What you're building\n\nAn **engineering harness** makes a repo's development loop explicit and operable:\n\n```text\nBoot → Backpressure Check → Do Work and Observe → Retro and Magic Wand → Improve\n```\n\nThe nucleus in one line: **[CLI focal point + required agent use] + [deterministic back pressure] + [friction capture] + [human-selected encoding] = engineering harness.**\n\nConcretely, you will leave behind:\n\n- the **`harness` CLI** installed as a repo dependency — the agent-friendly front door (`harness help`, `harness doctor`, structured JSON envelopes, honest exit codes);\n- a **`boot` verb** that proves the product runs (wraps the repo's canonical test/build/smoke lane);\n- the **harness skills** installed project-local, so future agents get the choreography, not just the tools;\n- an **`AGENTS.md` route**, so the next agent in this repo finds all of the above at session start;\n- committed **records** (`.harness/records/`) carrying forward what was learned.\n\nThe harness never fakes success. A `boot` that honestly reports `degraded` or `unconfigured` (with a `next_action`) is correct behaviour. If the repo genuinely cannot be harnessed, saying so — grounded in evidence — is a valid outcome.\n\n---\n\n## Stage 1 — Install the harness CLI into the repo\n\nThe CLI is a Node package installed straight from GitHub (Node >= 22 required; pin a tag with `#vX.Y.Z` for reproducibility):\n\n```bash\n# Repos with no package.json (typical for Python/Go): npm walks UP to the\n# nearest manifest and installs OUTSIDE the repo. Seed a manifest first:\n[ -f package.json ] || npm init -y\n\nnpm install github:AI-Substrate/harness-engineering\n```\n\nThe first run clones and compiles TypeScript via the package's `prepare` step — expect **20–60 seconds**. It is not hung.\n\nVerify — this must print the CLI's usage:\n\n```bash\nnpx --no-install harness --help\n```\n\n> **Never run bare `npx harness`** — that fetches an unrelated `harness` package from the npm registry. After the install above, always use `npx --no-install harness …` (or `./node_modules/.bin/harness …`) so you get the repo-local binary. This applies to **consumer installs** (harness as a dependency — the bin link is proven by the package-smoke CI job); inside the harness-engineering source repo itself, invoke the bin via node directly (`node harness/cli/bin/harness.js …`) — `npx` resolution of a root package's *own* bin is unreliable across npm majors.\n\n**If the install fails in the `prepare` build step** (the package compiles TypeScript on install, and a consumer repo's own toolchain can interfere): build a clean tarball outside the repo and vendor it —\n\n```bash\ngit clone --depth 1 https://github.com/AI-Substrate/harness-engineering /tmp/he-build\n(cd /tmp/he-build && npm install --ignore-scripts && npm run build && npm pack)\nmkdir -p .harness/vendor\ncp /tmp/he-build/ai-substrate-engineering-harness-*.tgz .harness/vendor/\nnpm install ./.harness/vendor/ai-substrate-engineering-harness-*.tgz\n```\n\nThis also makes the install deterministic for everyone who clones the repo later — `npm install` restores the harness from the vendored artifact with no network fetch.\n\n## Stage 2 — Read the CLI's own briefing\n\n```bash\nnpx --no-install harness instructions # the agent briefing — envelope contract, self-briefing loop\nnpx --no-install harness help --json # the verb map + safe first actions\nnpx --no-install harness docs # bundled offline docs (then `harness docs <id>`)\n```\n\nThe contract in one breath: every command emits one envelope `{command, status, data, error?, next_action?, timestamp}`; `status` is `ok | degraded | unconfigured | error`; exit codes are `0` (ok/degraded), `2` (unconfigured), `1` (error); `next_action` is required on any non-ok status — **follow it before improvising**. Pass `--json` for machine-readable output (piped output auto-selects JSON).\n\n## Stage 3 — Install the harness skills (project-local)\n\nThe CLI brings determinism; the **skills** bring the choreography. Install them into the repo for your agent CLI (the self-ID map in *Where are you?* tells you your `--target` value):\n\n```bash\nnpx --no-install harness skills install --target <your-cli>\n# targets (repeatable): claude-code | codex | cursor | github-copilot | opencode | pi\n```\n\nThis is a transparent pass-through to `npx skills add` (it announces the exact command before running, always non-interactive). The source defaults to `AI-Substrate/harness-engineering` — no flags needed beyond the target. Omit `--global` so the skills land **project-local** (e.g. `./.agents/skills/`, `./.claude/skills/`): the repo should stand alone for the next agent.\n\nInstalling from a branch (pre-release/pinned): add `--branch <ref>` — or write the source as `--source owner/repo#ref`. Single-segment branch names only; the underlying installer cannot express slashed refs (you'll get a clear `E108` if you try).\n\n**Consent note**: if your operator asked you to set up the harness end-to-end (including by handing you this document), that instruction is your go-ahead for a project-local install. If you are pairing interactively with a human, offer first.\n\nWhat you get — **seven skills, two groups**:\n\n| Skill | Group | Purpose |\n|---|---|---|\n| `eng-harness-flow` ⭐ | loop | **The front door** — stateless router; detects where the repo is on the loop and hands back the one right next command. After Stage 4, this is the only skill you need to remember. |\n| `eng-harness-0-adopt` | setup | The adoption flow — install, assess, inject, stand up `boot`, route agents. |\n| `eng-harness-0-harnessability-assessment` | setup | Score the repo's harnessability; map back-pressure surfaces and proof ceilings. |\n| `eng-harness-0-add-extension` | setup | Guided authoring of a new `harness <verb>` extension. |\n| `eng-harness-1-boot` | loop | Boot stage — validate harness health at session start. |\n| `eng-harness-2-backpressure` | loop | Advisory survey of deterministic-sensor coverage for the current scope. |\n| `eng-harness-4-retro` | loop | The friction lifecycle — capture (via `harness observe`), drain, harvest. |\n\n> Heads-up: your own CLI most likely won't *see* these new skills until your operator reloads it — that's Stage 4, next.\n\n## Stage 4 — Restart (load the skills)\n\nThe coding harness you are running inside enumerates its skills **at session start** — skills installed mid-session usually cannot be invoked until it is reloaded. Check first: **can you invoke `/eng-harness-flow` right now?** If yes, skip straight to Stage 5 — no restart needed.\n\nIf not: **you cannot reload yourself.** Restarting or reloading the agent CLI is your operator's action, not yours — ask for it and wait. Say exactly this:\n\n> *\"Skills installed. Please restart me (a fresh session), then feed me this same file again — I'll detect the installed skills and continue from where we left off.\"*\n\nOn re-entry, the *Where are you?* table routes you to Stage 5 in one hop.\n\n**No-restart fallback**: the installed skills are just markdown on disk. You can read them and follow them inline without skill invocation:\n\n```bash\ncat ./.claude/skills/eng-harness-0-adopt/SKILL.md # or ./.agents/skills/… per your CLI\n```\n\nIf a SKILL.md proves unreadable or incomplete this way, fall back to the restart script above rather than improvising.\n\n## Stage 5 — Go: enter the harness loop\n\n```text\n/eng-harness-flow\n```\n\n`/eng-harness-flow` is the front door to the harness loop. It is **stateless**: every time you run it, it re-reads the repo's signals and hands back the ONE right next command — finishing adoption if anything is missing (install → assess → governance → a working `boot`, built last), then cycling the loop (Boot → Backpressure → Observe → Retro → Improve). Run it any number of times, at any point, even after your context is wiped — it never guesses, never blocks, and says why it chose what it chose.\n\nWhether the router drives it or you follow the adopt skill inline (Stage 4 fallback), the first-time work has the same shape:\n\n1. **Assess** — run the harnessability assessment. It writes a graded report to `.harness/reports/harnessability/` (Operate-Today and Adaptability axes). If the assessment concludes the repo isn't workable, abandoning with that evidence is a valid, honest outcome.\n2. **Stand up `boot`** — find the repo's canonical \"prove it runs\" command (test suite, build + smoke, dev-server health check), then scaffold a real verb around it:\n\n ```bash\n npx --no-install harness new boot --wrap \"<canonical command>\"\n # e.g. --wrap \"npm test\" or --wrap \"make check\" or --wrap \"pytest -q\"\n ```\n\n This creates `.harness/extensions/boot/` (entry + `instructions.md` briefing). Edit the briefing so it tells the next agent what `boot` proves and what judgment is expected back.\n3. **Verify independently** — don't trust your own scaffold:\n\n ```bash\n npx --no-install harness doctor --json # extension loaded, no convention complaints\n npx --no-install harness instructions boot # the briefing reads true\n npx --no-install harness boot --json # a real run: honest status + legal exit code\n ```\n\nThen finish with Stage 6 — those steps are this guide's, not the router's.\n\n## Stage 6 — Leave the repo better than you found it\n\n### Route future agents (`AGENTS.md`)\n\nThis is what makes run 2 work. Add this block to the repo's `AGENTS.md` (create the file if it doesn't exist; adapt the boot line to what you wrapped):\n\n```markdown\n## Engineering harness\n\nThis repo has an engineering harness. At session start:\n\n1. `npm install` — restores the repo-local `harness` CLI\n2. `npx --no-install harness instructions` — the agent briefing (AGENTS START HERE)\n3. `npx --no-install harness doctor --json` — what's configured + which extensions loaded\n4. `npx --no-install harness boot --json` — prove the product runs before changing it\n\nIf the eng-harness skills are loaded in your CLI, `/eng-harness-flow` routes you\nto the right next harness action at any point.\n\nCapture friction the moment it happens:\n`npx --no-install harness observe \"<what happened>\" --kind difficulty --severity degrading`\nDrain at session end: `harness observe --list --json` → `harness record retro` → `harness observe --clear`.\n```\n\n### Record the journey, then commit\n\nYou just had the most valuable experience this repo will ever get: a fresh entrant's first contact. Capture it before it evaporates:\n\n```bash\n# As you work (the moment friction happens — don't batch):\nnpx --no-install harness observe \"<what happened, 10+ chars>\" \\\n --kind difficulty --severity degrading \\\n --workaround \"<what you did>\" --suggested-encoding \"<how to fix it for the next agent>\"\n# kinds: difficulty | magic-wand | gift | insight | coordination | improvement-suggestion | confusion\n# severities: blocking | degrading | annoying\n\n# At the end — drain the buffer into a committed record:\nnpx --no-install harness observe --list --json\nnpx --no-install harness record retro # returns data.path — write the retro there\nnpx --no-install harness observe --clear\n```\n\nTwo storage classes: `.harness/records/` is **committed team memory**; `.harness/temp/` is **gitignored session scratch** (the capture self-heals that protection; `doctor` checks it).\n\nThen commit everything durable: `package.json` (+ lockfile, + vendored tarball if used), `.harness/extensions/`, `.harness/records/`, `.harness/reports/`, the `AGENTS.md` block, and the project-local skills directory.\n\n---\n\n## Operating an existing harness (run 2+)\n\nSigns a repo already has a harness: a `.harness/` directory, `@ai-substrate/engineering-harness` in `package.json`, an `AGENTS.md` harness block, or skills in `./.agents/skills/` / `./.claude/skills/`.\n\n```bash\nnpm install # restore the CLI from the repo's own manifest\nnpx --no-install harness instructions # the briefing — read it before acting\nnpx --no-install harness doctor --json # what loaded, what failed, why (every complaint has a next_action)\nnpx --no-install harness boot --json # prove it runs BEFORE you change anything\n```\n\nIf the eng-harness skills are loaded in your CLI, `/eng-harness-flow` does all of this routing for you — run it at session start, at phase ends, or whenever you're unsure what the right next harness action is. (Skills installed in the repo but not loaded? Stage 4.)\n\nThen work normally, with the loop around you: run `harness instructions <verb>` before using any verb; capture friction with `harness observe` as it happens; drain into `harness record retro` at session end. If something is missing or broken, `doctor` and the envelope's `next_action` tell you the prescribed fix — follow that before improvising.\n\n---\n\n## Keeping the harness current (update)\n\nTwo things drift independently over time — the **CLI** and the **skills**. Update them in this order (CLI first, so the renamed-skill list it carries is current).\n\n### a) Update the harness CLI\n\nThe CLI is a GitHub dependency, so updating it is a re-install:\n\n```bash\n# Unpinned (tracks the default branch): re-resolve to the latest commit\nnpm install github:AI-Substrate/harness-engineering\n\n# Pinned (`…#vX.Y.Z` in package.json): bump the tag in package.json, then\nnpm install\n\n# Vendored tarball (Stage 1 fallback): rebuild the .tgz the same way, then re-install it\n```\n\nVerify the new build, then commit the changed `package.json` (+ lockfile, + tarball if vendored):\n\n```bash\nnpx --no-install harness --version\nnpx --no-install harness doctor --json\n```\n\n### b) Optionally update the skills\n\n```bash\nnpx --no-install harness skills update --target <your-cli> # add --global if you installed globally\n```\n\n`skills update` does two things the plain installer can't: it **refreshes** every skill to the latest published version (and pulls any newly added ones), then **prunes** skills this harness has since **renamed or removed** — so a rename never leaves a stale twin loading beside its replacement (e.g. an old `harness-1-boot` next to the current `eng-harness-1-boot`). It is a thin wrapper over `npx skills add` + `npx skills remove` and announces both exact commands before running. Run it **after** updating the CLI, since the CLI carries the list of renamed-away slugs to prune. Skills load at session start, so **restart your CLI** afterwards to pick up the changes.\n\n> `npx skills` itself has no prune — its `add`/`update` are additive, so without this a renamed skill's old copy lingers forever. That's the gap `skills update` closes.\n\n---\n\n## What done looks like (first-time adoption)\n\n- [ ] `npx --no-install harness --help` prints usage (CLI is repo-local)\n- [ ] `harness doctor --json` → extensions loaded, no convention complaints\n- [ ] `harness boot --json` → an honest envelope from a **real** run (a `degraded`/`unconfigured` status with `next_action` is honest; a fake `ok` is failure)\n- [ ] Harness skills installed **project-local** (both setup and loop groups — all seven)\n- [ ] `AGENTS.md` carries the harness block (Stage 6)\n- [ ] A retro record exists under `.harness/records/retro/` describing your onboarding — friction, workarounds, and what you'd magic-wand\n- [ ] All of the above committed\n\n## Command surface (cheat sheet)\n\n| Command | What it does |\n|---|---|\n| `harness help` | Purpose, dynamic verb list, output modes, safe first actions (`--json` for the machine-readable map) |\n| `harness instructions [verb]` | The agent briefing (AGENTS START HERE), or one verb's briefing |\n| `harness doctor` | What's configured + which extensions loaded/failed — safe at session start |\n| `harness new <name> [--wrap \"<cmd>\"]` | Scaffold a new extension package into `.harness/extensions/<name>/` |\n| `harness docs [id]` | List/print the bundled offline docs |\n| `harness skills install --target <cli> [--branch <ref>]` | Install this harness's skills (pass-through to `npx skills add`, always non-interactive) |\n| `harness skills update --target <cli> [--global]` | Refresh this harness's skills to latest **and prune** renamed/removed ones (wraps `npx skills add` + `npx skills remove`) |\n| `harness observe \"<desc>\" --kind … --severity …` | Capture one friction observation to the transient buffer |\n| `harness record [type]` | Scaffold a committed record (e.g. `retro`) into `.harness/records/<type>/` |\n| `harness <verb>` | Anything an extension contributes (e.g. `boot`), with its own `--help` and envelope |\n\nAnd once the skills are loaded: `/eng-harness-flow` (a skill, not a CLI verb) routes you to the right next harness action at any time.\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| `npx` seems to hang on the very first run | First-run clone + TypeScript compile (~20–60s) | Wait it out; subsequent runs are fast. |\n| `spawn npx ENOENT` from `harness skills install` (Windows) | The CLI spawns without a shell, which can't launch `npx.cmd` on Windows — known issue, upstream fix pending | Run the exact `npx skills@latest add …` line the command announced before failing — identical result. *(Temporary note: remove once the win32 spawn fix ships.)* |\n| Install fails during the `prepare` build | Consumer repo's toolchain interferes with the package build | Vendor-tarball fallback in Stage 1. |\n| `harness init` → unknown command | The installed CLI predates the deterministic bootstrap | Expected today — skip it; `harness new` creates `.harness/` lazily. |\n| Skills installed but you can't invoke them | Your CLI loads skills at session start | Stage 4 — ask your operator to reload/restart you (you can't do it yourself), then have them re-feed this file. |\n\n## Further reading\n\n- In-CLI, offline: `harness docs` → `cli-readme`, `extend-the-harness`, `authoring-verbs`, `record-and-record-types`\n- Repo docs (if you have web access): [`README.md`](./README.md) (the thesis), [`INSTALL.md`](./INSTALL.md) (per-CLI skills install matrix), [`skills/README.md`](./skills/README.md) (when to run which skill)\n",
46
+ },
47
+ ];
48
+ //# sourceMappingURL=docs-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-content.js","sourceRoot":"","sources":["../../../src/services/docs/docs-content.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,6FAA6F;AAC7F,sCAAsC;AAEtC,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB;QACE,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EACL,wHAAwH;QAC1H,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,4gSAA4gS;KAC/gS;IACD;QACE,EAAE,EAAE,yBAAyB;QAC7B,KAAK,EAAE,yBAAyB;QAChC,OAAO,EACL,2IAA2I;QAC7I,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,o4QAAo4Q;KACv4Q;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EACL,8GAA8G;QAChH,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,m7HAAm7H;KACt7H;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,0BAA0B;QACjC,OAAO,EACL,+GAA+G;QACjH,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,41PAA41P;KAC/1P;IACD;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EACL,sGAAsG;QACxG,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,2ldAA2ld;KAC9ld;IACD;QACE,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,kCAAkC;QACzC,OAAO,EACL,yIAAyI;QAC3I,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,slqBAAslqB;KACzlqB;CACO,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { DocEntry, DocLookup, DocsListResult } from './contract.js';
2
+ /**
3
+ * A single row of the bundled corpus — the index fields (`DocEntry`) plus the
4
+ * full `content`. The generated `DOCS` array is the production corpus; tests
5
+ * inject a fake `DocRecord[]` (fakes-only, no `vi.mock`).
6
+ */
7
+ export interface DocRecord {
8
+ id: string;
9
+ title: string;
10
+ summary: string;
11
+ audience: DocEntry['audience'];
12
+ content: string;
13
+ }
14
+ /**
15
+ * The pure docs surface (plan 007, workshop 001). Operates ONLY over the
16
+ * in-memory corpus — no `node:fs`, no `process.cwd()`, no CLI coupling (P2,
17
+ * Finding 03) — so a future `mcp/tools/docs_*` imports these functions unchanged
18
+ * (Finding 08). The act (`acts/docs.ts`) owns all I/O.
19
+ *
20
+ * Both functions default to the bundled `DOCS`; the optional `docs` parameter is
21
+ * the test seam (and keeps the functions trivially reusable).
22
+ */
23
+ /** List every doc as an index entry — title/summary only, never the body. */
24
+ export declare function listDocs(docs?: readonly DocRecord[]): DocsListResult;
25
+ /** Resolve one doc by id to its full content, or an explicit not-found marker. */
26
+ export declare function getDoc(id: string, docs?: readonly DocRecord[]): DocLookup;
@@ -0,0 +1,25 @@
1
+ import { DOCS } from './docs-content.js';
2
+ /**
3
+ * The pure docs surface (plan 007, workshop 001). Operates ONLY over the
4
+ * in-memory corpus — no `node:fs`, no `process.cwd()`, no CLI coupling (P2,
5
+ * Finding 03) — so a future `mcp/tools/docs_*` imports these functions unchanged
6
+ * (Finding 08). The act (`acts/docs.ts`) owns all I/O.
7
+ *
8
+ * Both functions default to the bundled `DOCS`; the optional `docs` parameter is
9
+ * the test seam (and keeps the functions trivially reusable).
10
+ */
11
+ /** List every doc as an index entry — title/summary only, never the body. */
12
+ export function listDocs(docs = DOCS) {
13
+ return {
14
+ docs: docs.map(({ id, title, summary, audience }) => ({ id, title, summary, audience })),
15
+ };
16
+ }
17
+ /** Resolve one doc by id to its full content, or an explicit not-found marker. */
18
+ export function getDoc(id, docs = DOCS) {
19
+ const found = docs.find((doc) => doc.id === id);
20
+ if (!found) {
21
+ return { notFound: true, id };
22
+ }
23
+ return { id: found.id, title: found.title, content: found.content, format: 'markdown' };
24
+ }
25
+ //# sourceMappingURL=docs-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-service.js","sourceRoot":"","sources":["../../../src/services/docs/docs-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAezC;;;;;;;;GAQG;AAEH,6EAA6E;AAC7E,MAAM,UAAU,QAAQ,CAAC,OAA6B,IAAI;IACxD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;KACzF,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,MAAM,CAAC,EAAU,EAAE,OAA6B,IAAI;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { Clock } from '../../adapters/clock/clock-port.js';
2
+ import type { EnvPort } from '../../adapters/env/env-port.js';
3
+ import type { FsPort } from '../../adapters/fs/fs-port.js';
4
+ import type { GitPort } from '../../adapters/git/git-port.js';
5
+ import type { ProcessPort } from '../../adapters/process/process-port.js';
6
+ import { type Envelope } from '../../output/envelope.js';
7
+ import type { ExtensionRecord } from '../extensions/contract.js';
8
+ import type { VerbRegistry } from '../extensions/registry.js';
9
+ import type { RecordRegistry, RecordTypeEntry } from '../record/registry.js';
10
+ /** Adapters the doctor service depends on (injected — never constructed here). */
11
+ export interface DoctorDeps {
12
+ fs: FsPort;
13
+ proc: ProcessPort;
14
+ git: GitPort;
15
+ env: EnvPort;
16
+ clock: Clock;
17
+ }
18
+ /** One layer of the doctor report. */
19
+ export interface LayerReport {
20
+ name: string;
21
+ /** True when the layer is configured/ready. */
22
+ ok: boolean;
23
+ detail: string;
24
+ /** Present when the layer is not ok — what to do about it. */
25
+ next_action?: string;
26
+ }
27
+ /**
28
+ * A package-convention violation for one loaded extension (plan 014 D2). The
29
+ * record itself STAYS `loaded` (the verb runs — AC-9); the complaint lives here
30
+ * so the contract's `ExtensionRecord` shape is untouched (D5).
31
+ */
32
+ export interface ConventionComplaint {
33
+ /** The extension folder in violation. */
34
+ folder: string;
35
+ /** E144-prefixed complaint line. */
36
+ detail: string;
37
+ /** What to do about it (P7). */
38
+ next_action: string;
39
+ }
40
+ /** The full doctor report (the envelope `data`). */
41
+ export interface DoctorReport {
42
+ layers: LayerReport[];
43
+ branch: string | null;
44
+ /** Whether HARNESS_JSON forces JSON output (read via the env port). */
45
+ json_env: boolean;
46
+ /** Per-extension provenance enumerated WITHOUT invoking any handler (P7). */
47
+ extensions: ExtensionRecord[];
48
+ /** Package-convention complaints (missing `instructions.md`) — the doctor wail (plan 014 D2). */
49
+ conventions: ConventionComplaint[];
50
+ /** The merged record types (core ∪ extension) enumerated declaratively. */
51
+ recordTypes: RecordTypeEntry[];
52
+ }
53
+ /**
54
+ * Gather the doctor report via the injected adapters + the assembled verb
55
+ * registry. Pure of `process.exit` and direct Node I/O — all side effects go
56
+ * through the ports, so the whole thing is unit-testable with fakes. The optional
57
+ * `recordRegistry` adds the record-types enumeration (core ∪ extension).
58
+ */
59
+ export declare function buildDoctorReport(deps: DoctorDeps, registry: VerbRegistry, recordRegistry?: RecordRegistry): DoctorReport;
60
+ /**
61
+ * Turn a report into an envelope: `ok` when every layer is ready, else
62
+ * `degraded` (still exit 0 — reporting succeeded) with a required next_action.
63
+ * Doctor produces no durable evidence, so it records `{none: true}`.
64
+ */
65
+ export declare function doctorEnvelope(report: DoctorReport, clock: Clock): Envelope;
66
+ /** Convenience: gather + envelope in one call. */
67
+ export declare function runDoctor(deps: DoctorDeps, registry: VerbRegistry, recordRegistry?: RecordRegistry): Envelope;
68
+ /** Render the report as human diagnostics text (each layer, the extensions, the branch). */
69
+ export declare function renderDoctorText(report: DoctorReport): string;
@@ -0,0 +1,237 @@
1
+ import { formatDegraded, formatOk } from '../../output/envelope.js';
2
+ import { ErrorCodes } from '../../output/error-codes.js';
3
+ import { posixDirname, posixJoin, posixRelative, toPosix } from '../shared/posix-path.js';
4
+ import { HARNESS_DIR, TEMP_DIR } from '../shared/temp.js';
5
+ const REQUIRED_TOOLS = ['node', 'just', 'biome'];
6
+ /**
7
+ * Relative to cwd. Two modes (FX001 / plan-013 FIND-2):
8
+ * - Dev (this repo, the harness's home): `CLI_DEV_MARKER` present → check the build output.
9
+ * - Consumer (installed clone): marker absent → the dev build check does not apply; the
10
+ * layer reports ok with a `consumer` detail instead of falsely degrading the envelope.
11
+ * The marker is a FILE (not the `harness/cli/` dir) so both NodeFs and FakeFs resolve it
12
+ * with plain exists(); there is no `harness/cli/package.json` — the CLI builds from the
13
+ * root package, so its tsconfig is the stable dev-tree marker.
14
+ */
15
+ const CLI_DEV_MARKER = 'harness/cli/tsconfig.json';
16
+ const CLI_BUILD_PATH = 'harness/cli/dist/index.js';
17
+ function checkToolchain(proc) {
18
+ const missing = REQUIRED_TOOLS.filter((tool) => proc.which(tool) === null);
19
+ const ok = missing.length === 0;
20
+ return {
21
+ name: 'toolchain',
22
+ ok,
23
+ detail: ok
24
+ ? `all required tools present (${REQUIRED_TOOLS.join(', ')})`
25
+ : `missing tools: ${missing.join(', ')}`,
26
+ ...(ok ? {} : { next_action: `Install the missing tools: ${missing.join(', ')}.` }),
27
+ };
28
+ }
29
+ function checkCliBuild(fs) {
30
+ if (!fs.exists(CLI_DEV_MARKER)) {
31
+ return {
32
+ name: 'cli-build',
33
+ ok: true,
34
+ detail: `consumer install — dev build check n/a (no ${CLI_DEV_MARKER})`,
35
+ };
36
+ }
37
+ const built = fs.exists(CLI_BUILD_PATH);
38
+ return {
39
+ name: 'cli-build',
40
+ ok: built,
41
+ detail: built ? `${CLI_BUILD_PATH} present` : `${CLI_BUILD_PATH} not built`,
42
+ ...(built ? {} : { next_action: 'Run `npm run build` to compile the CLI.' }),
43
+ };
44
+ }
45
+ /**
46
+ * Probe each LOADED extension folder for its convention-required
47
+ * `instructions.md` (plan 014 D2 — extensions are little packages; doctor wails
48
+ * about missing convention files but the verb keeps running, AC-9). The
49
+ * `next_action` path is repo-relative (companion F001 — matches the E143
50
+ * guidance and the public docs); `folder` in the report stays absolute (data).
51
+ */
52
+ function checkConventions(fs, proc, registry) {
53
+ const complaints = [];
54
+ for (const record of registry.records) {
55
+ if (record.status !== 'loaded') {
56
+ continue;
57
+ }
58
+ // entryPath is POSIX from discovery (the single POSIX origin, plan 017);
59
+ // these stay in POSIX space so `folder` matches it shape-for-shape.
60
+ const folder = posixDirname(record.entryPath);
61
+ if (!fs.exists(posixJoin(folder, 'instructions.md'))) {
62
+ const relFolder = posixRelative(toPosix(proc.cwd()), folder) || folder;
63
+ complaints.push({
64
+ folder,
65
+ detail: `${ErrorCodes.EXTENSION_INSTRUCTIONS_MISSING}: missing instructions.md (the agent briefing for this extension's verbs)`,
66
+ next_action: `author ${posixJoin(relFolder, 'instructions.md')} — see \`harness instructions\` for the pattern`,
67
+ });
68
+ }
69
+ }
70
+ // Temp-hygiene probe (plan 015 D5, AC-6): the transient storage class is only
71
+ // safe while its nested self-.gitignore exists. Complaint ONLY when the temp
72
+ // dir exists unprotected; no temp dir yet → silent; no `.harness/` → silent.
73
+ const tempDir = posixJoin(toPosix(proc.cwd()), HARNESS_DIR, TEMP_DIR);
74
+ if (fs.exists(tempDir) && !fs.exists(posixJoin(tempDir, '.gitignore'))) {
75
+ complaints.push({
76
+ folder: tempDir,
77
+ detail: `transient scratch ${HARNESS_DIR}/${TEMP_DIR}/ exists without its nested .gitignore — session buffers risk being committed`,
78
+ next_action: `create ${HARNESS_DIR}/${TEMP_DIR}/.gitignore (a \`harness observe\` capture or \`harness record <type>\` call restores it)`,
79
+ });
80
+ }
81
+ return complaints;
82
+ }
83
+ /**
84
+ * Enumerate the discovered extensions from the assembled registry (P7). A purely
85
+ * declarative pass — `doctor` NEVER invokes a verb handler; it only reports what
86
+ * the loader already recorded (loaded / failed / conflict). A failed or
87
+ * conflicting extension — or a package-convention violation (plan 014 D2) —
88
+ * makes the layer not-ok (degraded), but is never fatal.
89
+ */
90
+ function checkExtensions(registry, conventions) {
91
+ const loaded = registry.records.filter((r) => r.status === 'loaded').length;
92
+ const failed = registry.records.filter((r) => r.status === 'failed').length;
93
+ const conflicts = registry.records.filter((r) => r.status === 'conflict').length;
94
+ const instrMissing = conventions.filter((c) => c.detail.includes('instructions.md')).length;
95
+ const tempUnprotected = conventions.length - instrMissing;
96
+ const suffixParts = [];
97
+ if (instrMissing > 0)
98
+ suffixParts.push(`${instrMissing} missing instructions.md`);
99
+ if (tempUnprotected > 0)
100
+ suffixParts.push('transient scratch unprotected');
101
+ const conventionSuffix = suffixParts.length > 0 ? `, ${suffixParts.join(', ')}` : '';
102
+ if (registry.records.length === 0) {
103
+ const ok = conventions.length === 0;
104
+ return {
105
+ name: 'extensions',
106
+ ok,
107
+ detail: `no extensions installed (from ./.harness/extensions)${conventionSuffix}`,
108
+ next_action: ok
109
+ ? 'Add a verb with `harness new <name>` (a package at `./.harness/extensions/<name>/`).'
110
+ : 'Restore the convention files listed below; run `harness doctor` again.',
111
+ };
112
+ }
113
+ const broken = failed > 0 || conflicts > 0;
114
+ const ok = !broken && conventions.length === 0;
115
+ return {
116
+ name: 'extensions',
117
+ ok,
118
+ detail: `${loaded} loaded, ${failed} failed, ${conflicts} conflict(s)${conventionSuffix} (from ./.harness/extensions)`,
119
+ ...(ok
120
+ ? {}
121
+ : {
122
+ next_action: broken
123
+ ? 'Fix or remove the failed/conflicting extensions listed below; run `harness doctor` again.'
124
+ : instrMissing > 0
125
+ ? 'Author the missing instructions.md briefings listed below — see `harness instructions`.'
126
+ : 'Restore the convention files listed below; run `harness doctor` again.',
127
+ }),
128
+ };
129
+ }
130
+ /**
131
+ * The core agent briefing ships baked into the CLI, so this row is always
132
+ * present and always ok (plan 014 D2) — it exists to make the briefing channel
133
+ * discoverable from doctor output.
134
+ */
135
+ function checkCoreInstructions() {
136
+ return {
137
+ name: 'instructions',
138
+ ok: true,
139
+ detail: 'core agent briefing baked into the CLI — run `harness instructions`',
140
+ };
141
+ }
142
+ /**
143
+ * Enumerate the merged record types (core ∪ extension) declaratively — `doctor`
144
+ * NEVER invokes anything; it just reports what the registry resolved. Informational
145
+ * (always ok): an extension type that shadowed a core/earlier type already surfaces
146
+ * as a `conflict` in the extensions layer above (recordShadows), so this line need
147
+ * not re-flag it.
148
+ */
149
+ function checkRecordTypes(recordTypes) {
150
+ const core = recordTypes.filter((t) => t.source === 'core').length;
151
+ const ext = recordTypes.filter((t) => t.source === 'extension').length;
152
+ return {
153
+ name: 'record-types',
154
+ ok: true,
155
+ detail: `${recordTypes.length} available (${core} core, ${ext} extension)`,
156
+ };
157
+ }
158
+ /**
159
+ * Gather the doctor report via the injected adapters + the assembled verb
160
+ * registry. Pure of `process.exit` and direct Node I/O — all side effects go
161
+ * through the ports, so the whole thing is unit-testable with fakes. The optional
162
+ * `recordRegistry` adds the record-types enumeration (core ∪ extension).
163
+ */
164
+ export function buildDoctorReport(deps, registry, recordRegistry) {
165
+ const recordTypes = recordRegistry?.types ?? [];
166
+ const conventions = checkConventions(deps.fs, deps.proc, registry);
167
+ const layers = [
168
+ checkToolchain(deps.proc),
169
+ checkCliBuild(deps.fs),
170
+ checkExtensions(registry, conventions),
171
+ checkCoreInstructions(),
172
+ checkRecordTypes(recordTypes),
173
+ ];
174
+ const branch = deps.git.isRepo() ? deps.git.currentBranch() : null;
175
+ const json_env = deps.env.get('HARNESS_JSON') === '1';
176
+ return { layers, branch, json_env, extensions: registry.records, conventions, recordTypes };
177
+ }
178
+ /**
179
+ * Turn a report into an envelope: `ok` when every layer is ready, else
180
+ * `degraded` (still exit 0 — reporting succeeded) with a required next_action.
181
+ * Doctor produces no durable evidence, so it records `{none: true}`.
182
+ */
183
+ export function doctorEnvelope(report, clock) {
184
+ const anyFail = report.layers.some((layer) => !layer.ok);
185
+ const evidence = [{ label: 'doctor report', none: true }];
186
+ return anyFail
187
+ ? formatDegraded('doctor', report, 'Resolve the unconfigured/failing layers below; run `harness help` for the verb map.', clock, { evidence })
188
+ : formatOk('doctor', report, clock, { evidence });
189
+ }
190
+ /** Convenience: gather + envelope in one call. */
191
+ export function runDoctor(deps, registry, recordRegistry) {
192
+ return doctorEnvelope(buildDoctorReport(deps, registry, recordRegistry), deps.clock);
193
+ }
194
+ /** Render the report as human diagnostics text (each layer, the extensions, the branch). */
195
+ export function renderDoctorText(report) {
196
+ const lines = ['harness doctor — readiness report', ''];
197
+ for (const layer of report.layers) {
198
+ lines.push(`${layer.ok ? '✓' : '✗'} ${layer.name}: ${layer.detail}`);
199
+ if (layer.name === 'extensions') {
200
+ for (const ext of report.extensions) {
201
+ const mark = ext.status === 'loaded' ? '•' : '✗';
202
+ const verbNames = ext.verbs.map((v) => v.name);
203
+ const recordNames = (ext.recordTypes ?? []).map((t) => `${t.type} (record)`);
204
+ const names = [...verbNames, ...recordNames].join(', ') || '(none)';
205
+ const suffix = ext.error ? ` — ${ext.error}` : '';
206
+ lines.push(` ${mark} ${names} [${ext.status}] ${ext.entryPath}${suffix}`);
207
+ // Both comparison sides in POSIX space (plan 017 — no partial-normalization mismatch).
208
+ const complaint = report.conventions.find((c) => posixDirname(ext.entryPath) === c.folder);
209
+ if (complaint && ext.status === 'loaded') {
210
+ lines.push(` ✗ ${complaint.detail}`);
211
+ lines.push(` → ${complaint.next_action}`);
212
+ }
213
+ }
214
+ // Complaints not tied to an extension folder (e.g. the temp-hygiene probe,
215
+ // plan 015 D5) — the prescription must still be visible (P7).
216
+ for (const complaint of report.conventions) {
217
+ if (!report.extensions.some((ext) => posixDirname(ext.entryPath) === complaint.folder)) {
218
+ lines.push(` ✗ ${complaint.detail}`);
219
+ lines.push(` → ${complaint.next_action}`);
220
+ }
221
+ }
222
+ }
223
+ if (layer.name === 'record-types') {
224
+ for (const rt of report.recordTypes) {
225
+ const provenance = rt.source === 'extension' ? `[extension] ${rt.entryPath ?? ''}`.trim() : '[core]';
226
+ lines.push(` • ${rt.type} ${provenance}`);
227
+ }
228
+ }
229
+ if (layer.next_action) {
230
+ lines.push(` → ${layer.next_action}`);
231
+ }
232
+ }
233
+ lines.push('', `branch: ${report.branch ?? '(detached or not a repo)'}`);
234
+ lines.push(`output: ${report.json_env ? 'JSON forced via HARNESS_JSON' : 'auto (TTY/flag)'}`);
235
+ return `${lines.join('\n')}\n`;
236
+ }
237
+ //# sourceMappingURL=doctor-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-service.js","sourceRoot":"","sources":["../../../src/services/doctor/doctor-service.ts"],"names":[],"mappings":"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"}