@b00dea/cockpit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE +21 -0
  3. package/README.md +25 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2644 -0
  6. package/dist/autonomy.d.ts +45 -0
  7. package/dist/autonomy.js +173 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +117 -0
  11. package/dist/browser/commands.js +453 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +118 -0
  17. package/dist/browser/runtime.js +186 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +561 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/canonical-json.d.ts +2 -0
  23. package/dist/canonical-json.js +5 -0
  24. package/dist/cli-guard.d.ts +5 -0
  25. package/dist/cli-guard.js +71 -0
  26. package/dist/cli-main.d.ts +176 -0
  27. package/dist/cli-main.js +1584 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.js +16 -0
  30. package/dist/command-table.d.ts +121 -0
  31. package/dist/command-table.js +664 -0
  32. package/dist/config/load.d.ts +183 -0
  33. package/dist/config/load.js +367 -0
  34. package/dist/config/sibling-toml.d.ts +48 -0
  35. package/dist/config/sibling-toml.js +160 -0
  36. package/dist/config/surface-tools-toml.d.ts +9 -0
  37. package/dist/config/surface-tools-toml.js +37 -0
  38. package/dist/config/unconfirmed-toml.d.ts +9 -0
  39. package/dist/config/unconfirmed-toml.js +29 -0
  40. package/dist/config/verified-write.d.ts +43 -0
  41. package/dist/config/verified-write.js +89 -0
  42. package/dist/diagnostics.d.ts +26 -0
  43. package/dist/diagnostics.js +344 -0
  44. package/dist/docs/check.d.ts +16 -0
  45. package/dist/docs/check.js +197 -0
  46. package/dist/docs/decision-record.d.ts +34 -0
  47. package/dist/docs/decision-record.js +135 -0
  48. package/dist/docs/decisions.d.ts +16 -0
  49. package/dist/docs/decisions.js +103 -0
  50. package/dist/docs/markdown.d.ts +36 -0
  51. package/dist/docs/markdown.js +88 -0
  52. package/dist/docs/references.d.ts +32 -0
  53. package/dist/docs/references.js +148 -0
  54. package/dist/emit.d.ts +22 -0
  55. package/dist/emit.js +43 -0
  56. package/dist/entrypoint-kinds.d.ts +3 -0
  57. package/dist/entrypoint-kinds.js +11 -0
  58. package/dist/environment/application/confirm.d.ts +35 -0
  59. package/dist/environment/application/confirm.js +122 -0
  60. package/dist/environment/application/destructive.d.ts +6 -0
  61. package/dist/environment/application/destructive.js +429 -0
  62. package/dist/environment/application/doctor.d.ts +94 -0
  63. package/dist/environment/application/doctor.js +247 -0
  64. package/dist/environment/application/guard.d.ts +49 -0
  65. package/dist/environment/application/guard.js +677 -0
  66. package/dist/environment/application/health.d.ts +143 -0
  67. package/dist/environment/application/health.js +385 -0
  68. package/dist/environment/application/lifecycle-context.d.ts +235 -0
  69. package/dist/environment/application/lifecycle-context.js +154 -0
  70. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  71. package/dist/environment/application/lifecycle-state.js +202 -0
  72. package/dist/environment/application/lifecycle.d.ts +18 -0
  73. package/dist/environment/application/lifecycle.js +737 -0
  74. package/dist/environment/application/logs.d.ts +45 -0
  75. package/dist/environment/application/logs.js +127 -0
  76. package/dist/environment/application/services.d.ts +60 -0
  77. package/dist/environment/application/services.js +89 -0
  78. package/dist/environment/application/setup.d.ts +129 -0
  79. package/dist/environment/application/setup.js +741 -0
  80. package/dist/environment/application/shared.d.ts +11 -0
  81. package/dist/environment/application/shared.js +61 -0
  82. package/dist/environment/application/siblings.d.ts +43 -0
  83. package/dist/environment/application/siblings.js +269 -0
  84. package/dist/environment/claims.d.ts +389 -0
  85. package/dist/environment/claims.js +1701 -0
  86. package/dist/environment/codex-hook-trust.d.ts +11 -0
  87. package/dist/environment/codex-hook-trust.js +185 -0
  88. package/dist/environment/compose-environment.d.ts +1 -0
  89. package/dist/environment/compose-environment.js +6 -0
  90. package/dist/environment/compose-ownership.d.ts +21 -0
  91. package/dist/environment/compose-ownership.js +117 -0
  92. package/dist/environment/compose-topology.d.ts +125 -0
  93. package/dist/environment/compose-topology.js +490 -0
  94. package/dist/environment/contract.d.ts +522 -0
  95. package/dist/environment/contract.js +794 -0
  96. package/dist/environment/declaration.d.ts +14 -0
  97. package/dist/environment/declaration.js +39 -0
  98. package/dist/environment/destructive-composition.d.ts +29 -0
  99. package/dist/environment/destructive-composition.js +333 -0
  100. package/dist/environment/docker-daemon.d.ts +30 -0
  101. package/dist/environment/docker-daemon.js +78 -0
  102. package/dist/environment/docker-endpoint.d.ts +10 -0
  103. package/dist/environment/docker-endpoint.js +19 -0
  104. package/dist/environment/env-schema-observation.d.ts +46 -0
  105. package/dist/environment/env-schema-observation.js +241 -0
  106. package/dist/environment/evidence.d.ts +136 -0
  107. package/dist/environment/evidence.js +437 -0
  108. package/dist/environment/executable.d.ts +38 -0
  109. package/dist/environment/executable.js +190 -0
  110. package/dist/environment/generated-application/authorities.d.ts +5 -0
  111. package/dist/environment/generated-application/authorities.js +61 -0
  112. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  113. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  114. package/dist/environment/generated-application/check.d.ts +13 -0
  115. package/dist/environment/generated-application/check.js +191 -0
  116. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  117. package/dist/environment/generated-application/custom-surface.js +289 -0
  118. package/dist/environment/generated-application/prepare.d.ts +29 -0
  119. package/dist/environment/generated-application/prepare.js +278 -0
  120. package/dist/environment/generated-application/publication.d.ts +33 -0
  121. package/dist/environment/generated-application/publication.js +547 -0
  122. package/dist/environment/generated-application/shared.d.ts +492 -0
  123. package/dist/environment/generated-application/shared.js +354 -0
  124. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  125. package/dist/environment/generated-application/surface-observation.js +69 -0
  126. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  127. package/dist/environment/generated-application/surface-outputs.js +400 -0
  128. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  129. package/dist/environment/generated-application/surfaces.js +704 -0
  130. package/dist/environment/generated-application/sync.d.ts +6 -0
  131. package/dist/environment/generated-application/sync.js +124 -0
  132. package/dist/environment/git-observation.d.ts +202 -0
  133. package/dist/environment/git-observation.js +553 -0
  134. package/dist/environment/health-composition.d.ts +12 -0
  135. package/dist/environment/health-composition.js +645 -0
  136. package/dist/environment/host-observation.d.ts +56 -0
  137. package/dist/environment/host-observation.js +52 -0
  138. package/dist/environment/identity.d.ts +48 -0
  139. package/dist/environment/identity.js +85 -0
  140. package/dist/environment/init-application.d.ts +333 -0
  141. package/dist/environment/init-application.js +726 -0
  142. package/dist/environment/install-policy-observation.d.ts +71 -0
  143. package/dist/environment/install-policy-observation.js +284 -0
  144. package/dist/environment/install-policy.d.ts +48 -0
  145. package/dist/environment/install-policy.js +96 -0
  146. package/dist/environment/instance.d.ts +33 -0
  147. package/dist/environment/instance.js +122 -0
  148. package/dist/environment/instruction-pointer.d.ts +19 -0
  149. package/dist/environment/instruction-pointer.js +144 -0
  150. package/dist/environment/lifecycle-composition.d.ts +31 -0
  151. package/dist/environment/lifecycle-composition.js +742 -0
  152. package/dist/environment/lifecycle-stage.d.ts +120 -0
  153. package/dist/environment/lifecycle-stage.js +408 -0
  154. package/dist/environment/lifecycle.d.ts +75 -0
  155. package/dist/environment/lifecycle.js +354 -0
  156. package/dist/environment/logs.d.ts +132 -0
  157. package/dist/environment/logs.js +519 -0
  158. package/dist/environment/map-limit.d.ts +2 -0
  159. package/dist/environment/map-limit.js +13 -0
  160. package/dist/environment/mcp-observation.d.ts +9 -0
  161. package/dist/environment/mcp-observation.js +46 -0
  162. package/dist/environment/observations.d.ts +46 -0
  163. package/dist/environment/observations.js +346 -0
  164. package/dist/environment/operator-application.d.ts +79 -0
  165. package/dist/environment/operator-application.js +197 -0
  166. package/dist/environment/ports.d.ts +182 -0
  167. package/dist/environment/ports.js +1205 -0
  168. package/dist/environment/posture-observation.d.ts +68 -0
  169. package/dist/environment/posture-observation.js +253 -0
  170. package/dist/environment/presence.d.ts +4 -0
  171. package/dist/environment/presence.js +26 -0
  172. package/dist/environment/read-composition.d.ts +32 -0
  173. package/dist/environment/read-composition.js +397 -0
  174. package/dist/environment/readiness-composition.d.ts +57 -0
  175. package/dist/environment/readiness-composition.js +151 -0
  176. package/dist/environment/reclaim.d.ts +370 -0
  177. package/dist/environment/reclaim.js +1940 -0
  178. package/dist/environment/repository-identity.d.ts +13 -0
  179. package/dist/environment/repository-identity.js +108 -0
  180. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  181. package/dist/environment/runtime-measurement-record.js +111 -0
  182. package/dist/environment/runtime-measurement.d.ts +27 -0
  183. package/dist/environment/runtime-measurement.js +224 -0
  184. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  185. package/dist/environment/runtime-probe-residue.js +148 -0
  186. package/dist/environment/runtime-probe.d.ts +72 -0
  187. package/dist/environment/runtime-probe.js +725 -0
  188. package/dist/environment/runtime.d.ts +551 -0
  189. package/dist/environment/runtime.js +2405 -0
  190. package/dist/environment/seed.d.ts +16 -0
  191. package/dist/environment/seed.js +43 -0
  192. package/dist/environment/selftest-application.d.ts +86 -0
  193. package/dist/environment/selftest-application.js +713 -0
  194. package/dist/environment/sensitive.d.ts +3 -0
  195. package/dist/environment/sensitive.js +48 -0
  196. package/dist/environment/served-checkout-grants.d.ts +53 -0
  197. package/dist/environment/served-checkout-grants.js +182 -0
  198. package/dist/environment/setup-observation.d.ts +62 -0
  199. package/dist/environment/setup-observation.js +1473 -0
  200. package/dist/environment/setup.d.ts +43 -0
  201. package/dist/environment/setup.js +176 -0
  202. package/dist/environment/sibling-checkouts.d.ts +119 -0
  203. package/dist/environment/sibling-checkouts.js +254 -0
  204. package/dist/environment/sibling-instructions.d.ts +33 -0
  205. package/dist/environment/sibling-instructions.js +224 -0
  206. package/dist/environment/sibling-observation.d.ts +27 -0
  207. package/dist/environment/sibling-observation.js +45 -0
  208. package/dist/environment/sibling-pull.d.ts +23 -0
  209. package/dist/environment/sibling-pull.js +95 -0
  210. package/dist/environment/sibling-registration.d.ts +45 -0
  211. package/dist/environment/sibling-registration.js +538 -0
  212. package/dist/environment/stack-inputs.d.ts +18 -0
  213. package/dist/environment/stack-inputs.js +210 -0
  214. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  215. package/dist/environment/stack-inventory-prune.js +129 -0
  216. package/dist/environment/stack-inventory-report.d.ts +44 -0
  217. package/dist/environment/stack-inventory-report.js +78 -0
  218. package/dist/environment/stack-inventory.d.ts +94 -0
  219. package/dist/environment/stack-inventory.js +418 -0
  220. package/dist/environment/state.d.ts +410 -0
  221. package/dist/environment/state.js +2153 -0
  222. package/dist/environment/templates.d.ts +140 -0
  223. package/dist/environment/templates.js +685 -0
  224. package/dist/environment/tool-registration.d.ts +9 -0
  225. package/dist/environment/tool-registration.js +92 -0
  226. package/dist/environment/upgrade-application.d.ts +19 -0
  227. package/dist/environment/upgrade-application.js +175 -0
  228. package/dist/environment-exec/execute.d.ts +105 -0
  229. package/dist/environment-exec/execute.js +1179 -0
  230. package/dist/environment-exec/libexec-program.d.ts +6 -0
  231. package/dist/environment-exec/libexec-program.js +22 -0
  232. package/dist/environment-exec/verified-digest.d.ts +4 -0
  233. package/dist/environment-exec/verified-digest.js +16 -0
  234. package/dist/exact-keys.d.ts +7 -0
  235. package/dist/exact-keys.js +15 -0
  236. package/dist/fix-catalogue.d.ts +32 -0
  237. package/dist/fix-catalogue.js +60 -0
  238. package/dist/freshness.d.ts +29 -0
  239. package/dist/freshness.js +75 -0
  240. package/dist/generated-environment/adopt.d.ts +16 -0
  241. package/dist/generated-environment/adopt.js +271 -0
  242. package/dist/generated-environment/command.d.ts +47 -0
  243. package/dist/generated-environment/command.js +75 -0
  244. package/dist/generated-environment/transaction.d.ts +186 -0
  245. package/dist/generated-environment/transaction.js +1770 -0
  246. package/dist/grading/evaluate.d.ts +71 -0
  247. package/dist/grading/evaluate.js +403 -0
  248. package/dist/grading/grade.d.ts +33 -0
  249. package/dist/grading/grade.js +57 -0
  250. package/dist/guards/answer-confirmation.d.ts +41 -0
  251. package/dist/guards/answer-confirmation.js +109 -0
  252. package/dist/guards/evaluator.d.ts +130 -0
  253. package/dist/guards/evaluator.js +1258 -0
  254. package/dist/guards/harness-adapter.d.ts +17 -0
  255. package/dist/guards/harness-adapter.js +57 -0
  256. package/dist/guards/host.d.ts +8 -0
  257. package/dist/guards/host.js +10 -0
  258. package/dist/guards/invocation-parser.d.ts +66 -0
  259. package/dist/guards/invocation-parser.js +595 -0
  260. package/dist/guards/local-identity.d.ts +44 -0
  261. package/dist/guards/local-identity.js +129 -0
  262. package/dist/guards/policy-change.d.ts +43 -0
  263. package/dist/guards/policy-change.js +159 -0
  264. package/dist/guards/policy-pin.d.ts +13 -0
  265. package/dist/guards/policy-pin.js +177 -0
  266. package/dist/guards/protected-locations.d.ts +62 -0
  267. package/dist/guards/protected-locations.js +461 -0
  268. package/dist/guards/read-only.d.ts +37 -0
  269. package/dist/guards/read-only.js +315 -0
  270. package/dist/init/command.d.ts +41 -0
  271. package/dist/init/command.js +330 -0
  272. package/dist/init/derive.d.ts +53 -0
  273. package/dist/init/derive.js +819 -0
  274. package/dist/init/draft.d.ts +140 -0
  275. package/dist/init/draft.js +1228 -0
  276. package/dist/init/render.d.ts +47 -0
  277. package/dist/init/render.js +104 -0
  278. package/dist/init/scaffold.d.ts +53 -0
  279. package/dist/init/scaffold.js +121 -0
  280. package/dist/invocation.d.ts +13 -0
  281. package/dist/invocation.js +20 -0
  282. package/dist/package-metadata.d.ts +9 -0
  283. package/dist/package-metadata.js +19 -0
  284. package/dist/readiness.d.ts +53 -0
  285. package/dist/readiness.js +88 -0
  286. package/dist/references/named.d.ts +24 -0
  287. package/dist/references/named.js +27 -0
  288. package/dist/refusal.d.ts +15 -0
  289. package/dist/refusal.js +7 -0
  290. package/dist/registry-derivation.d.ts +31 -0
  291. package/dist/registry-derivation.js +32 -0
  292. package/dist/registry-resolve/command.d.ts +47 -0
  293. package/dist/registry-resolve/command.js +226 -0
  294. package/dist/registry-resolve/registry.d.ts +68 -0
  295. package/dist/registry-resolve/registry.js +292 -0
  296. package/dist/registry-resolve/render-shell.d.ts +18 -0
  297. package/dist/registry-resolve/render-shell.js +36 -0
  298. package/dist/repair.d.ts +29 -0
  299. package/dist/repair.js +8 -0
  300. package/dist/repository-path.d.ts +9 -0
  301. package/dist/repository-path.js +16 -0
  302. package/dist/schema-json.d.ts +1 -0
  303. package/dist/schema-json.js +177 -0
  304. package/dist/schema.d.ts +1289 -0
  305. package/dist/schema.js +985 -0
  306. package/dist/setup/command.d.ts +4 -0
  307. package/dist/setup/command.js +4 -0
  308. package/dist/sha256.d.ts +1 -0
  309. package/dist/sha256.js +4 -0
  310. package/dist/surfaces/adoption.d.ts +41 -0
  311. package/dist/surfaces/adoption.js +217 -0
  312. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  313. package/dist/surfaces/consumer-instructions.js +43 -0
  314. package/dist/surfaces/contract.d.ts +42 -0
  315. package/dist/surfaces/contract.js +169 -0
  316. package/dist/surfaces/core-declarations.d.ts +24 -0
  317. package/dist/surfaces/core-declarations.js +79 -0
  318. package/dist/surfaces/development-markers.d.ts +1 -0
  319. package/dist/surfaces/development-markers.js +18 -0
  320. package/dist/surfaces/generator-adapter.d.ts +71 -0
  321. package/dist/surfaces/generator-adapter.js +809 -0
  322. package/dist/surfaces/harness-registry.d.ts +260 -0
  323. package/dist/surfaces/harness-registry.js +146 -0
  324. package/dist/surfaces/mcp.d.ts +145 -0
  325. package/dist/surfaces/mcp.js +349 -0
  326. package/dist/surfaces/merge.d.ts +30 -0
  327. package/dist/surfaces/merge.js +105 -0
  328. package/dist/surfaces/ownership.d.ts +190 -0
  329. package/dist/surfaces/ownership.js +795 -0
  330. package/dist/surfaces/projection.d.ts +136 -0
  331. package/dist/surfaces/projection.js +716 -0
  332. package/dist/surfaces/render.d.ts +79 -0
  333. package/dist/surfaces/render.js +636 -0
  334. package/dist/surfaces/restrictions.d.ts +130 -0
  335. package/dist/surfaces/restrictions.js +447 -0
  336. package/dist/surfaces/selftest.d.ts +162 -0
  337. package/dist/surfaces/selftest.js +393 -0
  338. package/dist/surfaces/skill-contract.d.ts +85 -0
  339. package/dist/surfaces/skill-contract.js +449 -0
  340. package/dist/surfaces/sync.d.ts +57 -0
  341. package/dist/surfaces/sync.js +164 -0
  342. package/dist/team-metadata.d.ts +41 -0
  343. package/dist/team-metadata.js +513 -0
  344. package/dist/testing/gates.d.ts +91 -0
  345. package/dist/testing/gates.js +182 -0
  346. package/dist/testing/hooks.d.ts +59 -0
  347. package/dist/testing/hooks.js +139 -0
  348. package/dist/testing/test-protection.d.ts +34 -0
  349. package/dist/testing/test-protection.js +137 -0
  350. package/dist/testing/verdict.d.ts +29 -0
  351. package/dist/testing/verdict.js +62 -0
  352. package/dist/testing/weakening.d.ts +15 -0
  353. package/dist/testing/weakening.js +30 -0
  354. package/dist/work-orders/application-closure.d.ts +7 -0
  355. package/dist/work-orders/application-closure.js +76 -0
  356. package/dist/work-orders/application-context.d.ts +162 -0
  357. package/dist/work-orders/application-context.js +137 -0
  358. package/dist/work-orders/application-decisions.d.ts +27 -0
  359. package/dist/work-orders/application-decisions.js +121 -0
  360. package/dist/work-orders/application-grading.d.ts +11 -0
  361. package/dist/work-orders/application-grading.js +212 -0
  362. package/dist/work-orders/application.d.ts +6 -0
  363. package/dist/work-orders/application.js +223 -0
  364. package/dist/work-orders/composition.d.ts +101 -0
  365. package/dist/work-orders/composition.js +1202 -0
  366. package/dist/work-orders/contract.d.ts +77 -0
  367. package/dist/work-orders/contract.js +140 -0
  368. package/dist/work-orders/decisions.d.ts +23 -0
  369. package/dist/work-orders/decisions.js +55 -0
  370. package/dist/work-orders/store.d.ts +207 -0
  371. package/dist/work-orders/store.js +291 -0
  372. package/docs/README.md +68 -0
  373. package/docs/architecture.md +136 -0
  374. package/docs/capability-matrix.md +58 -0
  375. package/docs/cli-reference.md +807 -0
  376. package/docs/command-list.md +62 -0
  377. package/docs/compose-patterns.md +99 -0
  378. package/docs/concepts.md +173 -0
  379. package/docs/configuration-essentials.md +177 -0
  380. package/docs/configuration-reference.md +636 -0
  381. package/docs/docs-check.md +76 -0
  382. package/docs/environment.md +948 -0
  383. package/docs/extending.md +43 -0
  384. package/docs/faq.md +113 -0
  385. package/docs/getting-started.md +692 -0
  386. package/docs/harness-surfaces.md +500 -0
  387. package/docs/joining-a-team.md +76 -0
  388. package/docs/release-policy.md +49 -0
  389. package/docs/safety-and-autonomy.md +463 -0
  390. package/docs/troubleshooting.md +382 -0
  391. package/docs/tutorial.md +171 -0
  392. package/docs/work-and-browser.md +420 -0
  393. package/libexec/browser-operation.cjs +270 -0
  394. package/libexec/fence-marker.cjs +101 -0
  395. package/libexec/fence-marker.d.cts +24 -0
  396. package/libexec/fenced-group-runner.cjs +77 -0
  397. package/libexec/fenced-supervisor.cjs +137 -0
  398. package/libexec/generated-filesystem.cjs +731 -0
  399. package/libexec/install-filesystem.cjs +225 -0
  400. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  401. package/libexec/rulesync-generator.cjs +743 -0
  402. package/libexec/surface-generator-worker.cjs +266 -0
  403. package/libexec/surface-output-reader.cjs +99 -0
  404. package/libexec/surface-scratch-cleanup.cjs +14 -0
  405. package/libexec/validate-harness-evidence.d.mts +183 -0
  406. package/libexec/validate-harness-evidence.mjs +1312 -0
  407. package/package.json +74 -4
  408. package/surfaces/README.md +33 -0
  409. package/surfaces/agents/evaluator.agent.md +76 -0
  410. package/surfaces/guards/bash-guard.sh +48 -0
  411. package/surfaces/guards/claude.adapter.json +25 -0
  412. package/surfaces/guards/codex.adapter.json +24 -0
  413. package/surfaces/guards/opencode.adapter.json +25 -0
  414. package/surfaces/references/browser.md +63 -0
  415. package/surfaces/references/test-data-and-personas.md +84 -0
  416. package/surfaces/references/testing-tiers.md +11 -0
  417. package/surfaces/skills/cockpit-browser.skill.md +59 -0
  418. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  419. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  420. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  421. package/surfaces/skills/cockpit-work.skill.md +34 -0
  422. package/surfaces/skills/collection.json +5 -0
  423. package/surfaces/skills/procedures/explain.md +66 -0
  424. package/surfaces/skills/procedures/fix.md +95 -0
  425. package/surfaces/skills/procedures/setup.md +452 -0
  426. package/surfaces/skills/procedures/work.md +134 -0
