@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,135 @@
1
+ // The grammar of one decision record: how its file name numbers it, where its status is stated and
2
+ // which records it supersedes. `docs check` reads records with it and `work decide` writes them with
3
+ // it, so a record one accepts is a record the other can number and mark superseded.
4
+ import { posix } from 'node:path';
5
+ import { parseDocument } from 'yaml';
6
+ import { resolveLinkTarget, walkMarkdownProse } from './markdown.js';
7
+ // Dated names (2026-09-<slug>.md) order by date and carry no number to collide.
8
+ const DATED = /^(?:19|20)\d{2}-\d{2}(?:-\d{2})?(?=[-_.])/;
9
+ const NUMBERED = /^(\d+)(?=[-_. ])/;
10
+ const STATUS_LINE = /^(\s*(?:[-*]\s+)?(?:\*\*|__)?status(?:\*\*|__)?\s*:\s*(?:\*\*|__)?\s*)(.*)$/i;
11
+ const STATUS_HEADING = /^#{1,6}\s+status\s*$/i;
12
+ const FRONT_MATTER_STATUS = /^(status\s*:\s*)/i;
13
+ const KEYWORD = /\b(supersedes|superseded\s+by)\b/gi;
14
+ // A link (rewritten to [[target]]), a file name, `ADR-12`, `#12`, or a zero-padded `of 0012`; a bare
15
+ // number counts only right after the keyword, so a year later in the sentence is not read as a record.
16
+ const REFERENCE = /\[\[([^\]]+)\]\]|(?:\b(?:adr|decision|record)s?[-\s]?#?|#|\b(?:of|in)\s+(?=0))(\d{1,5})\b|([\w./-]+\.md)\b/gi;
17
+ /** Whether a record file is named by date, and so carries no number. */
18
+ export const isDatedRecordName = (name) => DATED.test(name);
19
+ /** The digits a record file is numbered by, or undefined for a dated or unnumbered name. */
20
+ export function recordNumber(name) {
21
+ return DATED.test(name) ? undefined : NUMBERED.exec(name)?.[1];
22
+ }
23
+ /** The first word of a status, lowercased: `accepted` for "**Accepted** (2026-01-02)". */
24
+ export function leadingWord(value) {
25
+ return value.replace(/[*_`[\]]/g, '').trim().split(/[\s,.;:()]/, 1)[0].toLowerCase();
26
+ }
27
+ // A link becomes [[target]] so its target survives as one token; code spans keep their text.
28
+ function referenceText(prose) {
29
+ return prose.replace(/\[([^\]]*)\]\(([^)\s]*)[^)]*\)/g, (_match, _label, target) => ` [[${target}]] `).replace(/`/g, ' ');
30
+ }
31
+ function referencesIn(line, text, record) {
32
+ const found = [];
33
+ const keywords = [...text.matchAll(KEYWORD)];
34
+ keywords.forEach((keyword, position) => {
35
+ const start = keyword.index + keyword[0].length;
36
+ // The clause the keyword governs: up to the next keyword, a semicolon or a sentence end.
37
+ const end = Math.min(keywords[position + 1]?.index ?? text.length, ...[/;/, /\.(?:\s|$)/].map(pattern => { const at = text.slice(start).search(pattern); return at === -1 ? text.length : start + at; }));
38
+ const clause = text.slice(start, end);
39
+ const outgoing = keyword[1].toLowerCase() === 'supersedes';
40
+ // "Supersedes 0012" names the whole record; "supersedes the cache clause of 0012" only a part.
41
+ // A number that runs on into a name (`2026-09-cache.md`, `0012-x.md`) is a file name, read below.
42
+ const bare = /^\s*:?\s*(\d{1,5})\b(?![-.]\w)/.exec(clause);
43
+ const leading = /^\s*:?\s*(?:\[\[|(?:adr|decision|record)s?\b|#|[\w./-]+\.md\b)/i.test(clause) || bare !== null;
44
+ // "Supersedes 0012 (ADR-0012)" names one record twice; it is one reference.
45
+ const add = (reference, text) => {
46
+ if (!found.some(row => row.number === reference.number && row.file === reference.file))
47
+ found.push({ line, text, full: outgoing && leading, ...reference });
48
+ };
49
+ if (bare)
50
+ add({ number: Number(bare[1]) }, bare[1]);
51
+ for (const match of clause.matchAll(REFERENCE)) {
52
+ const target = match[1] ?? match[3];
53
+ if (target !== undefined) {
54
+ const file = resolveLinkTarget(posix.dirname(record), target);
55
+ if (file?.endsWith('.md'))
56
+ add({ file }, target);
57
+ }
58
+ else
59
+ add({ number: Number(match[2]) }, match[0].trim());
60
+ }
61
+ });
62
+ return found;
63
+ }
64
+ /** A record's status and supersedes references, or why it cannot be read. `name` is its path inside the records' directory. */
65
+ export function parseDecisionRecord(name, body) {
66
+ let walked;
67
+ try {
68
+ walked = walkMarkdownProse(body);
69
+ }
70
+ catch (error) {
71
+ return { unreadable: error.message };
72
+ }
73
+ const rows = body.split(/\r?\n/);
74
+ let status;
75
+ const supersedes = [];
76
+ if (rows[0] === '---') {
77
+ const end = rows.indexOf('---', 1);
78
+ const front = rows.slice(1, end === -1 ? 1 : end);
79
+ const parsed = parseDocument(front.join('\n'));
80
+ if (parsed.errors.length > 0)
81
+ return { unreadable: `its front matter is not valid YAML (${parsed.errors[0].message.split('\n', 1)[0]})` };
82
+ const data = parsed.toJS();
83
+ if (data && typeof data === 'object') {
84
+ if (typeof data.status === 'string') {
85
+ // Counted from the opening `---`; 0 where the field is not written on a line of its own.
86
+ const at = front.findIndex(row => FRONT_MATTER_STATUS.test(row));
87
+ status = { line: at === -1 ? 0 : at + 2, value: data.status, form: 'front-matter' };
88
+ }
89
+ for (const [field, text] of [['supersedes', 'Supersedes'], ['superseded_by', 'Superseded by']]) {
90
+ for (const value of [data[field]].flat()) {
91
+ if (typeof value === 'string' || typeof value === 'number') {
92
+ supersedes.push(...referencesIn(rows.findIndex(row => row.startsWith(`${field}:`)) + 1, `${text} ${value}`, name));
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ walked.lines.forEach((row, position) => {
99
+ const prose = row.prose;
100
+ if (status === undefined) {
101
+ const line = STATUS_LINE.exec(prose);
102
+ if (line && line[2].trim() !== '')
103
+ status = { line: row.line, value: line[2], form: 'line' };
104
+ else if (STATUS_HEADING.test(prose)) {
105
+ const next = walked.lines.slice(position + 1).find(candidate => candidate.prose.trim() !== '');
106
+ if (next && !/^#/.test(next.prose))
107
+ status = { line: next.line, value: next.prose, form: 'heading' };
108
+ }
109
+ }
110
+ supersedes.push(...referencesIn(row.line, referenceText(prose), name));
111
+ });
112
+ return { ...status ? { status } : {}, supersedes };
113
+ }
114
+ /**
115
+ * The record's text with its status set to `status`, where `parseDecisionRecord` finds it and in the
116
+ * same form; undefined when the record states no status, or states it on a line this cannot rewrite
117
+ * in place (one an HTML comment shares).
118
+ */
119
+ export function withStatus(text, status) {
120
+ const parsed = parseDecisionRecord('', text);
121
+ if ('unreadable' in parsed || parsed.status === undefined)
122
+ return undefined;
123
+ const rows = text.split('\n');
124
+ const index = parsed.status.line - 1;
125
+ const row = rows[index];
126
+ if (row === undefined)
127
+ return undefined;
128
+ const ending = row.endsWith('\r') ? '\r' : '';
129
+ const raw = ending ? row.slice(0, -1) : row;
130
+ const prefix = parsed.status.form === 'heading' ? '' : (parsed.status.form === 'line' ? STATUS_LINE : FRONT_MATTER_STATUS).exec(raw)?.[1];
131
+ if (prefix === undefined)
132
+ return undefined;
133
+ rows[index] = `${prefix}${status}${ending}`;
134
+ return rows.join('\n');
135
+ }
@@ -0,0 +1,16 @@
1
+ import { type Diagnostic } from '../environment/contract.js';
2
+ interface DecisionRecordSet {
3
+ /** Prefixed onto every reported path: '' in the cockpit's own repository, `<slug>:` in a sibling. */
4
+ readonly repository: string;
5
+ /** The records' directory, relative to the repository root. */
6
+ readonly directory: string;
7
+ /** Each Markdown file directly inside the directory, by file name. */
8
+ readonly files: ReadonlyMap<string, string>;
9
+ readonly index?: Readonly<{
10
+ path: string;
11
+ body: string;
12
+ }>;
13
+ }
14
+ /** Every integrity finding in one decision-record set. Index and template files are not records. */
15
+ export declare function inspectDecisionRecords(set: DecisionRecordSet): Diagnostic[];
16
+ export {};
@@ -0,0 +1,103 @@
1
+ // The integrity of a decision-record set: numbering, the index, statuses and supersedes links.
2
+ // Decision records drift in ways nothing else notices: concurrent branches each take the next
3
+ // free number, a record lands without an index line, and a record superseded in full keeps
4
+ // reading "Accepted" because only the newer one was edited.
5
+ import { posix } from 'node:path';
6
+ import { ENVIRONMENT_CODES } from '../environment/contract.js';
7
+ import { leadingWord, parseDecisionRecord, recordNumber } from './decision-record.js';
8
+ import { resolveLinkTarget, walkMarkdownProse } from './markdown.js';
9
+ const DECISION_STATUSES = ['proposed', 'accepted', 'superseded', 'deprecated', 'rejected'];
10
+ const numberOf = (name) => { const digits = recordNumber(name); return digits === undefined ? undefined : Number(digits); };
11
+ /** Every integrity finding in one decision-record set. Index and template files are not records. */
12
+ export function inspectDecisionRecords(set) {
13
+ const findings = [];
14
+ const at = (name, line) => `${set.repository}${posix.join(set.directory, name)}${line ? `:${line}` : ''}`;
15
+ const indexName = set.index && posix.dirname(set.index.path) === set.directory ? posix.basename(set.index.path) : undefined;
16
+ // A README or index page lists the records, and a template is the shape of one; neither decides anything.
17
+ const names = [...set.files.keys()].filter(name => name !== indexName && !/^(?:readme|index)\.md$/i.test(name) && !/template/i.test(name)).sort();
18
+ const records = new Map();
19
+ for (const name of names) {
20
+ const parsed = parseDecisionRecord(name, set.files.get(name));
21
+ if ('unreadable' in parsed) {
22
+ findings.push({ severity: 'error', code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE,
23
+ message: `decision record ${at(name)} could not be read: ${parsed.unreadable}`, target: at(name),
24
+ fix: 'correct the record so it parses as Markdown with optional YAML front matter, then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
25
+ continue;
26
+ }
27
+ records.set(name, parsed);
28
+ }
29
+ const byNumber = new Map();
30
+ for (const name of names) {
31
+ const number = numberOf(name);
32
+ if (number !== undefined)
33
+ byNumber.set(number, [...byNumber.get(number) ?? [], name]);
34
+ }
35
+ for (const [number, shared] of byNumber) {
36
+ if (shared.length < 2)
37
+ continue;
38
+ // Not "_DUPLICATE": the CLI reads that suffix as a repeated option and prints a usage hint.
39
+ findings.push({ severity: 'error', code: 'DECISION_NUMBER_REUSED', target: at(shared[1]),
40
+ message: `decision records ${shared.map(name => at(name)).join(' and ')} share number ${number}`,
41
+ fix: 'renumber the later record to the next free number, then update the index and every supersedes line that cites it' });
42
+ }
43
+ for (const [name, record] of records) {
44
+ const status = record.status === undefined ? undefined : leadingWord(record.status.value);
45
+ if (!DECISION_STATUSES.some(known => known === status)) {
46
+ findings.push({ severity: 'error', code: 'DECISION_STATUS_MISSING', target: at(name, record.status?.line),
47
+ message: record.status === undefined ? `decision record ${at(name)} states no status`
48
+ : `decision record ${at(name)} has status "${record.status.value.trim()}", which starts with none of ${DECISION_STATUSES.join(', ')}`,
49
+ fix: `give the record a status line such as "Status: Accepted", or status: accepted in its front matter, starting with one of ${DECISION_STATUSES.join(', ')}` });
50
+ }
51
+ for (const reference of record.supersedes) {
52
+ const target = reference.file !== undefined
53
+ ? posix.dirname(reference.file) === '.' && records.has(reference.file) ? reference.file : undefined
54
+ : names.find(candidate => numberOf(candidate) === reference.number);
55
+ if (target === undefined) {
56
+ findings.push({ severity: 'error', code: 'DECISION_LINK_UNRESOLVED', target: at(name, reference.line),
57
+ message: `decision record ${at(name, reference.line)} cites ${reference.text}, which names no record in ${set.repository}${set.directory}`,
58
+ fix: 'correct the number or link to name an existing record, or restore the record it names' });
59
+ continue;
60
+ }
61
+ const superseded = records.get(target)?.status;
62
+ // Only an accepted record replaces another; a proposed or rejected one leaves it standing.
63
+ if (reference.full && ['accepted', 'superseded', 'deprecated'].includes(status ?? '') && superseded && leadingWord(superseded.value) === 'accepted') {
64
+ findings.push({ severity: 'error', code: 'DECISION_SUPERSEDED_STALE', target: at(target, superseded.line),
65
+ message: `decision record ${at(target)} still has status "${superseded.value.trim()}", but ${at(name, reference.line)} supersedes it`,
66
+ fix: `change the superseded record's status to "Superseded by" the newer record; if only part of it is replaced, say which part in the newer record's supersedes line, such as "supersedes the retry clause of ${target}"` });
67
+ }
68
+ }
69
+ }
70
+ if (set.index) {
71
+ let links;
72
+ try {
73
+ links = walkMarkdownProse(set.index.body).links;
74
+ }
75
+ catch (error) {
76
+ findings.push({ severity: 'error', code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE,
77
+ message: `decision index ${set.repository}${set.index.path} could not be read: ${error.message}`, target: `${set.repository}${set.index.path}`,
78
+ fix: 'close the HTML comment the message names, then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
79
+ return findings;
80
+ }
81
+ const linked = new Set();
82
+ for (const link of links) {
83
+ const resolved = resolveLinkTarget(posix.dirname(set.index.path), link.target);
84
+ if (resolved === undefined || !resolved.endsWith('.md') || posix.dirname(resolved) !== set.directory)
85
+ continue;
86
+ const name = posix.basename(resolved);
87
+ linked.add(name);
88
+ if (!set.files.has(name)) {
89
+ findings.push({ severity: 'error', code: 'DECISION_INDEX_ENTRY_MISSING', target: `${set.repository}${set.index.path}:${link.line}`,
90
+ message: `decision index ${set.repository}${set.index.path}:${link.line} links ${link.target}, which does not exist`,
91
+ fix: 'correct the link to the record\'s current file name, or remove the entry if the record was deleted on purpose' });
92
+ }
93
+ }
94
+ for (const name of names) {
95
+ if (linked.has(name))
96
+ continue;
97
+ findings.push({ severity: 'error', code: 'DECISION_UNINDEXED', target: at(name),
98
+ message: `decision record ${at(name)} is not linked from ${set.repository}${set.index.path}`,
99
+ fix: 'add a line linking the record to the decision index' });
100
+ }
101
+ }
102
+ return findings;
103
+ }
@@ -0,0 +1,36 @@
1
+ interface ProseLine {
2
+ readonly line: number;
3
+ readonly prose: string;
4
+ readonly comments: readonly string[];
5
+ }
6
+ interface MarkdownLink {
7
+ readonly line: number;
8
+ readonly label: string;
9
+ readonly target: string;
10
+ readonly index: number;
11
+ readonly end: number;
12
+ }
13
+ export interface WalkedMarkdown {
14
+ readonly lines: readonly ProseLine[];
15
+ readonly fences: readonly Readonly<{
16
+ line: number;
17
+ info: string;
18
+ }>[];
19
+ /** Each line inside a fenced block, with the info string of its fence. */
20
+ readonly fencedLines: readonly Readonly<{
21
+ line: number;
22
+ info: string;
23
+ text: string;
24
+ }>[];
25
+ readonly links: readonly MarkdownLink[];
26
+ }
27
+ /** The visible prose of a page, line by line: fenced code and front matter removed, HTML comments lifted out. */
28
+ export declare function walkMarkdownProse(body: string): WalkedMarkdown;
29
+ /**
30
+ * The repository path a link target or cited path names, resolved against `base`, the directory it
31
+ * is relative to ('' for the repository root): its fragment, query, `:line[:column]` suffix and
32
+ * trailing slashes dropped, and its percent escapes decoded. Undefined for an empty, absolute or
33
+ * external (`scheme:`) target. The result may still climb out with `../`; the caller decides.
34
+ */
35
+ export declare function resolveLinkTarget(base: string, target: string): string | undefined;
36
+ export {};
@@ -0,0 +1,88 @@
1
+ // The Markdown reading shared by the repository's own docs lint and `cockpit docs check`: one walker,
2
+ // so both agree on what counts as prose, code and a link.
3
+ import { posix } from 'node:path';
4
+ /** The visible prose of a page, line by line: fenced code and front matter removed, HTML comments lifted out. */
5
+ export function walkMarkdownProse(body) {
6
+ const lines = [];
7
+ const fences = [];
8
+ const fencedLines = [];
9
+ const links = [];
10
+ const rows = body.split(/\r?\n/);
11
+ let fence;
12
+ const start = rows[0] === '---' ? rows.indexOf('---', 1) + 1 : 0;
13
+ let comment;
14
+ for (let index = start; index < rows.length; index += 1) {
15
+ const number = index + 1;
16
+ let text = rows[index];
17
+ const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(text);
18
+ if (fence !== undefined) {
19
+ if (marker && marker[1][0] === fence[0] && marker[1].length >= fence.length && marker[2].trim() === '')
20
+ fence = undefined;
21
+ else
22
+ fencedLines.push({ line: number, info: fences.at(-1).info, text });
23
+ continue;
24
+ }
25
+ if (marker && comment === undefined) {
26
+ fence = marker[1];
27
+ fences.push({ line: number, info: marker[2].trim() });
28
+ continue;
29
+ }
30
+ const comments = [];
31
+ let prose = '';
32
+ while (text !== '') {
33
+ if (comment) {
34
+ const close = text.indexOf('-->');
35
+ comment.text += ` ${close === -1 ? text : text.slice(0, close)}`;
36
+ if (close === -1) {
37
+ text = '';
38
+ break;
39
+ }
40
+ text = text.slice(close + 3);
41
+ const owner = lines.find(row => row.line === comment.line);
42
+ const done = comment.text.trim().replace(/\s+/g, ' ');
43
+ if (owner)
44
+ owner.comments.push(done);
45
+ else
46
+ comments.push(done);
47
+ comment = undefined;
48
+ continue;
49
+ }
50
+ const open = text.indexOf('<!--');
51
+ if (open === -1) {
52
+ prose += text;
53
+ break;
54
+ }
55
+ prose += text.slice(0, open);
56
+ comment = { line: number, text: '' };
57
+ text = text.slice(open + 4);
58
+ }
59
+ lines.push({ line: number, prose, comments });
60
+ const visible = prose.replace(/`+[^`]*`+/g, span => ' '.repeat(span.length));
61
+ for (const match of visible.matchAll(/\[([^\]]*)\]\(([^)\s]*)[^)]*\)/g)) {
62
+ links.push({ line: number, label: match[1], target: match[2], index: match.index, end: match.index + match[0].length });
63
+ }
64
+ }
65
+ if (comment)
66
+ throw new Error(`HTML comment opened on line ${comment.line} is not closed`);
67
+ return { lines, fences, fencedLines, links };
68
+ }
69
+ /**
70
+ * The repository path a link target or cited path names, resolved against `base`, the directory it
71
+ * is relative to ('' for the repository root): its fragment, query, `:line[:column]` suffix and
72
+ * trailing slashes dropped, and its percent escapes decoded. Undefined for an empty, absolute or
73
+ * external (`scheme:`) target. The result may still climb out with `../`; the caller decides.
74
+ */
75
+ export function resolveLinkTarget(base, target) {
76
+ let value = target.split(/[#?]/, 1)[0].replace(/:\d+(?::\d+)?$/, '').replace(/\/+$/, '');
77
+ // A `%` that starts no valid escape is part of the file name, as CommonMark reads it.
78
+ try {
79
+ value = decodeURI(value);
80
+ }
81
+ catch (error) {
82
+ if (!(error instanceof URIError))
83
+ throw error;
84
+ }
85
+ if (value === '' || value.startsWith('/') || /^[a-z][a-z0-9+.-]*:/i.test(value))
86
+ return undefined;
87
+ return posix.normalize(posix.join(base, value));
88
+ }
@@ -0,0 +1,32 @@
1
+ import type { Diagnostic } from '../environment/contract.js';
2
+ import { type WalkedMarkdown } from './markdown.js';
3
+ interface RepositoryHistory {
4
+ /** Each path a commit deleted or renamed away, newest first, mapped to its new name for a rename. */
5
+ readonly removed: ReadonlyMap<string, string | undefined>;
6
+ /** Every make target any version of a Makefile defined. */
7
+ readonly targets: ReadonlySet<string>;
8
+ }
9
+ export interface LivingDocument {
10
+ readonly path: string;
11
+ readonly walked: WalkedMarkdown;
12
+ }
13
+ /** `git log --format= --name-status -z -M --diff-filter=DR` output, newest commit first. */
14
+ export declare function parseRemovedPaths(output: string): Map<string, string | undefined>;
15
+ /** The targets the given Makefile lines define, including .PHONY names; special and pattern targets are skipped. */
16
+ export declare function makeTargets(lines: Iterable<string>): Set<string>;
17
+ /** The targets ever defined, from `git log -p` over the Makefiles: every line was added once. */
18
+ export declare function historicalMakeTargets(patch: string): Set<string>;
19
+ /**
20
+ * The stale references in the given documents. `files` are the repository's files now; a cited
21
+ * directory counts as present while any file is under it.
22
+ */
23
+ export declare function staleReferences(input: Readonly<{
24
+ repository: string;
25
+ documents: readonly LivingDocument[];
26
+ files: ReadonlySet<string>;
27
+ targets: ReadonlySet<string>;
28
+ history: RepositoryHistory;
29
+ /** Whether the path is on disk; an ignored file is absent from `files` but not removed. */
30
+ present: (path: string) => boolean;
31
+ }>): Diagnostic[];
32
+ export {};
@@ -0,0 +1,148 @@
1
+ // Stale references in living documents: a path or `make` target a document cites that the
2
+ // repository once had and has since removed. Keying on history is the whole precision story: a
3
+ // backlog full of proposals ("add `make park`") names things that never existed, and those are
4
+ // left alone, while `make publish` after its target was deleted is exactly the rot to report. No
5
+ // allowlist is needed, and a link that never resolved is a link checker's business.
6
+ import { posix } from 'node:path';
7
+ import { resolveLinkTarget } from './markdown.js';
8
+ /** `git log --format= --name-status -z -M --diff-filter=DR` output, newest commit first. */
9
+ export function parseRemovedPaths(output) {
10
+ const removed = new Map();
11
+ const tokens = output.split('\0').filter(token => token !== '');
12
+ for (let index = 0; index < tokens.length;) {
13
+ const status = tokens[index++];
14
+ if (status === 'D') {
15
+ const path = tokens[index++];
16
+ if (!removed.has(path))
17
+ removed.set(path, undefined);
18
+ }
19
+ else if (/^R\d*$/.test(status)) {
20
+ const from = tokens[index++];
21
+ const to = tokens[index++];
22
+ if (!removed.has(from))
23
+ removed.set(from, to);
24
+ }
25
+ else
26
+ throw new Error(`unexpected git name-status record ${JSON.stringify(status)}`);
27
+ }
28
+ return removed;
29
+ }
30
+ const RULE = /^([^\s:#=$][^:#=$]*?)\s*::?(?!=)/;
31
+ /** The targets the given Makefile lines define, including .PHONY names; special and pattern targets are skipped. */
32
+ export function makeTargets(lines) {
33
+ const targets = new Set();
34
+ for (const line of lines) {
35
+ const phony = /^\.PHONY\s*:(.*)$/.exec(line);
36
+ const names = phony ? phony[1] : RULE.exec(line)?.[1];
37
+ for (const name of names?.trim().split(/\s+/) ?? []) {
38
+ if (name !== '' && !name.startsWith('.') && !name.includes('%'))
39
+ targets.add(name);
40
+ }
41
+ }
42
+ return targets;
43
+ }
44
+ /** The targets ever defined, from `git log -p` over the Makefiles: every line was added once. */
45
+ export function historicalMakeTargets(patch) {
46
+ return makeTargets(patch.split('\n').filter(line => /^[+-]/.test(line) && !/^(?:\+\+\+|---) /.test(line)).map(line => line.slice(1)));
47
+ }
48
+ const SHELL_FENCES = new Set(['', 'sh', 'bash', 'shell', 'zsh', 'console', 'terminal', 'shell-session']);
49
+ const PATH_SPAN = /^[A-Za-z0-9._@+~-][A-Za-z0-9._@+~/-]*$/;
50
+ // `make a b` cites a and b; `make -C dir x` or `make -f other.mk x` cites another Makefile, so not x.
51
+ function citedTargets(text) {
52
+ const targets = [];
53
+ for (const match of text.matchAll(/(?:^|[\s;&|(`$])make((?:[ \t]+[^\s;&|)`]+)*)/g)) {
54
+ const words = match[1].trim().split(/\s+/).filter(Boolean);
55
+ if (words.some(word => /^-(?:C|f)|^--(?:directory|file|makefile)\b/.test(word)))
56
+ continue;
57
+ for (const word of words) {
58
+ if (word.startsWith('-') || word.includes('='))
59
+ continue;
60
+ if (!/^[A-Za-z0-9_][A-Za-z0-9._/-]*$/.test(word))
61
+ break;
62
+ targets.push(word);
63
+ }
64
+ }
65
+ return targets;
66
+ }
67
+ function citedPath(document, raw, relativeToDocument) {
68
+ const value = raw.trim().replace(/^<|>$/g, '');
69
+ const relative = relativeToDocument || value.startsWith('./') || value.startsWith('../');
70
+ const resolved = resolveLinkTarget(relative ? posix.dirname(document) : '', value);
71
+ return resolved === undefined || resolved === '.' || resolved.startsWith('../') ? undefined : resolved;
72
+ }
73
+ // Every directory above the given files: `a/b/c.md` contributes `a` and `a/b`.
74
+ const ancestors = (files) => new Set([...files].flatMap(file => file.split('/').slice(0, -1).map((_, index, parts) => parts.slice(0, index + 1).join('/'))));
75
+ /**
76
+ * The stale references in the given documents. `files` are the repository's files now; a cited
77
+ * directory counts as present while any file is under it.
78
+ */
79
+ export function staleReferences(input) {
80
+ const findings = [];
81
+ const directories = ancestors(input.files);
82
+ const exists = (path) => input.files.has(path) || directories.has(path);
83
+ const removedDirectories = ancestors(input.history.removed.keys());
84
+ // Follow renames to where the content lives now; a chain that ends in a deletion is a removal.
85
+ const successor = (path) => {
86
+ const seen = new Set();
87
+ let current = path;
88
+ while (input.history.removed.has(current) && !seen.has(current)) {
89
+ seen.add(current);
90
+ const next = input.history.removed.get(current);
91
+ if (next === undefined)
92
+ return undefined;
93
+ if (exists(next))
94
+ return next;
95
+ current = next;
96
+ }
97
+ return undefined;
98
+ };
99
+ for (const document of input.documents) {
100
+ const reported = new Set();
101
+ const start = findings.length;
102
+ const where = (line) => `${input.repository}${document.path}:${line}`;
103
+ const cite = (line, path) => {
104
+ if (path === undefined || exists(path) || !input.history.removed.has(path) && !removedDirectories.has(path) || input.present(path))
105
+ return;
106
+ const key = `${line}\0${path}`;
107
+ if (reported.has(key))
108
+ return;
109
+ reported.add(key);
110
+ const renamed = successor(path);
111
+ findings.push({ severity: 'error', code: 'DOCS_REFERENCE_REMOVED', target: where(line),
112
+ message: renamed === undefined
113
+ ? `${where(line)} cites ${path}, which the repository history removed`
114
+ : `${where(line)} cites ${path}, which the repository history renamed to ${renamed}`,
115
+ fix: 'cite the path that replaced it (a rename is named in the message), or rewrite or delete the sentence if what it described is gone; then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
116
+ };
117
+ const citeTargets = (line, text) => {
118
+ for (const target of citedTargets(text)) {
119
+ const key = `${line}\0make ${target}`;
120
+ if (input.targets.has(target) || !input.history.targets.has(target) || reported.has(key))
121
+ continue;
122
+ reported.add(key);
123
+ findings.push({ severity: 'error', code: 'DOCS_TARGET_REMOVED', target: where(line),
124
+ message: `${where(line)} runs make ${target}, a target the Makefiles once defined and no longer do`,
125
+ fix: 'cite the target or command that replaced it, or rewrite or delete the sentence if the step is gone; then rerun npx --no cockpit docs check --config <your cockpit.toml>' });
126
+ }
127
+ };
128
+ for (const row of document.walked.lines) {
129
+ for (const span of row.prose.matchAll(/(`+)([^`]+)\1/g)) {
130
+ const value = span[2].trim();
131
+ if (PATH_SPAN.test(value))
132
+ cite(row.line, citedPath(document.path, value, false));
133
+ else
134
+ citeTargets(row.line, value);
135
+ }
136
+ }
137
+ for (const link of document.walked.links)
138
+ cite(link.line, citedPath(document.path, link.target, true));
139
+ for (const row of document.walked.fencedLines) {
140
+ if (SHELL_FENCES.has(row.info.split(/\s+/, 1)[0].toLowerCase()))
141
+ citeTargets(row.line, row.text.replace(/^\s*[$%>#]\s+/, ''));
142
+ }
143
+ // Spans, links and fences are read in separate passes; the report follows the document.
144
+ const line = (finding) => Number(finding.target.slice(finding.target.lastIndexOf(':') + 1));
145
+ findings.splice(start, findings.length - start, ...findings.slice(start).sort((left, right) => line(left) - line(right)));
146
+ }
147
+ return findings;
148
+ }
package/dist/emit.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { CockpitConfig } from './schema.js';
2
+ import type { DocumentedSibling, Gap } from './registry-resolve/registry.js';
3
+ /**
4
+ * Render a config as `cockpit.toml`.
5
+ *
6
+ * The round trip this closes is the acceptance test for the whole schema: a registry read
7
+ * out of an existing harness, emitted here, and parsed back must mean the same thing. Until
8
+ * this existed the "round trip" stopped at an in-memory object, which proves the parser
9
+ * agrees with itself and nothing else.
10
+ */
11
+ export declare function emitToml(config: CockpitConfig): string;
12
+ /**
13
+ * Render the gaps a registry draft could not answer, as commented TOML.
14
+ *
15
+ * Deliberately emitted as comments rather than placeholder values: a placeholder is a value,
16
+ * and a value is indistinguishable from an answer the moment the file is read by anything
17
+ * other than the person who generated it. A blocking gap keeps the config invalid on
18
+ * purpose, so the schema refuses it until a human writes the real answer.
19
+ */
20
+ export declare function emitGaps(gaps: readonly Gap[]): string;
21
+ /** The sibling rows as the schema wants them, dropping fields the source did not carry. */
22
+ export declare function toSiblingRows(siblings: readonly DocumentedSibling[]): Record<string, unknown>[];
package/dist/emit.js ADDED
@@ -0,0 +1,43 @@
1
+ import { stringify } from 'smol-toml';
2
+ /**
3
+ * Render a config as `cockpit.toml`.
4
+ *
5
+ * The round trip this closes is the acceptance test for the whole schema: a registry read
6
+ * out of an existing harness, emitted here, and parsed back must mean the same thing. Until
7
+ * this existed the "round trip" stopped at an in-memory object, which proves the parser
8
+ * agrees with itself and nothing else.
9
+ */
10
+ export function emitToml(config) {
11
+ return stringify(config);
12
+ }
13
+ /**
14
+ * Render the gaps a registry draft could not answer, as commented TOML.
15
+ *
16
+ * Deliberately emitted as comments rather than placeholder values: a placeholder is a value,
17
+ * and a value is indistinguishable from an answer the moment the file is read by anything
18
+ * other than the person who generated it. A blocking gap keeps the config invalid on
19
+ * purpose, so the schema refuses it until a human writes the real answer.
20
+ */
21
+ export function emitGaps(gaps) {
22
+ if (gaps.length === 0)
23
+ return '';
24
+ const lines = ['# Gaps — cockpit could not read these from its sources.', '#'];
25
+ for (const g of gaps) {
26
+ const mark = g.blocking ? 'REQUIRED' : 'optional';
27
+ lines.push(`# [${mark}] ${g.slug}.${g.field}: ${g.why}`);
28
+ }
29
+ return lines.join('\n') + '\n';
30
+ }
31
+ /** The sibling rows as the schema wants them, dropping fields the source did not carry. */
32
+ export function toSiblingRows(siblings) {
33
+ return siblings.map((s) => ({
34
+ slug: s.slug,
35
+ dir: s.dir,
36
+ ...(s.remote ? { remote: s.remote } : {}),
37
+ ...(s.branch ? { branch: s.branch } : {}),
38
+ mount: s.mount.value,
39
+ acquire: s.acquire.value,
40
+ var: s.var,
41
+ purpose: s.purpose,
42
+ }));
43
+ }
@@ -0,0 +1,3 @@
1
+ export declare const ENTRYPOINT_KINDS: readonly ["entrypoint", "origin", "callback_url", "advertised_listener", "cors_origin", "public_base_url"];
2
+ export type EntrypointKind = (typeof ENTRYPOINT_KINDS)[number];
3
+ export declare function isEntrypointKind(value: unknown): value is EntrypointKind;
@@ -0,0 +1,11 @@
1
+ export const ENTRYPOINT_KINDS = Object.freeze([
2
+ 'entrypoint',
3
+ 'origin',
4
+ 'callback_url',
5
+ 'advertised_listener',
6
+ 'cors_origin',
7
+ 'public_base_url',
8
+ ]);
9
+ export function isEntrypointKind(value) {
10
+ return typeof value === 'string' && ENTRYPOINT_KINDS.includes(value);
11
+ }