@ai-substrate/engineering-harness 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -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,56 @@
1
+ import { exitWithEnvelope } from '../output/exit.js';
2
+ import { createOutputPort } from '../output/output-port.js';
3
+ import { buildVerbContext, runVerb } from '../services/extensions/verb-context.js';
4
+ /**
5
+ * Register ONE extension verb as a top-level `harness <verb>` subcommand. Thin:
6
+ * it maps the declarative `HarnessVerb` onto commander (name, description,
7
+ * options, args), and its action parses opts/args → builds the `VerbContext`
8
+ * (cwd from the process port) → `await runVerb` → exits via the kernel (so the
9
+ * status→exit mapping + `process.exit` confinement are unchanged). No business
10
+ * logic lives here. Returns the created Command (usage is inspectable).
11
+ */
12
+ export function registerVerbAct(program, verb, deps, io) {
13
+ const command = program.command(verb.name);
14
+ command.description(verb.description ?? verb.summary);
15
+ command.summary(verb.summary);
16
+ // Group every contributed verb under its own `--help` heading so the dynamic,
17
+ // extension-owned surface reads separately from the fixed core commands.
18
+ command.helpGroup('Extensions:');
19
+ const args = verb.args ?? [];
20
+ for (const arg of args) {
21
+ command.argument(arg.name, arg.description);
22
+ }
23
+ for (const option of verb.options ?? []) {
24
+ if (option.defaultValue !== undefined) {
25
+ command.option(option.flags, option.description, option.defaultValue);
26
+ }
27
+ else {
28
+ command.option(option.flags, option.description);
29
+ }
30
+ }
31
+ command.action(async (...callArgs) => {
32
+ const cmd = callArgs[callArgs.length - 1];
33
+ const positionals = callArgs.slice(0, args.length);
34
+ const ctx = buildVerbContext(deps, {
35
+ cwd: deps.proc.cwd(),
36
+ args: buildArgMap(args, positionals),
37
+ options: cmd.opts(),
38
+ });
39
+ const envelope = await runVerb(verb, ctx, deps.clock);
40
+ exitWithEnvelope(envelope, createOutputPort(io.mode, io.writers));
41
+ });
42
+ return command;
43
+ }
44
+ /** Map declared args (`<name>`, `[env]`, `<files...>`) to a `{argKey: value}` record. */
45
+ function buildArgMap(args, positionals) {
46
+ const result = {};
47
+ args.forEach((arg, index) => {
48
+ result[argKey(arg.name)] = positionals[index];
49
+ });
50
+ return result;
51
+ }
52
+ /** Strip commander placeholder punctuation: `<files...>` → `files`. */
53
+ function argKey(name) {
54
+ return name.replace(/[<>[\].]/g, '').trim();
55
+ }
56
+ //# sourceMappingURL=verb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verb.js","sourceRoot":"","sources":["../../src/acts/verb.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAc,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAYnF;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,IAAiB,EACjB,IAAiB,EACjB,EAAS;IAET,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9B,8EAA8E;IAC9E,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACxC,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,QAAmB,EAAE,EAAE;QAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAY,CAAC;QACrD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAA2B,CAAC;QAC7E,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,EAAE;YACjC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACpB,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;YACpC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;SACpB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yFAAyF;AACzF,SAAS,WAAW,CAClB,IAAwB,EACxB,WAAmC;IAEnC,MAAM,MAAM,GAAuC,EAAE,CAAC;IACtD,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QAC1B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,uEAAuE;AACvE,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Clock port — the kernel's only side-effect dependency.
3
+ *
4
+ * Injected (never `new Date()` inside services/kernel) so envelope timestamps
5
+ * are deterministic in unit tests. See workshop 001 + plan Finding 04.
6
+ */
7
+ export interface Clock {
8
+ /** Current instant as an ISO-8601 string, e.g. "2026-06-08T07:20:00.000Z". */
9
+ nowIso(): string;
10
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=clock-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock-port.js","sourceRoot":"","sources":["../../../src/adapters/clock/clock-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import type { Clock } from './clock-port.js';
2
+ /**
3
+ * Deterministic Clock for tests. Returns a fixed instant until advanced/set,
4
+ * and records its call history (fakes over mocks — assert on `calls`).
5
+ */
6
+ export declare class FakeClock implements Clock {
7
+ private current;
8
+ readonly calls: string[];
9
+ constructor(start?: string | number | Date);
10
+ nowIso(): string;
11
+ /** Advance the fake clock forward by `ms` milliseconds. */
12
+ advance(ms: number): void;
13
+ /** Jump the fake clock to an absolute instant. */
14
+ set(instant: string | number | Date): void;
15
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Deterministic Clock for tests. Returns a fixed instant until advanced/set,
3
+ * and records its call history (fakes over mocks — assert on `calls`).
4
+ */
5
+ export class FakeClock {
6
+ current;
7
+ calls = [];
8
+ constructor(start = '2026-06-08T07:20:00.000Z') {
9
+ this.current = new Date(start).getTime();
10
+ }
11
+ nowIso() {
12
+ const iso = new Date(this.current).toISOString();
13
+ this.calls.push(iso);
14
+ return iso;
15
+ }
16
+ /** Advance the fake clock forward by `ms` milliseconds. */
17
+ advance(ms) {
18
+ this.current += ms;
19
+ }
20
+ /** Jump the fake clock to an absolute instant. */
21
+ set(instant) {
22
+ this.current = new Date(instant).getTime();
23
+ }
24
+ }
25
+ //# sourceMappingURL=fake-clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-clock.js","sourceRoot":"","sources":["../../../src/adapters/clock/fake-clock.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,OAAO,CAAS;IACf,KAAK,GAAa,EAAE,CAAC;IAE9B,YAAY,QAAgC,0BAA0B;QACpE,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,2DAA2D;IAC3D,OAAO,CAAC,EAAU;QAChB,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IACrB,CAAC;IAED,kDAAkD;IAClD,GAAG,CAAC,OAA+B;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ import type { Clock } from './clock-port.js';
2
+ /** Real clock — the only place wall-clock time is read. */
3
+ export declare class SystemClock implements Clock {
4
+ nowIso(): string;
5
+ }
@@ -0,0 +1,7 @@
1
+ /** Real clock — the only place wall-clock time is read. */
2
+ export class SystemClock {
3
+ nowIso() {
4
+ return new Date().toISOString();
5
+ }
6
+ }
7
+ //# sourceMappingURL=system-clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-clock.js","sourceRoot":"","sources":["../../../src/adapters/clock/system-clock.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,OAAO,WAAW;IACtB,MAAM;QACJ,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Environment port — reads process environment variables behind an interface.
3
+ *
4
+ * Lets services read/report env (e.g. `HARNESS_JSON`) without touching
5
+ * `process.env` directly, so they stay unit-testable with `FakeEnv`.
6
+ */
7
+ export interface EnvPort {
8
+ /** Value of an env var, or undefined if unset. */
9
+ get(name: string): string | undefined;
10
+ /**
11
+ * Absolute path to the user's home directory (`$HOME` / `%USERPROFILE%`), or
12
+ * undefined if it cannot be resolved. Lets services place user-global state
13
+ * (e.g. the update-check cache under `~/.harness/`) without reading
14
+ * `os.homedir()` directly — keeping them free of `node:*` (P2).
15
+ */
16
+ home(): string | undefined;
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=env-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-port.js","sourceRoot":"","sources":["../../../src/adapters/env/env-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import type { EnvPort } from './env-port.js';
2
+ /**
3
+ * Deterministic environment for tests. Seeded with a `{name: value}` map;
4
+ * records every requested name on `gets` (fakes over mocks).
5
+ */
6
+ export declare class FakeEnv implements EnvPort {
7
+ private readonly vars;
8
+ private readonly homeDir?;
9
+ readonly gets: string[];
10
+ /** How many times home() was called (fakes over mocks — assert on history). */
11
+ homeCalls: number;
12
+ constructor(vars?: Record<string, string>, homeDir?: string | undefined);
13
+ get(name: string): string | undefined;
14
+ home(): string | undefined;
15
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Deterministic environment for tests. Seeded with a `{name: value}` map;
3
+ * records every requested name on `gets` (fakes over mocks).
4
+ */
5
+ export class FakeEnv {
6
+ vars;
7
+ homeDir;
8
+ gets = [];
9
+ /** How many times home() was called (fakes over mocks — assert on history). */
10
+ homeCalls = 0;
11
+ constructor(vars = {}, homeDir) {
12
+ this.vars = vars;
13
+ this.homeDir = homeDir;
14
+ }
15
+ get(name) {
16
+ this.gets.push(name);
17
+ return this.vars[name];
18
+ }
19
+ home() {
20
+ this.homeCalls++;
21
+ return this.homeDir;
22
+ }
23
+ }
24
+ //# sourceMappingURL=fake-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-env.js","sourceRoot":"","sources":["../../../src/adapters/env/fake-env.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,OAAO;IAMC;IACA;IANV,IAAI,GAAa,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,SAAS,GAAG,CAAC,CAAC;IAEd,YACmB,OAA+B,EAAE,EACjC,OAAgB;QADhB,SAAI,GAAJ,IAAI,CAA6B;QACjC,YAAO,GAAP,OAAO,CAAS;IAChC,CAAC;IAEJ,GAAG,CAAC,IAAY;QACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ import type { EnvPort } from './env-port.js';
2
+ /** Real environment — wraps `process.env`. */
3
+ export declare class NodeEnv implements EnvPort {
4
+ get(name: string): string | undefined;
5
+ home(): string | undefined;
6
+ }
@@ -0,0 +1,16 @@
1
+ import { homedir } from 'node:os';
2
+ /** Real environment — wraps `process.env`. */
3
+ export class NodeEnv {
4
+ get(name) {
5
+ return process.env[name];
6
+ }
7
+ home() {
8
+ // Prefer the explicit env vars ($HOME on POSIX, %USERPROFILE% on Windows),
9
+ // then fall back to os.homedir(). `||` (not `??`) so an EMPTY string falls
10
+ // through to the next source rather than being treated as a resolved value
11
+ // (companion F001). Empty after all sources ⇒ unresolved ⇒ undefined.
12
+ const resolved = process.env.HOME || process.env.USERPROFILE || homedir();
13
+ return resolved ? resolved : undefined;
14
+ }
15
+ }
16
+ //# sourceMappingURL=node-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-env.js","sourceRoot":"","sources":["../../../src/adapters/env/node-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,8CAA8C;AAC9C,MAAM,OAAO,OAAO;IAClB,GAAG,CAAC,IAAY;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI;QACF,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,EAAE,CAAC;QAC1E,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Exec port — runs a REAL repo command (the P8 "wrap, don't rebuild" capability).
3
+ *
4
+ * Verbs invoke this through `ctx.exec` to wrap existing project commands (build,
5
+ * lint, test…). Injected so verb/loader logic stays unit-testable with `FakeExec`
6
+ * and never spawns a child directly — `NodeExec` is the only place a child is
7
+ * spawned for the verb path (KF-06 adapter discipline).
8
+ */
9
+ export interface ExecResult {
10
+ /** Child process exit code (127 when the binary could not be spawned). */
11
+ code: number;
12
+ stdout: string;
13
+ stderr: string;
14
+ /** Convenience: `code === 0`. */
15
+ ok: boolean;
16
+ }
17
+ export interface ExecPort {
18
+ /** Spawn `command args` in `opts.cwd` (no shell), capturing code/stdout/stderr. */
19
+ run(command: string, args: string[], opts: {
20
+ cwd: string;
21
+ }): Promise<ExecResult>;
22
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=exec-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-port.js","sourceRoot":"","sources":["../../../src/adapters/exec/exec-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import type { ExecPort, ExecResult } from './exec-port.js';
2
+ /** A scripted result — `ok` is derived from `code`, so callers script only the facts. */
3
+ export interface ExecScript {
4
+ code: number;
5
+ stdout?: string;
6
+ stderr?: string;
7
+ }
8
+ /**
9
+ * Deterministic exec for tests. Seeded with `{ 'cmd a b': {code,stdout?,stderr?} }`
10
+ * keyed by full command line; records each `{command,args,cwd}` on `calls` (fakes
11
+ * over mocks). An unscripted command resolves to a benign success (absent ≠ error,
12
+ * mirroring FakeFs/FakeProcess); `ok` is always computed from `code`.
13
+ */
14
+ export declare class FakeExec implements ExecPort {
15
+ private readonly scripts;
16
+ readonly calls: {
17
+ command: string;
18
+ args: string[];
19
+ cwd: string;
20
+ }[];
21
+ constructor(scripts?: Record<string, ExecScript>);
22
+ run(command: string, args: string[], opts: {
23
+ cwd: string;
24
+ }): Promise<ExecResult>;
25
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Deterministic exec for tests. Seeded with `{ 'cmd a b': {code,stdout?,stderr?} }`
3
+ * keyed by full command line; records each `{command,args,cwd}` on `calls` (fakes
4
+ * over mocks). An unscripted command resolves to a benign success (absent ≠ error,
5
+ * mirroring FakeFs/FakeProcess); `ok` is always computed from `code`.
6
+ */
7
+ export class FakeExec {
8
+ scripts;
9
+ calls = [];
10
+ constructor(scripts = {}) {
11
+ this.scripts = scripts;
12
+ }
13
+ run(command, args, opts) {
14
+ this.calls.push({ command, args, cwd: opts.cwd });
15
+ const key = [command, ...args].join(' ');
16
+ const script = this.scripts[key] ?? this.scripts[command] ?? { code: 0 };
17
+ return Promise.resolve({
18
+ code: script.code,
19
+ stdout: script.stdout ?? '',
20
+ stderr: script.stderr ?? '',
21
+ ok: script.code === 0,
22
+ });
23
+ }
24
+ }
25
+ //# sourceMappingURL=fake-exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-exec.js","sourceRoot":"","sources":["../../../src/adapters/exec/fake-exec.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,MAAM,OAAO,QAAQ;IAGU;IAFpB,KAAK,GAAuD,EAAE,CAAC;IAExE,YAA6B,UAAsC,EAAE;QAAxC,YAAO,GAAP,OAAO,CAAiC;IAAG,CAAC;IAEzE,GAAG,CAAC,OAAe,EAAE,IAAc,EAAE,IAAqB;QACxD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,EAAE,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC;SACtB,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { ExecPort, ExecResult } from './exec-port.js';
2
+ /**
3
+ * Real process execution — the only place a child is spawned for the verb path.
4
+ * `shell: false` + an args array means no shell-injection surface (KF-06). Never
5
+ * rejects: a spawn error or non-zero exit resolves to an `ExecResult` so the verb
6
+ * handler can map it to an Envelope rather than throwing through the kernel. This
7
+ * includes a SYNCHRONOUS spawn throw (e.g. a null byte in the command), which is
8
+ * caught and resolved as code 127 rather than rejecting the promise.
9
+ */
10
+ export declare class NodeExec implements ExecPort {
11
+ run(command: string, args: string[], opts: {
12
+ cwd: string;
13
+ }): Promise<ExecResult>;
14
+ }
@@ -0,0 +1,38 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Real process execution — the only place a child is spawned for the verb path.
4
+ * `shell: false` + an args array means no shell-injection surface (KF-06). Never
5
+ * rejects: a spawn error or non-zero exit resolves to an `ExecResult` so the verb
6
+ * handler can map it to an Envelope rather than throwing through the kernel. This
7
+ * includes a SYNCHRONOUS spawn throw (e.g. a null byte in the command), which is
8
+ * caught and resolved as code 127 rather than rejecting the promise.
9
+ */
10
+ export class NodeExec {
11
+ run(command, args, opts) {
12
+ return new Promise((resolve) => {
13
+ let stdout = '';
14
+ let stderr = '';
15
+ try {
16
+ const child = spawn(command, args, { cwd: opts.cwd, shell: false });
17
+ child.stdout?.on('data', (chunk) => {
18
+ stdout += chunk.toString();
19
+ });
20
+ child.stderr?.on('data', (chunk) => {
21
+ stderr += chunk.toString();
22
+ });
23
+ child.on('error', (err) => {
24
+ resolve({ code: 127, stdout, stderr: stderr + String(err.message ?? err), ok: false });
25
+ });
26
+ child.on('close', (code) => {
27
+ const exitCode = code ?? 1;
28
+ resolve({ code: exitCode, stdout, stderr, ok: exitCode === 0 });
29
+ });
30
+ }
31
+ catch (err) {
32
+ const message = err instanceof Error ? err.message : String(err);
33
+ resolve({ code: 127, stdout, stderr: stderr + message, ok: false });
34
+ }
35
+ });
36
+ }
37
+ }
38
+ //# sourceMappingURL=node-exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-exec.js","sourceRoot":"","sources":["../../../src/adapters/exec/node-exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C;;;;;;;GAOG;AACH,MAAM,OAAO,QAAQ;IACnB,GAAG,CAAC,OAAe,EAAE,IAAc,EAAE,IAAqB;QACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBACjC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBACjC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBACxB,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzF,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;oBACzB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC;oBAC3B,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC,CAAC;gBAClE,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import type { FsPort } from './fs-port.js';
2
+ /**
3
+ * Deterministic filesystem for tests. Seeded with a `{path: contents}` map and
4
+ * an optional `{dir: entryNames[]}` map; records every probed path on `reads`,
5
+ * every written path on `writes`, and every `mkdirp` on `mkdirs` (fakes over
6
+ * mocks — assert on history). Writes mutate the in-memory file map so a later
7
+ * `exists`/`readText` sees what was written.
8
+ */
9
+ export declare class FakeFs implements FsPort {
10
+ private readonly files;
11
+ private readonly dirs;
12
+ readonly reads: string[];
13
+ readonly writes: string[];
14
+ readonly mkdirs: string[];
15
+ private readonly madeDirs;
16
+ constructor(files?: Record<string, string>, dirs?: Record<string, string[]>);
17
+ exists(path: string): boolean;
18
+ readText(path: string): string | null;
19
+ readdir(path: string): string[];
20
+ mkdirp(path: string): void;
21
+ writeText(path: string, contents: string): void;
22
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Deterministic filesystem for tests. Seeded with a `{path: contents}` map and
3
+ * an optional `{dir: entryNames[]}` map; records every probed path on `reads`,
4
+ * every written path on `writes`, and every `mkdirp` on `mkdirs` (fakes over
5
+ * mocks — assert on history). Writes mutate the in-memory file map so a later
6
+ * `exists`/`readText` sees what was written.
7
+ */
8
+ export class FakeFs {
9
+ files;
10
+ dirs;
11
+ reads = [];
12
+ writes = [];
13
+ mkdirs = [];
14
+ madeDirs = new Set();
15
+ constructor(files = {}, dirs = {}) {
16
+ this.files = files;
17
+ this.dirs = dirs;
18
+ }
19
+ exists(path) {
20
+ this.reads.push(path);
21
+ return path in this.files || this.madeDirs.has(path);
22
+ }
23
+ readText(path) {
24
+ this.reads.push(path);
25
+ return this.files[path] ?? null;
26
+ }
27
+ readdir(path) {
28
+ this.reads.push(path);
29
+ // Seeded names first, then immediate child dirs created via mkdirp — so a
30
+ // dir made DURING the test is visible to a later listing, as NodeFs would
31
+ // be (plan 015: capture mkdirps a bucket; a later sweep readdirs its parent).
32
+ // Probes tolerate Windows-shaped paths; registered state is canonical
33
+ // POSIX (plan 017 — Windows-shaped-input sensors run on every OS).
34
+ const posixPath = path.replace(/\\/g, '/');
35
+ const names = [...(this.dirs[path] ?? this.dirs[posixPath] ?? [])];
36
+ const prefix = posixPath.endsWith('/') ? posixPath : `${posixPath}/`;
37
+ for (const dir of this.madeDirs) {
38
+ if (dir.startsWith(prefix)) {
39
+ const name = dir.slice(prefix.length).split(/[\\/]/)[0];
40
+ if (name && !names.includes(name))
41
+ names.push(name);
42
+ }
43
+ }
44
+ return names;
45
+ }
46
+ mkdirp(path) {
47
+ this.mkdirs.push(path);
48
+ // Register each ancestor segment so exists() models a recursive create
49
+ // (matches NodeFs.mkdirSync({ recursive: true }); F001). Segments split on
50
+ // either separator and are stored in canonical POSIX form (plan 017).
51
+ const parts = path.split(/[\\/]/);
52
+ for (let i = 1; i <= parts.length; i++) {
53
+ const seg = parts.slice(0, i).join('/');
54
+ if (seg)
55
+ this.madeDirs.add(seg);
56
+ }
57
+ }
58
+ writeText(path, contents) {
59
+ this.writes.push(path);
60
+ this.files[path] = contents;
61
+ }
62
+ }
63
+ //# sourceMappingURL=fake-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-fs.js","sourceRoot":"","sources":["../../../src/adapters/fs/fake-fs.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,MAAM;IAOE;IACA;IAPV,KAAK,GAAa,EAAE,CAAC;IACrB,MAAM,GAAa,EAAE,CAAC;IACtB,MAAM,GAAa,EAAE,CAAC;IACd,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAE9C,YACmB,QAAgC,EAAE,EAClC,OAAiC,EAAE;QADnC,UAAK,GAAL,KAAK,CAA6B;QAClC,SAAI,GAAJ,IAAI,CAA+B;IACnD,CAAC;IAEJ,MAAM,CAAC,IAAY;QACjB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAClC,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,0EAA0E;QAC1E,0EAA0E;QAC1E,8EAA8E;QAC9E,sEAAsE;QACtE,mEAAmE;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC;QACrE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxD,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,uEAAuE;QACvE,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAgB;QACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;IAC9B,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Filesystem port — the side effect `doctor`/config reads sit behind, plus the
3
+ * writes the scaffolder (`harness new`, plan 006) needs.
4
+ *
5
+ * Reads (`exists`/`readText`/`readdir`) and writes (`mkdirp`/`writeText`) are
6
+ * injected so services stay unit-testable with `FakeFs` and never import
7
+ * `node:fs`.
8
+ */
9
+ export interface FsPort {
10
+ /** True if a path exists on disk. */
11
+ exists(path: string): boolean;
12
+ /** File contents as UTF-8, or null if missing/unreadable (never throws). */
13
+ readText(path: string): string | null;
14
+ /** Entry names directly inside a directory, or `[]` if missing/unreadable (never throws). */
15
+ readdir(path: string): string[];
16
+ /** Recursively create a directory (no-op if it already exists). For the scaffolder. */
17
+ mkdirp(path: string): void;
18
+ /** Write UTF-8 text to a path, overwriting. Caller ensures the parent dir exists (mkdirp). */
19
+ writeText(path: string, contents: string): void;
20
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fs-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-port.js","sourceRoot":"","sources":["../../../src/adapters/fs/fs-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import type { FsPort } from './fs-port.js';
2
+ /** Real filesystem — the only place `node:fs` is touched. */
3
+ export declare class NodeFs implements FsPort {
4
+ exists(path: string): boolean;
5
+ readText(path: string): string | null;
6
+ readdir(path: string): string[];
7
+ mkdirp(path: string): void;
8
+ writeText(path: string, contents: string): void;
9
+ }
@@ -0,0 +1,30 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ /** Real filesystem — the only place `node:fs` is touched. */
3
+ export class NodeFs {
4
+ exists(path) {
5
+ return existsSync(path);
6
+ }
7
+ readText(path) {
8
+ try {
9
+ return readFileSync(path, 'utf8');
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ readdir(path) {
16
+ try {
17
+ return readdirSync(path);
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ }
23
+ mkdirp(path) {
24
+ mkdirSync(path, { recursive: true });
25
+ }
26
+ writeText(path, contents) {
27
+ writeFileSync(path, contents, 'utf8');
28
+ }
29
+ }
30
+ //# sourceMappingURL=node-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs.js","sourceRoot":"","sources":["../../../src/adapters/fs/node-fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1F,6DAA6D;AAC7D,MAAM,OAAO,MAAM;IACjB,MAAM,CAAC,IAAY;QACjB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC;YACH,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,QAAgB;QACtC,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ import type { GitPort } from './git-port.js';
2
+ /** Real git access — wraps `git rev-parse` (read-only, informational). */
3
+ export declare class ExecGit implements GitPort {
4
+ isRepo(): boolean;
5
+ currentBranch(): string | null;
6
+ }
@@ -0,0 +1,21 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ /** Real git access — wraps `git rev-parse` (read-only, informational). */
3
+ export class ExecGit {
4
+ isRepo() {
5
+ const result = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
6
+ encoding: 'utf8',
7
+ });
8
+ return result.status === 0 && result.stdout.trim() === 'true';
9
+ }
10
+ currentBranch() {
11
+ const result = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
12
+ encoding: 'utf8',
13
+ });
14
+ if (result.status !== 0) {
15
+ return null;
16
+ }
17
+ const branch = result.stdout.trim();
18
+ return branch && branch !== 'HEAD' ? branch : null;
19
+ }
20
+ }
21
+ //# sourceMappingURL=exec-git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-git.js","sourceRoot":"","sources":["../../../src/adapters/git/exec-git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,0EAA0E;AAC1E,MAAM,OAAO,OAAO;IAClB,MAAM;QACJ,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;YACtE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IAChE,CAAC;IAED,aAAa;QACX,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE;YACrE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpC,OAAO,MAAM,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import type { GitPort } from './git-port.js';
2
+ /**
3
+ * Deterministic git for tests. Seeded with repo/branch state; records each
4
+ * method called on `calls` (fakes over mocks).
5
+ */
6
+ export declare class FakeGit implements GitPort {
7
+ private readonly state;
8
+ readonly calls: string[];
9
+ constructor(state?: {
10
+ isRepo?: boolean;
11
+ branch?: string | null;
12
+ });
13
+ isRepo(): boolean;
14
+ currentBranch(): string | null;
15
+ }