@@ -0,0 +1,344 @@
1
+ import { snapshotJson } from './environment/contract.js';
2
+ import { admitDiagnosticFix, bindConfigPath, cockpitCommand, initCommand } from './fix-catalogue.js';
3
+ const COMMON_REFUSAL_GUIDANCE = Object.freeze({
4
+ INIT_CANONICAL_OWNERSHIP_MISSING: Object.freeze({
5
+ why: 'init cannot safely resume when its recorded bootstrap state and generated ownership disagree',
6
+ fix: `restore the committed ownership metadata, then rerun ${initCommand()} with the root, target and options you ran it with`,
7
+ }),
8
+ SURFACE_SOURCE_UNAVAILABLE: Object.freeze({
9
+ why: 'generated instructions cannot be trusted without the declared sibling source',
10
+ fix: `restore the named sibling checkout at its declared path, then rerun ${cockpitCommand('sync')}`,
11
+ }),
12
+ SURFACE_OWNERSHIP_INVALID: Object.freeze({
13
+ why: 'cockpit cannot prove which generated settings it is allowed to replace',
14
+ fix: `restore the last committed generated ownership metadata, then rerun ${cockpitCommand('sync')}`,
15
+ }),
16
+ SURFACE_STATE_CONFLICT: Object.freeze({
17
+ why: 'sync would overwrite content that cockpit did not write',
18
+ fix: `for a generated file you edited, move the change into cockpit.toml or a surface overlay, then ask the person who owns the cockpit before you undo the edit or delete the file, and rerun ${cockpitCommand('sync')}; for the team's own .claude/settings.json, .codex/config.toml or .codex/hooks.json, have a human approve ${cockpitCommand('sync', { args: '--adopt <path>' })}`,
19
+ }),
20
+ GENERATED_ARTIFACT_DRIFT: Object.freeze({
21
+ why: 'generated files no longer match the canonical cockpit configuration',
22
+ fix: `run ${cockpitCommand('sync')}, then retry`,
23
+ }),
24
+ GENERATED_OWNERSHIP_INVALID: Object.freeze({
25
+ why: 'publication cannot safely update files without authenticated ownership metadata',
26
+ fix: `restore the committed ownership metadata and rerun ${cockpitCommand('sync')}`,
27
+ }),
28
+ GUARD_AUTHORITY_UNAVAILABLE: Object.freeze({
29
+ why: 'the guard cannot make a safe allow or deny decision without current generated authority',
30
+ fix: `restore cockpit.toml and the generated guard files, then have a human run ${cockpitCommand('sync')}`,
31
+ }),
32
+ GUARD_REFUSED: Object.freeze({
33
+ why: 'the requested command is outside the current cockpit guard policy',
34
+ fix: `follow the refusal reason or ask a human to change cockpit.toml and run ${cockpitCommand('sync')}`,
35
+ }),
36
+ RUNTIME_UNAVAILABLE: Object.freeze({
37
+ why: 'cockpit cannot start or inspect the declared environment without a container runtime it can reach and identify',
38
+ fix: 'start the configured container runtime and verify docker info succeeds, then retry',
39
+ }),
40
+ RUNTIME_UNMEASURED: Object.freeze({
41
+ why: 'this machine has no passing measurement for the current runtime, so cockpit cannot vouch for its mounts, labels, health and teardown',
42
+ fix: `run ${cockpitCommand('runtime measure', { args: '--image <local-image>' })}, which records the result for this machine only, then retry`,
43
+ }),
44
+ CLAIM_CO_TENANT: Object.freeze({
45
+ why: 'another holder still owns or is using the same stack',
46
+ fix: `for a failed up attempt, run ${cockpitCommand('up')} to resume or ${cockpitCommand('down')} to release; otherwise end each cockpit session the message names (let its command finish, or stop the named process), then retry; a session whose process is gone is released by the next up, sleep, down or reset`,
47
+ }),
48
+ PORT_FAMILY_COLLISION: Object.freeze({
49
+ why: 'one or more ports selected for this stack are already in use',
50
+ fix: 'stop the named listener or release the inactive stack, then retry',
51
+ }),
52
+ PORT_LEASE_EXHAUSTED: Object.freeze({
53
+ why: 'every declared port family is leased or occupied',
54
+ fix: 'release an inactive stack or extend ports.count after checking host capacity',
55
+ }),
56
+ SETUP_MANUAL_MISSING: Object.freeze({
57
+ why: 'a manually managed sibling must already exist before cockpit can wire it',
58
+ fix: `create the named checkout at its declared path, then run ${cockpitCommand('setup')} again`,
59
+ }),
60
+ SETUP_REMOTE_MISMATCH: Object.freeze({
61
+ why: 'rewiring an existing checkout to a different repository could replace the wrong source',
62
+ fix: `correct the checkout remote or the sibling declaration, then run ${cockpitCommand('setup')} again`,
63
+ }),
64
+ SETUP_REMOTE_UNRESOLVABLE: Object.freeze({
65
+ why: 'a relative local remote that cannot be resolved cannot be compared with, or cloned into, a checkout',
66
+ fix: `make the remote path readable or declare an absolute path or URL, then run ${cockpitCommand('setup')} again`,
67
+ }),
68
+ });
69
+ // Command-line mistakes (as opposed to failed work) get a pointer to the relevant --help.
70
+ const USAGE_ERROR_CODE = /^CLI_(?:USAGE_INVALID|UNKNOWN_COMMAND|COMMAND_REQUIRED)$|_(?:DUPLICATE|UNSUPPORTED)$/;
71
+ function escapeForTerminal(value) {
72
+ return value.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => {
73
+ if (/[\u0080-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/.test(character)) {
74
+ return `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`;
75
+ }
76
+ const escaped = JSON.stringify(character);
77
+ return escaped.slice(1, -1);
78
+ });
79
+ }
80
+ function terminalSafeJson(value, space) {
81
+ // JSON already escapes C0; the remaining terminal controls must retain their JSON value.
82
+ return JSON.stringify(value, null, space).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`);
83
+ }
84
+ function humanLines(value, depth) {
85
+ const indent = ' '.repeat(depth);
86
+ if (value === null)
87
+ return [`${indent}none`];
88
+ if (typeof value === 'string')
89
+ return [`${indent}${escapeForTerminal(value)}`];
90
+ if (typeof value === 'boolean' || typeof value === 'number')
91
+ return [`${indent}${String(value)}`];
92
+ if (Array.isArray(value)) {
93
+ if (value.length === 0)
94
+ return [`${indent}(none)`];
95
+ return value.flatMap((item) => {
96
+ if (item === null || typeof item !== 'object') {
97
+ const [line] = humanLines(item, 0);
98
+ return [`${indent}- ${line}`];
99
+ }
100
+ return [`${indent}-`, ...humanLines(item, depth + 1)];
101
+ });
102
+ }
103
+ const entries = Object.entries(value);
104
+ if (entries.length === 0)
105
+ return [`${indent}(none)`];
106
+ return entries.flatMap(([key, item]) => {
107
+ const label = `${indent}${escapeForTerminal(key)}:`;
108
+ if (item === null || typeof item !== 'object') {
109
+ const [line] = humanLines(item, 0);
110
+ return [`${label} ${line}`];
111
+ }
112
+ return [label, ...humanLines(item, depth + 1)];
113
+ });
114
+ }
115
+ export function renderHumanValue(value) {
116
+ const captured = snapshotJson(value);
117
+ return captured === undefined ? 'Result is not available as bounded human-readable text.\n'
118
+ : `${humanLines(captured, 0).join('\n')}\n`;
119
+ }
120
+ function projectDiagnostics(input, context) {
121
+ const diagnostics = new Map();
122
+ const guidance = new Map();
123
+ for (const source of input) {
124
+ const { severity, code, target, fix } = source;
125
+ const admitted = admitDiagnosticFix(source, context);
126
+ const identity = { code, ...(target === undefined ? {} : { target }) };
127
+ const message = bindConfigPath(source.message, context);
128
+ const diagnostic = { severity, ...identity, message, ...(admitted ? { fix: admitted.command } : {}) };
129
+ diagnostics.set(JSON.stringify(diagnostic), diagnostic);
130
+ if (fix !== undefined && !admitted) {
131
+ const row = { ...identity, text: bindConfigPath(fix, context) };
132
+ guidance.set(JSON.stringify(row), row);
133
+ }
134
+ else if (fix === undefined && Object.prototype.hasOwnProperty.call(COMMON_REFUSAL_GUIDANCE, code)) {
135
+ const row = { ...identity, text: bindConfigPath(COMMON_REFUSAL_GUIDANCE[code].fix, context) };
136
+ guidance.set(JSON.stringify(row), row);
137
+ }
138
+ }
139
+ return { diagnostics: [...diagnostics.values()], guidance: [...guidance.values()] };
140
+ }
141
+ // What each lifecycle `change` means to the person who ran the command; `--json` keeps the raw value.
142
+ const LIFECYCLE_CHANGES = Object.freeze({
143
+ up: Object.freeze({ started: 'Stack started', resumed: 'Stack resumed', unchanged: 'Stack already running' }),
144
+ sleep: Object.freeze({
145
+ stopped: 'Stack stopped',
146
+ retained: "Released this checkout's hold; another holder keeps the stack running",
147
+ unchanged: 'Nothing to stop: no session holds the stack',
148
+ }),
149
+ down: Object.freeze({ removed: 'Stack removed' }),
150
+ reset: Object.freeze({ unchanged: 'Nothing to reset' }),
151
+ });
152
+ function resetTargetLines(targets) {
153
+ return targets.map(({ kind, identity, declaration }) => ` ${escapeForTerminal(`${String(kind)} ${String(identity)}${declaration === identity ? '' : ` (${String(declaration)})`}`)}\n`).join('');
154
+ }
155
+ // A successful up, sleep, down or preflight printed nothing in human mode, so a person could not
156
+ // tell success from a command that never ran. One line says what happened; anything outside the
157
+ // known result shape prints nothing rather than a guess.
158
+ function lifecycleSummary(command, result) {
159
+ if (command === 'preflight') {
160
+ if (typeof result.verdict !== 'string' || !Array.isArray(result.checks))
161
+ return '';
162
+ const counts = new Map();
163
+ for (const check of result.checks) {
164
+ const verdict = String(check?.verdict).replaceAll('-', ' ');
165
+ counts.set(verdict, (counts.get(verdict) ?? 0) + 1);
166
+ }
167
+ const tally = [...counts].map(([verdict, count]) => `${count} ${verdict === 'pass' ? count === 1 ? 'check passes' : 'checks pass' : verdict}`);
168
+ return escapeForTerminal(`Preflight ${result.verdict === 'pass' ? 'passed' : result.verdict}: ${tally.join(', ') || 'no checks'}.`) + '\n';
169
+ }
170
+ // A plain reset only previews; printing nothing left a person unable to tell an empty plan from
171
+ // one that would delete their data volume.
172
+ if (command === 'reset' && typeof result.planDigest === 'string' && Array.isArray(result.targets)) {
173
+ if (typeof result.stackId !== 'string')
174
+ return '';
175
+ const targets = result.targets;
176
+ if (targets.length === 0)
177
+ return `${escapeForTerminal(`Nothing to reset (${result.stackId}).`)}\n`;
178
+ return `${escapeForTerminal(`Reset would remove ${targets.length} ${targets.length === 1 ? 'target' : 'targets'} (${result.stackId}); plan ${result.planDigest}:`)}\n`
179
+ + `${resetTargetLines(targets)}Nothing was changed. To apply this plan, run the same command with --interactive and approve it.\n`;
180
+ }
181
+ // An applied reset printed nothing, so a person could not tell that their data was gone.
182
+ if (command === 'reset' && result.change === 'reset' && Array.isArray(result.targets)) {
183
+ if (typeof result.stackId !== 'string')
184
+ return '';
185
+ const targets = result.targets;
186
+ return `${escapeForTerminal(`Reset removed ${targets.length} ${targets.length === 1 ? 'target' : 'targets'} (${result.stackId}):`)}\n`
187
+ + resetTargetLines(targets)
188
+ + 'The next up recreates the removed volumes and, where environment.seed is declared, runs the seed to fill them.\n';
189
+ }
190
+ if (command === 'ps' && Array.isArray(result.services)) {
191
+ return result.services
192
+ .map(row => `${escapeForTerminal(`${String(row.service)} ${String(row.state)}${typeof row.status === 'string' ? ` ${row.status}` : ''}`)}\n`).join('');
193
+ }
194
+ if ((command === 'restart' || command === 'rebuild') && typeof result.service === 'string' && typeof result.change === 'string') {
195
+ return `${escapeForTerminal(`Service ${result.service} ${result.change} and healthy.`)}\n`;
196
+ }
197
+ const headline = Object.hasOwn(LIFECYCLE_CHANGES, command) && typeof result.change === 'string'
198
+ && Object.hasOwn(LIFECYCLE_CHANGES[command], result.change) ? LIFECYCLE_CHANGES[command][result.change] : undefined;
199
+ if (headline === undefined || typeof result.stackId !== 'string')
200
+ return '';
201
+ const cold = result.coldStart;
202
+ const timing = typeof cold?.measuredMillis === 'number' && typeof cold.limitMillis === 'number'
203
+ ? `; cold start ${(cold.measuredMillis / 1000).toFixed(1)}s of the ${Math.round(cold.limitMillis / 1000)}s limit` : '';
204
+ const entrypoints = Array.isArray(result.entrypoints) ? result.entrypoints
205
+ .filter(({ name, value }) => typeof name === 'string' && typeof value === 'string')
206
+ .map(({ name, value }) => ` ${escapeForTerminal(String(name))}: ${escapeForTerminal(String(value))}\n`) : [];
207
+ return `${escapeForTerminal(`${headline} (${result.stackId})${timing}.`)}\n${entrypoints.join('')}`;
208
+ }
209
+ function plural(count, one, many = `${one}s`) {
210
+ return `${count} ${count === 1 ? one : many}`;
211
+ }
212
+ // Doctor's full result is a structured report for scripts; printed whole it buried the findings
213
+ // under thousands of lines. A person gets one line per area and the commands that show the detail.
214
+ function doctorSummary(result, diagnostics, fixContext) {
215
+ if (typeof result.verdict !== 'string' || !Array.isArray(result.checks))
216
+ return renderHumanValue(result);
217
+ const lines = [];
218
+ const errors = diagnostics.filter(({ severity }) => severity === 'error').length;
219
+ const warnings = diagnostics.filter(({ severity }) => severity === 'warning').length;
220
+ const findings = [errors > 0 ? plural(errors, 'error') : '', warnings > 0 ? plural(warnings, 'warning') : ''].filter(Boolean);
221
+ lines.push(`Doctor: ${result.verdict}. ${findings.length === 0 ? 'No findings.' : `Findings: ${findings.join(' and ')}, listed below.`}`);
222
+ const tally = new Map();
223
+ for (const check of result.checks) {
224
+ const verdict = String(check?.verdict).replaceAll('-', ' ');
225
+ tally.set(verdict, (tally.get(verdict) ?? 0) + 1);
226
+ }
227
+ lines.push(`Checks: ${[...tally].map(([verdict, count]) => `${count} ${verdict}`).join(', ') || 'none ran'}.`);
228
+ if (Array.isArray(result.comparisons) && result.comparisons.length > 0)
229
+ lines.push(`Remote comparisons: ${result.comparisons.length}.`);
230
+ const freshness = result.freshness;
231
+ if (freshness?.local) {
232
+ const changes = Array.isArray(freshness.changes) ? freshness.changes.length : 0;
233
+ const refused = freshness.local.kind !== 'complete' && Array.isArray(freshness.local.diagnostics)
234
+ ? freshness.local.diagnostics.map(({ code }) => String(code)) : [];
235
+ lines.push(`Generated files: ${refused.length > 0 ? `check refused (${refused.join(', ')})`
236
+ : changes === 0 ? 'current' : `${plural(changes, 'change')} pending`}.`);
237
+ }
238
+ const overrides = result.surfaceOverrides;
239
+ if (overrides?.kind === 'complete' && Array.isArray(overrides.activeOverrides) && Array.isArray(overrides.changedOverrideOriginals)) {
240
+ const active = overrides.activeOverrides.map(({ name }) => String(name));
241
+ const changed = overrides.changedOverrideOriginals.map(({ name }) => String(name));
242
+ lines.push(`Surface overrides: ${active.length === 0 ? 'none' : `${active.length} active (${active.join(', ')})`}`
243
+ + `${changed.length === 0 ? '' : `; ${changed.length} changed upstream (${changed.join(', ')})`}.`);
244
+ }
245
+ else if (overrides)
246
+ lines.push(`Surface overrides: not inspected (${String(overrides.code)}).`);
247
+ if (Array.isArray(result.declarations) && result.declarations.length > 0) {
248
+ const rows = result.declarations;
249
+ const named = (state) => rows.filter(row => row.state === state).map(row => String(row.subject).replaceAll('-', ' '));
250
+ const declared = named('declared');
251
+ const absent = named('not-declared');
252
+ lines.push(`Declared: ${declared.join(', ') || 'nothing'}${absent.length === 0 ? '' : `; not declared: ${absent.join(', ')}`}.`);
253
+ if (declared.includes('docs'))
254
+ lines.push(`Documents: ${bindConfigPath(cockpitCommand('docs check'), fixContext)} checks them; doctor does not.`);
255
+ }
256
+ const readiness = result.readiness;
257
+ if (readiness && Array.isArray(readiness.gaps)) {
258
+ const gaps = readiness.gaps;
259
+ const clear = Array.isArray(readiness.noFinding) ? readiness.noFinding.length : 0;
260
+ lines.push(`Readiness: ${gaps.length === 0 ? 'no gaps' : plural(gaps.length, 'gap')}; ${plural(clear, 'other prerequisite')} with no finding.`);
261
+ for (const gap of gaps) {
262
+ const codes = Array.isArray(gap.findings) ? [...new Set(gap.findings.map(({ code }) => String(code)))] : [];
263
+ lines.push(` ${String(gap.level)} ${String(gap.id)}: ${String(gap.needs)} (${codes.join(', ')})`);
264
+ }
265
+ }
266
+ const release = freshness?.release;
267
+ if (release?.kind === 'known') {
268
+ lines.push(`Release: ${release.currency === 'same-version' ? `${String(release.installedVersion)} is the latest`
269
+ : `${String(release.latestVersion)} is published; ${String(release.installedVersion)} is installed`}.`);
270
+ }
271
+ else if (release?.kind === 'unknown')
272
+ lines.push(`Release: unknown (${String(release.reason)}).`);
273
+ lines.push(`Full report: ${bindConfigPath(cockpitCommand('doctor', { args: '--json' }), fixContext)}`);
274
+ lines.push(`Generated file detail: ${bindConfigPath(cockpitCommand('check'), fixContext)}`);
275
+ return `${lines.map(escapeForTerminal).join('\n')}\n`;
276
+ }
277
+ export function renderHumanDiagnostics(diagnostics, review, fixContext, usageHint) {
278
+ if (review) {
279
+ const { command, outcome } = review;
280
+ if (outcome.result === null || typeof outcome.result !== 'object')
281
+ return '';
282
+ // Work, grade, browser and test commands return their whole result as the answer, and an unmet gate,
283
+ // grade or step returns one with a failing status: the person needs that evidence either way.
284
+ if (command !== undefined && /^(?:(?:work|browser) |test-run$|grade$)/.test(command))
285
+ return renderHumanValue(outcome.result);
286
+ if (outcome.status !== 0)
287
+ return '';
288
+ if (command === 'sync' && 'dryRun' in outcome.result && outcome.result.dryRun === true
289
+ && 'changes' in outcome.result && Array.isArray(outcome.result.changes)) {
290
+ const changes = outcome.result.changes;
291
+ if (!changes.every((change) => change && (change.operation === 'write' || change.operation === 'remove')
292
+ && typeof change.path === 'string'))
293
+ return '';
294
+ const stale = 'staleRemoval' in outcome.result ? outcome.result.staleRemoval : undefined;
295
+ const approval = typeof stale?.digest === 'string' && /^[a-f0-9]{64}$/.test(stale.digest)
296
+ ? `${escapeForTerminal(`Stale-removal digest ${stale.digest}: to approve these removals, a person runs `
297
+ + `${bindConfigPath(cockpitCommand('sync', { args: '--interactive' }), fixContext)} in a terminal and types it.`)}\n` : '';
298
+ return (changes.length === 0 ? 'No generated changes.\n'
299
+ : `${changes.map((change) => `${change.operation} ${terminalSafeJson(change.path)}`).join('\n')}\n`) + approval;
300
+ }
301
+ if (command === 'logs' && 'records' in outcome.result && Array.isArray(outcome.result.records)) {
302
+ const records = outcome.result.records;
303
+ return records.length === 0 ? 'No log records.\n'
304
+ : `${records.map(record => escapeForTerminal(`${String(record.timestamp)} ${String(record.stream)} ${String(record.message)}`)).join('\n')}\n`;
305
+ }
306
+ if (command === 'doctor') {
307
+ return doctorSummary(outcome.result, outcome.diagnostics ?? [], fixContext);
308
+ }
309
+ if (command === 'selftest' || command === 'upgrade' || command === 'init' || command === 'stacks') {
310
+ return renderHumanValue(outcome.result);
311
+ }
312
+ return command === undefined ? '' : lifecycleSummary(command, outcome.result);
313
+ }
314
+ if (diagnostics.length === 0)
315
+ return '';
316
+ const projected = projectDiagnostics(diagnostics, fixContext);
317
+ const hint = usageHint !== undefined && diagnostics.some(({ code }) => USAGE_ERROR_CODE.test(code)) ? usageHint : '';
318
+ return `${projected.diagnostics
319
+ .map((diagnostic) => {
320
+ const target = diagnostic.target ? ` [${escapeForTerminal(diagnostic.target)}]` : '';
321
+ const guidance = projected.guidance.filter(row => row.code === diagnostic.code && row.target === diagnostic.target);
322
+ const common = Object.prototype.hasOwnProperty.call(COMMON_REFUSAL_GUIDANCE, diagnostic.code)
323
+ ? COMMON_REFUSAL_GUIDANCE[diagnostic.code]
324
+ : undefined;
325
+ if (common) {
326
+ const fix = diagnostic.fix ?? guidance[0]?.text ?? bindConfigPath(common.fix, fixContext);
327
+ return `${diagnostic.severity.toUpperCase()} ${escapeForTerminal(diagnostic.code)}${target}\n`
328
+ + `What failed: ${escapeForTerminal(diagnostic.message)}\n`
329
+ + `Why it matters: ${escapeForTerminal(common.why)}\n`
330
+ + `Fix: ${escapeForTerminal(fix)}`;
331
+ }
332
+ const fix = diagnostic.fix ? ` Fix: ${escapeForTerminal(diagnostic.fix)}` : '';
333
+ const renderedGuidance = guidance.map(row => ` Guidance: ${escapeForTerminal(row.text)}`).join('');
334
+ return `${diagnostic.severity.toUpperCase()} ${escapeForTerminal(diagnostic.code)}${target}: ${escapeForTerminal(diagnostic.message)}${fix}${renderedGuidance}`;
335
+ })
336
+ .join('\n')}\n${hint}`;
337
+ }
338
+ export function renderJsonResponse(outcome, fixContext) {
339
+ const { result, diagnostics, evidence, progress, invocation } = outcome;
340
+ const projected = projectDiagnostics(diagnostics, fixContext);
341
+ return `${terminalSafeJson({ result, diagnostics: projected.diagnostics,
342
+ ...(projected.guidance.length === 0 ? {} : { guidance: projected.guidance }), ...(evidence === undefined ? {} : { evidence }),
343
+ ...(progress === undefined ? {} : { progress }), ...(invocation === undefined ? {} : { invocation }) })}\n`;
344
+ }
@@ -0,0 +1,16 @@
1
+ import type { ExecuteProbeOutcome } from '../environment-exec/execute.js';
2
+ import { type Diagnostic, type ExecutionStep } from '../environment/contract.js';
3
+ interface DocsCheckInput {
4
+ readonly configPath: string;
5
+ readonly env: Readonly<Record<string, string | undefined>>;
6
+ }
7
+ interface DocsCheckOutcome {
8
+ readonly status: number;
9
+ readonly diagnostics: readonly Diagnostic[];
10
+ readonly result: unknown;
11
+ }
12
+ type Execute = (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
13
+ export declare function runDocsCheck(input: DocsCheckInput, dependencies: Readonly<{
14
+ execute: Execute;
15
+ }>): Promise<DocsCheckOutcome>;
16
+ export {};
@@ -0,0 +1,197 @@
1
+ // `cockpit docs check`: the living documents and decision records [docs] declares, checked against
2
+ // each repository's files and history. It reads and runs read-only Git; it writes nothing.
3
+ import { lstatIfPresentSync } from '../environment/presence.js';
4
+ import { posix, resolve } from 'node:path';
5
+ import { readBoundedFile } from '../bounded-read.js';
6
+ import { loadCockpitConfig } from '../config/load.js';
7
+ import { ENVIRONMENT_CODES, errorCause } from '../environment/contract.js';
8
+ import { gitRepositoryRefusal } from '../environment/git-observation.js';
9
+ import { redactSensitiveMessage } from '../environment/sensitive.js';
10
+ import { createGitRunner, loadServedSiblings, missingServedCheckout, servedPath } from '../environment/sibling-checkouts.js';
11
+ import { refusalOutcome } from '../refusal.js';
12
+ import { splitRepositoryPath } from '../repository-path.js';
13
+ import { scopeMatcher } from '../work-orders/contract.js';
14
+ import { inspectDecisionRecords } from './decisions.js';
15
+ import { walkMarkdownProse } from './markdown.js';
16
+ import { historicalMakeTargets, makeTargets, parseRemovedPaths, staleReferences } from './references.js';
17
+ const GIT_TIMEOUT_MS = 60_000;
18
+ // The executor's ceiling; a history longer than this is refused rather than read in part.
19
+ const GIT_OUTPUT_LIMIT = 16_777_216;
20
+ const DOCUMENT_LIMIT = 4_194_304;
21
+ const MARKDOWN = /\.(?:md|markdown|mdx)$/i;
22
+ const MAKEFILE = /(?:^|\/)(?:GNUmakefile|[Mm]akefile|[^/]+\.mk)$/;
23
+ const MAKEFILE_PATHSPECS = [':(glob)**/GNUmakefile', ':(glob)**/Makefile', ':(glob)**/makefile', ':(glob)**/*.mk'];
24
+ class EvidenceUnavailable extends Error {
25
+ }
26
+ async function git(runner, root, args) {
27
+ const command = `git -C ${root} ${args.join(' ')}`;
28
+ const ran = await runner.run(root, `git:docs-check:${args[0]}`, args, { timeoutMs: GIT_TIMEOUT_MS, stdoutLimitBytes: GIT_OUTPUT_LIMIT });
29
+ if (ran.kind === 'complete')
30
+ return ran.stdout;
31
+ if (ran.code === ENVIRONMENT_CODES.EXECUTION_STEP_FAILED) {
32
+ // A repository made with `sibling add --create` has no commit yet, so it has no history to read.
33
+ if (args[0] === 'log' && /does not have any commits yet/.test(ran.stderr))
34
+ return '';
35
+ const refused = gitRepositoryRefusal(Buffer.from(ran.stderr));
36
+ throw new EvidenceUnavailable(refused.kind === 'not-a-repository' ? `${root} is not a Git repository` : `${command}: ${redactSensitiveMessage(refused.reason) ?? 'Git printed text that could not be redacted, so it is withheld'}`);
37
+ }
38
+ throw new EvidenceUnavailable(ran.code === ENVIRONMENT_CODES.EXECUTION_STDOUT_LIMIT
39
+ ? `${command} printed more than ${GIT_OUTPUT_LIMIT} bytes` : `${command} did not complete (${ran.detail})`);
40
+ }
41
+ function read(root, path) {
42
+ let bytes;
43
+ try {
44
+ bytes = readBoundedFile(resolve(root, path), DOCUMENT_LIMIT);
45
+ }
46
+ catch (error) {
47
+ throw new EvidenceUnavailable(`${path} could not be read (${errorCause(error)})`);
48
+ }
49
+ if (bytes.kind === 'too-large')
50
+ throw new EvidenceUnavailable(`${path} is larger than ${DOCUMENT_LIMIT} bytes`);
51
+ // Fatal decoding throws only for invalid UTF-8.
52
+ try {
53
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes.bytes);
54
+ }
55
+ catch {
56
+ throw new EvidenceUnavailable(`${path} is not valid UTF-8`);
57
+ }
58
+ }
59
+ function present(root, path) {
60
+ try {
61
+ return lstatIfPresentSync(resolve(root, path)) !== undefined;
62
+ }
63
+ catch (error) {
64
+ throw new EvidenceUnavailable(`${path} could not be inspected (${errorCause(error)})`);
65
+ }
66
+ }
67
+ function removedPaths(output) {
68
+ try {
69
+ return parseRemovedPaths(output);
70
+ }
71
+ catch (error) {
72
+ throw new EvidenceUnavailable(error.message);
73
+ }
74
+ }
75
+ // `<slug>:path` names a sibling's repository; a bare path names the cockpit's own, keyed ''.
76
+ function split(reference) {
77
+ const { slug, path } = splitRepositoryPath(reference);
78
+ return { repository: slug ?? '', path };
79
+ }
80
+ export async function runDocsCheck(input, dependencies) {
81
+ const loaded = await loadCockpitConfig(input.configPath);
82
+ if (loaded.kind !== 'complete')
83
+ return loaded;
84
+ const { config, source } = loaded.result;
85
+ const docs = config.docs;
86
+ if (docs === undefined) {
87
+ return refusalOutcome('DOCS_NOT_DECLARED', `${source.path} has no [docs] table, so there are no living documents or decision records to check`, 'docs', 'add a [docs] table to cockpit.toml: living = ["README.md", "docs/**/*.md"] lists the documents that must stay true, and the optional decisions = "docs/adr" and decisions_index = "docs/adr/README.md" name the decision records and their index; then rerun npx --no cockpit docs check --config <your cockpit.toml>');
88
+ }
89
+ const runner = await createGitRunner(input.env, dependencies.execute);
90
+ if ('unavailable' in runner) {
91
+ return refusalOutcome(ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE, `docs check reads repository history with Git, which is unavailable: ${runner.unavailable}`, 'git', 'install Git or put it on PATH, then rerun npx --no cockpit docs check --config <your cockpit.toml>');
92
+ }
93
+ const cockpitRoot = posix.dirname(source.path);
94
+ // A sibling `cockpit use` serves from another checkout is checked there, where the stack runs it.
95
+ const served = await loadServedSiblings(cockpitRoot, config);
96
+ if (served.kind !== 'complete')
97
+ return { status: 2, diagnostics: [served.diagnostic], result: null };
98
+ const gone = await missingServedCheckout(served.siblings);
99
+ if (gone)
100
+ return { status: 2, diagnostics: [gone], result: null };
101
+ const rootOf = (repository) => repository === '' ? cockpitRoot : servedPath(served.siblings, repository);
102
+ const decisions = docs.decisions === undefined ? undefined : split(docs.decisions);
103
+ const living = docs.living.map(split);
104
+ const repositories = [...new Set([...living.map(entry => entry.repository), ...decisions ? [decisions.repository] : []])];
105
+ const checked = await Promise.all(repositories.map(repository => checkRepository({
106
+ repository, root: rootOf(repository), runner,
107
+ globs: living.filter(entry => entry.repository === repository).map(entry => entry.path),
108
+ ...decisions?.repository === repository ? { decisions: { directory: decisions.path,
109
+ ...docs.decisions_index === undefined ? {} : { index: split(docs.decisions_index).path } } } : {},
110
+ })));
111
+ // Joined in declaration order, so the report does not depend on which repository Git answered first.
112
+ const findings = checked.flatMap(row => row.findings);
113
+ return { status: findings.length === 0 ? 0 : 2, diagnostics: findings,
114
+ result: { repositories: checked.map(row => row.summary), findings: findings.length } };
115
+ }
116
+ async function checkRepository(input) {
117
+ const { repository, root, globs } = input;
118
+ const prefix = repository === '' ? '' : `${repository}:`;
119
+ const findings = [];
120
+ const unavailable = (error) => {
121
+ if (!(error instanceof EvidenceUnavailable))
122
+ throw error;
123
+ findings.push({ severity: 'error', code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE,
124
+ message: `the documents in ${repository === '' ? 'the cockpit repository' : `sibling ${repository}`} could not be checked: ${error.message}`,
125
+ target: repository === '' ? 'docs' : `sibling:${repository}`,
126
+ fix: 'fix what the message names (for a missing sibling checkout, run npx --no cockpit setup --config <your cockpit.toml>), then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
127
+ };
128
+ let livingDocuments = 0;
129
+ let decisionFiles = 0;
130
+ try {
131
+ // Untracked files that are not ignored count, so a document added in this change is checked before its commit.
132
+ const [listed, history] = await Promise.all([
133
+ git(input.runner, root, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']),
134
+ globs.length > 0 ? Promise.all([
135
+ git(input.runner, root, ['log', '--relative', '--format=', '--name-status', '-z', '-M', '--diff-filter=DR']),
136
+ git(input.runner, root, ['log', '--relative', '-p', '--format=', '--no-color', '--no-ext-diff', '--no-textconv', '--', ...MAKEFILE_PATHSPECS]),
137
+ ]) : undefined,
138
+ ]);
139
+ const all = listed.split('\0').filter(Boolean);
140
+ const files = new Set(all);
141
+ const markdown = all.filter(file => MARKDOWN.test(file));
142
+ if (history) {
143
+ const [names, patch] = history;
144
+ for (const glob of globs) {
145
+ if (!markdown.some(scopeMatcher([glob]))) {
146
+ findings.push({ severity: 'error', code: 'DOCS_DECLARATION_UNMATCHED', target: `${prefix}${glob}`,
147
+ message: `[docs] living entry ${prefix}${glob} matches no Markdown file, so it checks nothing`,
148
+ fix: 'correct the glob under [docs] living in cockpit.toml to match the documents, or remove it; then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
149
+ }
150
+ }
151
+ const documents = [];
152
+ for (const path of markdown.filter(scopeMatcher(globs)).sort()) {
153
+ let body;
154
+ try {
155
+ body = read(root, path);
156
+ }
157
+ catch (error) {
158
+ unavailable(error);
159
+ continue;
160
+ }
161
+ // The walker throws only for an HTML comment that never closes.
162
+ try {
163
+ documents.push({ path, walked: walkMarkdownProse(body) });
164
+ }
165
+ catch (error) {
166
+ unavailable(new EvidenceUnavailable(`${path}: ${error.message}`));
167
+ }
168
+ }
169
+ livingDocuments = documents.length;
170
+ const current = makeTargets(all.filter(file => MAKEFILE.test(file)).flatMap(file => read(root, file).split('\n')));
171
+ findings.push(...staleReferences({ repository: prefix, documents, files, targets: current, present: path => present(root, path),
172
+ history: { removed: removedPaths(names), targets: historicalMakeTargets(patch) } }));
173
+ }
174
+ if (input.decisions) {
175
+ const directory = posix.normalize(input.decisions.directory).replace(/\/$/, '');
176
+ const index = input.decisions.index === undefined ? undefined : posix.normalize(input.decisions.index);
177
+ const records = markdown.filter(file => posix.dirname(file) === directory);
178
+ const missing = records.length === 0 ? { target: directory, message: `[docs] decisions ${prefix}${directory} holds no Markdown file` }
179
+ : index !== undefined && !files.has(index) ? { target: index, message: `[docs] decisions_index ${prefix}${index} does not exist` } : undefined;
180
+ if (missing) {
181
+ findings.push({ severity: 'error', code: 'DOCS_DECLARATION_UNMATCHED',
182
+ target: `${prefix}${missing.target}`, message: `${missing.message}, so it checks nothing`,
183
+ fix: 'correct decisions or decisions_index under [docs] in cockpit.toml to the directory of decision records and the file that lists them, or remove the key; then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
184
+ }
185
+ else {
186
+ decisionFiles = records.length;
187
+ findings.push(...inspectDecisionRecords({ repository: prefix, directory,
188
+ files: new Map(records.map(name => [posix.basename(name), read(root, name)])),
189
+ ...index === undefined ? {} : { index: { path: index, body: read(root, index) } } }));
190
+ }
191
+ }
192
+ }
193
+ catch (error) {
194
+ unavailable(error);
195
+ }
196
+ return { summary: { repository: repository === '' ? '.' : repository, root, livingDocuments, decisionFiles }, findings };
197
+ }
@@ -0,0 +1,34 @@
1
+ /** Whether a record file is named by date, and so carries no number. */
2
+ export declare const isDatedRecordName: (name: string) => boolean;
3
+ /** The digits a record file is numbered by, or undefined for a dated or unnumbered name. */
4
+ export declare function recordNumber(name: string): string | undefined;
5
+ /** The first word of a status, lowercased: `accepted` for "**Accepted** (2026-01-02)". */
6
+ export declare function leadingWord(value: string): string;
7
+ type SupersedesReference = Readonly<{
8
+ line: number;
9
+ text: string;
10
+ full: boolean;
11
+ number?: number;
12
+ file?: string;
13
+ }>;
14
+ /** Where a status is stated: a front-matter field, a `Status:` line, or the line under a `## Status` heading. */
15
+ type RecordStatus = Readonly<{
16
+ line: number;
17
+ value: string;
18
+ form: 'front-matter' | 'line' | 'heading';
19
+ }>;
20
+ export interface ParsedRecord {
21
+ readonly status?: RecordStatus;
22
+ readonly supersedes: readonly SupersedesReference[];
23
+ }
24
+ /** A record's status and supersedes references, or why it cannot be read. `name` is its path inside the records' directory. */
25
+ export declare function parseDecisionRecord(name: string, body: string): ParsedRecord | Readonly<{
26
+ unreadable: string;
27
+ }>;
28
+ /**
29
+ * The record's text with its status set to `status`, where `parseDecisionRecord` finds it and in the
30
+ * same form; undefined when the record states no status, or states it on a line this cannot rewrite
31
+ * in place (one an HTML comment shares).
32
+ */
33
+ export declare function withStatus(text: string, status: string): string | undefined;
34
+ export {};