harnery 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/bin/agent-coord +42 -0
  4. package/bin/agent-hook +44 -0
  5. package/bin/harn +40 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +18 -0
  9. package/dist/commander.d.ts +128 -0
  10. package/dist/commander.d.ts.map +1 -0
  11. package/dist/commander.js +126 -0
  12. package/dist/commands/agents.d.ts +18 -0
  13. package/dist/commands/agents.d.ts.map +1 -0
  14. package/dist/commands/agents.js +3946 -0
  15. package/dist/commands/backup.d.ts +22 -0
  16. package/dist/commands/backup.d.ts.map +1 -0
  17. package/dist/commands/backup.js +262 -0
  18. package/dist/commands/browse-ai.d.ts +4 -0
  19. package/dist/commands/browse-ai.d.ts.map +1 -0
  20. package/dist/commands/browse-ai.js +156 -0
  21. package/dist/commands/browse.d.ts +4 -0
  22. package/dist/commands/browse.d.ts.map +1 -0
  23. package/dist/commands/browse.js +590 -0
  24. package/dist/commands/callers.d.ts +4 -0
  25. package/dist/commands/callers.d.ts.map +1 -0
  26. package/dist/commands/callers.js +276 -0
  27. package/dist/commands/completion.d.ts +17 -0
  28. package/dist/commands/completion.d.ts.map +1 -0
  29. package/dist/commands/completion.js +158 -0
  30. package/dist/commands/config-get.d.ts +4 -0
  31. package/dist/commands/config-get.d.ts.map +1 -0
  32. package/dist/commands/config-get.js +131 -0
  33. package/dist/commands/context.d.ts +11 -0
  34. package/dist/commands/context.d.ts.map +1 -0
  35. package/dist/commands/context.js +185 -0
  36. package/dist/commands/cookies.d.ts +4 -0
  37. package/dist/commands/cookies.d.ts.map +1 -0
  38. package/dist/commands/cookies.js +140 -0
  39. package/dist/commands/docs.d.ts +4 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +137 -0
  42. package/dist/commands/doctor.d.ts +25 -0
  43. package/dist/commands/doctor.d.ts.map +1 -0
  44. package/dist/commands/doctor.js +200 -0
  45. package/dist/commands/edit-batch.d.ts +18 -0
  46. package/dist/commands/edit-batch.d.ts.map +1 -0
  47. package/dist/commands/edit-batch.js +172 -0
  48. package/dist/commands/eml.d.ts +4 -0
  49. package/dist/commands/eml.d.ts.map +1 -0
  50. package/dist/commands/eml.js +428 -0
  51. package/dist/commands/env.d.ts +4 -0
  52. package/dist/commands/env.d.ts.map +1 -0
  53. package/dist/commands/env.js +201 -0
  54. package/dist/commands/fetch.d.ts +4 -0
  55. package/dist/commands/fetch.d.ts.map +1 -0
  56. package/dist/commands/fetch.js +99 -0
  57. package/dist/commands/file-history.d.ts +4 -0
  58. package/dist/commands/file-history.d.ts.map +1 -0
  59. package/dist/commands/file-history.js +152 -0
  60. package/dist/commands/grep.d.ts +4 -0
  61. package/dist/commands/grep.d.ts.map +1 -0
  62. package/dist/commands/grep.js +317 -0
  63. package/dist/commands/init.d.ts +82 -0
  64. package/dist/commands/init.d.ts.map +1 -0
  65. package/dist/commands/init.js +288 -0
  66. package/dist/commands/outline.d.ts +4 -0
  67. package/dist/commands/outline.d.ts.map +1 -0
  68. package/dist/commands/outline.js +494 -0
  69. package/dist/commands/presence.d.ts +12 -0
  70. package/dist/commands/presence.d.ts.map +1 -0
  71. package/dist/commands/presence.js +123 -0
  72. package/dist/commands/read.d.ts +7 -0
  73. package/dist/commands/read.d.ts.map +1 -0
  74. package/dist/commands/read.js +46 -0
  75. package/dist/commands/scratch.d.ts +4 -0
  76. package/dist/commands/scratch.d.ts.map +1 -0
  77. package/dist/commands/scratch.js +426 -0
  78. package/dist/commands/session.d.ts +4 -0
  79. package/dist/commands/session.d.ts.map +1 -0
  80. package/dist/commands/session.js +162 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.d.ts.map +1 -0
  83. package/dist/commands/sync.js +275 -0
  84. package/dist/commands/toc.d.ts +5 -0
  85. package/dist/commands/toc.d.ts.map +1 -0
  86. package/dist/commands/toc.js +153 -0
  87. package/dist/commands/tokens.d.ts +4 -0
  88. package/dist/commands/tokens.d.ts.map +1 -0
  89. package/dist/commands/tokens.js +48 -0
  90. package/dist/commands/tunnel.d.ts +4 -0
  91. package/dist/commands/tunnel.d.ts.map +1 -0
  92. package/dist/commands/tunnel.js +513 -0
  93. package/dist/commands/uninstall.d.ts +22 -0
  94. package/dist/commands/uninstall.d.ts.map +1 -0
  95. package/dist/commands/uninstall.js +126 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +165 -0
  99. package/dist/core/agents/canonical-emit.d.ts +27 -0
  100. package/dist/core/agents/canonical-emit.d.ts.map +1 -0
  101. package/dist/core/agents/canonical-emit.js +72 -0
  102. package/dist/core/agents/cli-emit.d.ts +27 -0
  103. package/dist/core/agents/cli-emit.d.ts.map +1 -0
  104. package/dist/core/agents/cli-emit.js +57 -0
  105. package/dist/core/agents/cli.d.ts +10 -0
  106. package/dist/core/agents/cli.d.ts.map +1 -0
  107. package/dist/core/agents/cli.js +757 -0
  108. package/dist/core/agents/codex-replay.d.ts +29 -0
  109. package/dist/core/agents/codex-replay.d.ts.map +1 -0
  110. package/dist/core/agents/codex-replay.js +138 -0
  111. package/dist/core/agents/coord-client.d.ts +98 -0
  112. package/dist/core/agents/coord-client.d.ts.map +1 -0
  113. package/dist/core/agents/coord-client.js +212 -0
  114. package/dist/core/agents/events/consume.d.ts +59 -0
  115. package/dist/core/agents/events/consume.d.ts.map +1 -0
  116. package/dist/core/agents/events/consume.js +147 -0
  117. package/dist/core/agents/events/emit.d.ts +42 -0
  118. package/dist/core/agents/events/emit.d.ts.map +1 -0
  119. package/dist/core/agents/events/emit.js +70 -0
  120. package/dist/core/agents/events/ulid.d.ts +11 -0
  121. package/dist/core/agents/events/ulid.d.ts.map +1 -0
  122. package/dist/core/agents/events/ulid.js +47 -0
  123. package/dist/core/agents/index.d.ts +14 -0
  124. package/dist/core/agents/index.d.ts.map +1 -0
  125. package/dist/core/agents/index.js +13 -0
  126. package/dist/core/agents/paths.d.ts +6 -0
  127. package/dist/core/agents/paths.d.ts.map +1 -0
  128. package/dist/core/agents/paths.js +17 -0
  129. package/dist/core/agents/render/prompt-context.d.ts +43 -0
  130. package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
  131. package/dist/core/agents/render/prompt-context.js +335 -0
  132. package/dist/core/agents/render/session-context.d.ts +39 -0
  133. package/dist/core/agents/render/session-context.d.ts.map +1 -0
  134. package/dist/core/agents/render/session-context.js +283 -0
  135. package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
  136. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
  137. package/dist/core/agents/rules/claim-conflict.js +244 -0
  138. package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
  139. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
  140. package/dist/core/agents/rules/commit-conflict.js +244 -0
  141. package/dist/core/agents/rules/stop-hook.d.ts +44 -0
  142. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
  143. package/dist/core/agents/rules/stop-hook.js +161 -0
  144. package/dist/core/agents/session-events.d.ts +41 -0
  145. package/dist/core/agents/session-events.d.ts.map +1 -0
  146. package/dist/core/agents/session-events.js +205 -0
  147. package/dist/core/agents/state/activity-log.d.ts +18 -0
  148. package/dist/core/agents/state/activity-log.d.ts.map +1 -0
  149. package/dist/core/agents/state/activity-log.js +34 -0
  150. package/dist/core/agents/state/council.d.ts +39 -0
  151. package/dist/core/agents/state/council.d.ts.map +1 -0
  152. package/dist/core/agents/state/council.js +216 -0
  153. package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
  154. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
  155. package/dist/core/agents/state/heartbeat-projector.js +436 -0
  156. package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
  157. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
  158. package/dist/core/agents/state/heartbeat-writer.js +271 -0
  159. package/dist/core/agents/state/names.d.ts +35 -0
  160. package/dist/core/agents/state/names.d.ts.map +1 -0
  161. package/dist/core/agents/state/names.js +376 -0
  162. package/dist/core/agents/state/pidmap.d.ts +11 -0
  163. package/dist/core/agents/state/pidmap.d.ts.map +1 -0
  164. package/dist/core/agents/state/pidmap.js +32 -0
  165. package/dist/core/agents/state/scratch.d.ts +27 -0
  166. package/dist/core/agents/state/scratch.d.ts.map +1 -0
  167. package/dist/core/agents/state/scratch.js +90 -0
  168. package/dist/core/agents/state/shell-mutation.d.ts +17 -0
  169. package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
  170. package/dist/core/agents/state/shell-mutation.js +41 -0
  171. package/dist/core/agents/state/stale-sweep.d.ts +16 -0
  172. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
  173. package/dist/core/agents/state/stale-sweep.js +166 -0
  174. package/dist/core/config.d.ts +29 -0
  175. package/dist/core/config.d.ts.map +1 -0
  176. package/dist/core/config.js +108 -0
  177. package/dist/core/hooks/cli.d.ts +21 -0
  178. package/dist/core/hooks/cli.d.ts.map +1 -0
  179. package/dist/core/hooks/cli.js +1123 -0
  180. package/dist/core/hooks/effects/image-capture.d.ts +43 -0
  181. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
  182. package/dist/core/hooks/effects/image-capture.js +288 -0
  183. package/dist/core/hooks/effects/index.d.ts +64 -0
  184. package/dist/core/hooks/effects/index.d.ts.map +1 -0
  185. package/dist/core/hooks/effects/index.js +197 -0
  186. package/dist/core/hooks/events/emit.d.ts +31 -0
  187. package/dist/core/hooks/events/emit.d.ts.map +1 -0
  188. package/dist/core/hooks/events/emit.js +89 -0
  189. package/dist/core/hooks/events/schema.d.ts +235 -0
  190. package/dist/core/hooks/events/schema.d.ts.map +1 -0
  191. package/dist/core/hooks/events/schema.js +12 -0
  192. package/dist/core/hooks/events/ulid.d.ts +10 -0
  193. package/dist/core/hooks/events/ulid.d.ts.map +1 -0
  194. package/dist/core/hooks/events/ulid.js +47 -0
  195. package/dist/core/hooks/harness/detect.d.ts +9 -0
  196. package/dist/core/hooks/harness/detect.d.ts.map +1 -0
  197. package/dist/core/hooks/harness/detect.js +29 -0
  198. package/dist/core/hooks/harness/events.d.ts +45 -0
  199. package/dist/core/hooks/harness/events.d.ts.map +1 -0
  200. package/dist/core/hooks/harness/events.js +71 -0
  201. package/dist/core/hooks/harness/output.d.ts +46 -0
  202. package/dist/core/hooks/harness/output.d.ts.map +1 -0
  203. package/dist/core/hooks/harness/output.js +87 -0
  204. package/dist/core/hooks/harness/parse.d.ts +67 -0
  205. package/dist/core/hooks/harness/parse.d.ts.map +1 -0
  206. package/dist/core/hooks/harness/parse.js +132 -0
  207. package/dist/core/hooks/index.d.ts +8 -0
  208. package/dist/core/hooks/index.d.ts.map +1 -0
  209. package/dist/core/hooks/index.js +7 -0
  210. package/dist/core/hooks/resolve/anchor.d.ts +37 -0
  211. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
  212. package/dist/core/hooks/resolve/anchor.js +48 -0
  213. package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
  214. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
  215. package/dist/core/hooks/resolve/coord-root.js +27 -0
  216. package/dist/core/hooks/resolve/intent.d.ts +33 -0
  217. package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
  218. package/dist/core/hooks/resolve/intent.js +79 -0
  219. package/dist/core/hooks/resolve/owner.d.ts +42 -0
  220. package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
  221. package/dist/core/hooks/resolve/owner.js +140 -0
  222. package/dist/core/hooks/resolve/transcript.d.ts +26 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
  224. package/dist/core/hooks/resolve/transcript.js +73 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +13 -0
  228. package/dist/lib/agent-browser/client.d.ts +99 -0
  229. package/dist/lib/agent-browser/client.d.ts.map +1 -0
  230. package/dist/lib/agent-browser/client.js +177 -0
  231. package/dist/lib/agent-browser/index.d.ts +2 -0
  232. package/dist/lib/agent-browser/index.d.ts.map +1 -0
  233. package/dist/lib/agent-browser/index.js +1 -0
  234. package/dist/lib/browser/client.d.ts +193 -0
  235. package/dist/lib/browser/client.d.ts.map +1 -0
  236. package/dist/lib/browser/client.js +325 -0
  237. package/dist/lib/browser/dev-overlay.d.ts +23 -0
  238. package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
  239. package/dist/lib/browser/dev-overlay.js +153 -0
  240. package/dist/lib/browser/index.d.ts +5 -0
  241. package/dist/lib/browser/index.d.ts.map +1 -0
  242. package/dist/lib/browser/index.js +2 -0
  243. package/dist/lib/browser/layout.d.ts +79 -0
  244. package/dist/lib/browser/layout.d.ts.map +1 -0
  245. package/dist/lib/browser/layout.js +220 -0
  246. package/dist/lib/browser/visibility.d.ts +86 -0
  247. package/dist/lib/browser/visibility.d.ts.map +1 -0
  248. package/dist/lib/browser/visibility.js +333 -0
  249. package/dist/lib/browser/visual-diff.d.ts +38 -0
  250. package/dist/lib/browser/visual-diff.d.ts.map +1 -0
  251. package/dist/lib/browser/visual-diff.js +107 -0
  252. package/dist/lib/completion/bash.d.ts +25 -0
  253. package/dist/lib/completion/bash.d.ts.map +1 -0
  254. package/dist/lib/completion/bash.js +284 -0
  255. package/dist/lib/completion/fish.d.ts +16 -0
  256. package/dist/lib/completion/fish.d.ts.map +1 -0
  257. package/dist/lib/completion/fish.js +118 -0
  258. package/dist/lib/completion/index.d.ts +5 -0
  259. package/dist/lib/completion/index.d.ts.map +1 -0
  260. package/dist/lib/completion/index.js +4 -0
  261. package/dist/lib/completion/walk.d.ts +68 -0
  262. package/dist/lib/completion/walk.d.ts.map +1 -0
  263. package/dist/lib/completion/walk.js +102 -0
  264. package/dist/lib/completion/zsh.d.ts +13 -0
  265. package/dist/lib/completion/zsh.d.ts.map +1 -0
  266. package/dist/lib/completion/zsh.js +249 -0
  267. package/dist/lib/context/index.d.ts +107 -0
  268. package/dist/lib/context/index.d.ts.map +1 -0
  269. package/dist/lib/context/index.js +275 -0
  270. package/dist/lib/cookies/client.d.ts +131 -0
  271. package/dist/lib/cookies/client.d.ts.map +1 -0
  272. package/dist/lib/cookies/client.js +239 -0
  273. package/dist/lib/cookies/index.d.ts +2 -0
  274. package/dist/lib/cookies/index.d.ts.map +1 -0
  275. package/dist/lib/cookies/index.js +1 -0
  276. package/dist/lib/council/index.d.ts +266 -0
  277. package/dist/lib/council/index.d.ts.map +1 -0
  278. package/dist/lib/council/index.js +674 -0
  279. package/dist/lib/docs-index.d.ts +28 -0
  280. package/dist/lib/docs-index.d.ts.map +1 -0
  281. package/dist/lib/docs-index.js +169 -0
  282. package/dist/lib/docs-lint.d.ts +26 -0
  283. package/dist/lib/docs-lint.d.ts.map +1 -0
  284. package/dist/lib/docs-lint.js +378 -0
  285. package/dist/lib/docs-sweep.d.ts +34 -0
  286. package/dist/lib/docs-sweep.d.ts.map +1 -0
  287. package/dist/lib/docs-sweep.js +304 -0
  288. package/dist/lib/docs.d.ts +27 -0
  289. package/dist/lib/docs.d.ts.map +1 -0
  290. package/dist/lib/docs.js +142 -0
  291. package/dist/lib/env.d.ts +11 -0
  292. package/dist/lib/env.d.ts.map +1 -0
  293. package/dist/lib/env.js +12 -0
  294. package/dist/lib/exec.d.ts +32 -0
  295. package/dist/lib/exec.d.ts.map +1 -0
  296. package/dist/lib/exec.js +54 -0
  297. package/dist/lib/format.d.ts +29 -0
  298. package/dist/lib/format.d.ts.map +1 -0
  299. package/dist/lib/format.js +139 -0
  300. package/dist/lib/http/client.d.ts +56 -0
  301. package/dist/lib/http/client.d.ts.map +1 -0
  302. package/dist/lib/http/client.js +160 -0
  303. package/dist/lib/http/index.d.ts +2 -0
  304. package/dist/lib/http/index.d.ts.map +1 -0
  305. package/dist/lib/http/index.js +1 -0
  306. package/dist/lib/identities/index.d.ts +77 -0
  307. package/dist/lib/identities/index.d.ts.map +1 -0
  308. package/dist/lib/identities/index.js +190 -0
  309. package/dist/lib/machine.d.ts +19 -0
  310. package/dist/lib/machine.d.ts.map +1 -0
  311. package/dist/lib/machine.js +61 -0
  312. package/dist/lib/presence.d.ts +48 -0
  313. package/dist/lib/presence.d.ts.map +1 -0
  314. package/dist/lib/presence.js +123 -0
  315. package/dist/lib/readability/client.d.ts +32 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +119 -0
  318. package/dist/lib/readability/index.d.ts +2 -0
  319. package/dist/lib/readability/index.d.ts.map +1 -0
  320. package/dist/lib/readability/index.js +1 -0
  321. package/dist/lib/scratch/index.d.ts +74 -0
  322. package/dist/lib/scratch/index.d.ts.map +1 -0
  323. package/dist/lib/scratch/index.js +393 -0
  324. package/dist/lib/tunnel/gate.d.ts +12 -0
  325. package/dist/lib/tunnel/gate.d.ts.map +1 -0
  326. package/dist/lib/tunnel/gate.js +101 -0
  327. package/dist/lib/tunnel/state.d.ts +34 -0
  328. package/dist/lib/tunnel/state.d.ts.map +1 -0
  329. package/dist/lib/tunnel/state.js +132 -0
  330. package/package.json +160 -8
  331. package/schemas/.gitkeep +0 -0
  332. package/schemas/config.schema.json +109 -0
  333. package/src/cli.ts +22 -0
  334. package/src/commander.ts +242 -0
  335. package/src/commands/.gitkeep +0 -0
  336. package/src/commands/agents.ts +4567 -0
  337. package/src/commands/backup.ts +305 -0
  338. package/src/commands/browse-ai.ts +198 -0
  339. package/src/commands/browse.ts +849 -0
  340. package/src/commands/callers.ts +363 -0
  341. package/src/commands/completion.ts +193 -0
  342. package/src/commands/config-get.ts +161 -0
  343. package/src/commands/context.ts +209 -0
  344. package/src/commands/cookies.ts +198 -0
  345. package/src/commands/docs.ts +174 -0
  346. package/src/commands/doctor.ts +231 -0
  347. package/src/commands/edit-batch.ts +233 -0
  348. package/src/commands/eml.ts +519 -0
  349. package/src/commands/env.ts +254 -0
  350. package/src/commands/fetch.ts +136 -0
  351. package/src/commands/file-history.ts +202 -0
  352. package/src/commands/grep.ts +371 -0
  353. package/src/commands/init.ts +335 -0
  354. package/src/commands/outline.ts +564 -0
  355. package/src/commands/presence.ts +152 -0
  356. package/src/commands/read.ts +64 -0
  357. package/src/commands/scratch.ts +445 -0
  358. package/src/commands/session.ts +187 -0
  359. package/src/commands/sync.ts +306 -0
  360. package/src/commands/toc.ts +218 -0
  361. package/src/commands/tokens.ts +79 -0
  362. package/src/commands/tunnel.ts +633 -0
  363. package/src/commands/uninstall.ts +144 -0
  364. package/src/commands/web.ts +193 -0
  365. package/src/core/agents/canonical-emit.ts +77 -0
  366. package/src/core/agents/cli-emit.ts +64 -0
  367. package/src/core/agents/cli.ts +838 -0
  368. package/src/core/agents/codex-replay.ts +163 -0
  369. package/src/core/agents/coord-client.ts +249 -0
  370. package/src/core/agents/events/consume.ts +196 -0
  371. package/src/core/agents/events/emit.ts +108 -0
  372. package/src/core/agents/events/ulid.ts +51 -0
  373. package/src/core/agents/index.ts +14 -0
  374. package/src/core/agents/paths.ts +16 -0
  375. package/src/core/agents/render/prompt-context.ts +401 -0
  376. package/src/core/agents/render/session-context.ts +341 -0
  377. package/src/core/agents/rules/claim-conflict.ts +282 -0
  378. package/src/core/agents/rules/commit-conflict.ts +303 -0
  379. package/src/core/agents/rules/stop-hook.ts +229 -0
  380. package/src/core/agents/session-events.ts +228 -0
  381. package/src/core/agents/state/activity-log.ts +33 -0
  382. package/src/core/agents/state/council.ts +265 -0
  383. package/src/core/agents/state/heartbeat-projector.ts +488 -0
  384. package/src/core/agents/state/heartbeat-writer.ts +333 -0
  385. package/src/core/agents/state/names.ts +399 -0
  386. package/src/core/agents/state/pidmap.ts +38 -0
  387. package/src/core/agents/state/scratch.ts +121 -0
  388. package/src/core/agents/state/shell-mutation.ts +44 -0
  389. package/src/core/agents/state/stale-sweep.ts +190 -0
  390. package/src/core/config.ts +111 -0
  391. package/src/core/hooks/cli.ts +1247 -0
  392. package/src/core/hooks/effects/image-capture.ts +330 -0
  393. package/src/core/hooks/effects/index.ts +210 -0
  394. package/src/core/hooks/events/emit.ts +120 -0
  395. package/src/core/hooks/events/schema.ts +430 -0
  396. package/src/core/hooks/events/ulid.ts +51 -0
  397. package/src/core/hooks/harness/detect.ts +30 -0
  398. package/src/core/hooks/harness/events.ts +102 -0
  399. package/src/core/hooks/harness/output.ts +100 -0
  400. package/src/core/hooks/harness/parse.ts +180 -0
  401. package/src/core/hooks/index.ts +16 -0
  402. package/src/core/hooks/resolve/anchor.ts +51 -0
  403. package/src/core/hooks/resolve/coord-root.ts +25 -0
  404. package/src/core/hooks/resolve/intent.ts +89 -0
  405. package/src/core/hooks/resolve/owner.ts +140 -0
  406. package/src/core/hooks/resolve/transcript.ts +72 -0
  407. package/src/hooks/.gitkeep +0 -0
  408. package/src/index.ts +15 -0
  409. package/src/lib/agent-browser/client.ts +239 -0
  410. package/src/lib/agent-browser/index.ts +1 -0
  411. package/src/lib/browser/client.ts +449 -0
  412. package/src/lib/browser/dev-overlay.ts +207 -0
  413. package/src/lib/browser/index.ts +24 -0
  414. package/src/lib/browser/layout.ts +288 -0
  415. package/src/lib/browser/visibility.ts +419 -0
  416. package/src/lib/browser/visual-diff.ts +150 -0
  417. package/src/lib/completion/bash.ts +291 -0
  418. package/src/lib/completion/fish.ts +134 -0
  419. package/src/lib/completion/index.ts +10 -0
  420. package/src/lib/completion/walk.ts +184 -0
  421. package/src/lib/completion/zsh.ts +262 -0
  422. package/src/lib/context/index.ts +386 -0
  423. package/src/lib/cookies/client.ts +301 -0
  424. package/src/lib/cookies/index.ts +13 -0
  425. package/src/lib/council/index.ts +803 -0
  426. package/src/lib/docs-index.ts +216 -0
  427. package/src/lib/docs-lint.ts +413 -0
  428. package/src/lib/docs-sweep.ts +348 -0
  429. package/src/lib/docs.ts +199 -0
  430. package/src/lib/env.ts +12 -0
  431. package/src/lib/exec.ts +74 -0
  432. package/src/lib/format.ts +147 -0
  433. package/src/lib/http/client.ts +211 -0
  434. package/src/lib/http/index.ts +1 -0
  435. package/src/lib/identities/index.ts +210 -0
  436. package/src/lib/machine.ts +61 -0
  437. package/src/lib/presence.ts +154 -0
  438. package/src/lib/readability/client.ts +156 -0
  439. package/src/lib/readability/index.ts +5 -0
  440. package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
  441. package/src/lib/scratch/index.ts +470 -0
  442. package/src/lib/tunnel/gate.ts +113 -0
  443. package/src/lib/tunnel/state.ts +167 -0
  444. package/src/web/.gitkeep +0 -0
  445. package/index.js +0 -1
@@ -0,0 +1,216 @@
1
+ import { existsSync as __existsSyncForDocs } from "node:fs";
2
+ import { resolve as __resolveForDocs } from "node:path";
3
+
4
+ // Module-level docs context, initialized by initDocsContext() before any
5
+ // other function in this file is called. The repo root + submodule list
6
+ // are passed via the context provided to registerDocsCommand.
7
+ let REPO_ROOT = "";
8
+ let SUBMODULES: readonly string[] = [];
9
+
10
+ export function initDocsContext(opts: { repoRoot: string; submodules: readonly string[] }): void {
11
+ REPO_ROOT = opts.repoRoot;
12
+ SUBMODULES = opts.submodules;
13
+ }
14
+
15
+ function submodulePath(name: string): string {
16
+ return __resolveForDocs(REPO_ROOT, name);
17
+ }
18
+
19
+ function isSubmoduleInitialized(name: string): boolean {
20
+ return __existsSyncForDocs(__resolveForDocs(REPO_ROOT, name, ".git"));
21
+ }
22
+
23
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
24
+ import { join, relative } from "node:path";
25
+ import { resolveBinName } from "../core/config.ts";
26
+
27
+ /**
28
+ * Regenerates index READMEs in docs/audits/ and docs/issues/ directories.
29
+ * Idempotent: safe to run on every commit. Reads the first ~30 lines of
30
+ * each dated file to extract title and (for issues) status.
31
+ *
32
+ * Preserves hand-written preambles. The command updates only the section
33
+ * between `<!-- BEGIN INDEX -->` and `<!-- END INDEX -->` markers. If a
34
+ * README exists without markers, the command refuses to overwrite it and
35
+ * reports it as `needs-markers` so the human can insert them explicitly.
36
+ * New READMEs (no file yet) are created with a minimal default preamble.
37
+ */
38
+
39
+ export interface IndexOpts {
40
+ dryRun?: boolean;
41
+ repo?: string;
42
+ }
43
+
44
+ export type IndexStatus = "unchanged" | "updated" | "needs-markers" | "created";
45
+
46
+ export interface IndexResult {
47
+ path: string; // the README being regenerated
48
+ status: IndexStatus;
49
+ before: string;
50
+ after: string; // what would be written (identical to before if status=unchanged or needs-markers)
51
+ }
52
+
53
+ interface DatedEntry {
54
+ file: string; // e.g. "2026-04-11_merge-dup-key.md"
55
+ date: string; // YYYY-MM-DD
56
+ title: string; // first H1 or filename-derived
57
+ status?: string; // for issues
58
+ }
59
+
60
+ const DATED_FILE_PATTERN = /^(\d{4}-\d{2}-\d{2})_([a-z0-9][a-z0-9_-]*)\.md$/i;
61
+
62
+ function extractTitle(content: string, fallbackSlug: string): string {
63
+ const h1Match = content.match(/^#\s+(.+)$/m);
64
+ if (h1Match) return h1Match[1]!.trim();
65
+ // Fallback: slug → Title Case
66
+ return fallbackSlug.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
67
+ }
68
+
69
+ function extractStatus(content: string): string | undefined {
70
+ const head = content.split("\n").slice(0, 20).join("\n");
71
+ const match = head.match(/\*\*Status:\*\*\s*([a-zA-Z][a-zA-Z-]*)/);
72
+ return match ? match[1]!.toLowerCase() : undefined;
73
+ }
74
+
75
+ function readEntries(dir: string, includeStatus: boolean): DatedEntry[] {
76
+ if (!existsSync(dir)) return [];
77
+ const entries: DatedEntry[] = [];
78
+ for (const f of readdirSync(dir)) {
79
+ if (f === "README.md") continue;
80
+ const m = f.match(DATED_FILE_PATTERN);
81
+ if (!m) continue;
82
+ const [, date, slug] = m;
83
+ let content = "";
84
+ try {
85
+ content = readFileSync(join(dir, f), "utf8");
86
+ } catch {
87
+ continue;
88
+ }
89
+ entries.push({
90
+ file: f,
91
+ date: date!,
92
+ title: extractTitle(content, slug!),
93
+ status: includeStatus ? extractStatus(content) : undefined,
94
+ });
95
+ }
96
+ // Newest first
97
+ entries.sort((a, b) => b.date.localeCompare(a.date));
98
+ return entries;
99
+ }
100
+
101
+ function renderAuditsTable(entries: DatedEntry[]): string {
102
+ if (entries.length === 0) return "_No audits recorded._\n";
103
+ const lines = ["| Date | File | Description |", "|------|------|-------------|"];
104
+ for (const e of entries) {
105
+ lines.push(`| ${e.date} | [${e.file}](${e.file}) | ${e.title} |`);
106
+ }
107
+ return `${lines.join("\n")}\n`;
108
+ }
109
+
110
+ function renderIssuesTable(entries: DatedEntry[]): string {
111
+ if (entries.length === 0) return "_No issues recorded._\n";
112
+ const lines = [
113
+ "| Date | File | Status | Description |",
114
+ "|------|------|--------|-------------|",
115
+ ];
116
+ for (const e of entries) {
117
+ const status = e.status ?? "unknown";
118
+ lines.push(`| ${e.date} | [${e.file}](${e.file}) | ${status} | ${e.title} |`);
119
+ }
120
+ return `${lines.join("\n")}\n`;
121
+ }
122
+
123
+ const BEGIN_MARKER = "<!-- BEGIN INDEX -->";
124
+ const END_MARKER = "<!-- END INDEX -->";
125
+
126
+ function defaultPreamble(kind: "audits" | "issues"): string {
127
+ const regen = `The table below is regenerated by \`${resolveBinName()} docs index\`; do not hand-edit it. Add prose above the \`BEGIN INDEX\` marker instead.`;
128
+ return kind === "audits"
129
+ ? [
130
+ "# Audit Documents",
131
+ "",
132
+ "Immutable, date-stamped reports. File names follow `YYYY-MM-DD_<slug>.md`.",
133
+ "",
134
+ regen,
135
+ "",
136
+ ].join("\n")
137
+ : [
138
+ "# Issues",
139
+ "",
140
+ "Date-stamped post-mortems and investigations. File names follow `YYYY-MM-DD_<slug>.md`.",
141
+ "Each file carries a `**Status:**` line (open | resolved | wontfix).",
142
+ "",
143
+ regen,
144
+ "",
145
+ ].join("\n");
146
+ }
147
+
148
+ /** Replace ONLY the section between the markers. Never touch content outside them. */
149
+ function spliceBetweenMarkers(existing: string, body: string): string {
150
+ const beginIdx = existing.indexOf(BEGIN_MARKER);
151
+ const endIdx = existing.indexOf(END_MARKER);
152
+ if (beginIdx < 0 || endIdx < 0 || endIdx < beginIdx) {
153
+ // Caller should have checked hasMarkers() first; return unchanged as a safe fallback.
154
+ return existing;
155
+ }
156
+ const before = existing.slice(0, beginIdx + BEGIN_MARKER.length);
157
+ const after = existing.slice(endIdx);
158
+ return `${before}\n${body}${after}`;
159
+ }
160
+
161
+ function hasMarkers(existing: string): boolean {
162
+ return existing.includes(BEGIN_MARKER) && existing.includes(END_MARKER);
163
+ }
164
+
165
+ function buildNewReadme(kind: "audits" | "issues", body: string): string {
166
+ return `${defaultPreamble(kind)}\n${BEGIN_MARKER}\n${body}${END_MARKER}\n`;
167
+ }
168
+
169
+ export async function runIndex(opts: IndexOpts): Promise<IndexResult[]> {
170
+ const targets: { name: string; path: string }[] = [{ name: "(root)", path: REPO_ROOT }];
171
+ for (const name of SUBMODULES) {
172
+ if (!isSubmoduleInitialized(name)) continue;
173
+ targets.push({ name, path: submodulePath(name) });
174
+ }
175
+
176
+ const filter = opts.repo === "." ? "(root)" : opts.repo;
177
+ const filtered = filter ? targets.filter((t) => t.name === filter) : targets;
178
+
179
+ const results: IndexResult[] = [];
180
+ for (const { name: _name, path } of filtered) {
181
+ for (const kind of ["audits", "issues"] as const) {
182
+ const dir = join(path, "docs", kind);
183
+ if (!existsSync(dir)) continue;
184
+
185
+ const entries = readEntries(dir, kind === "issues");
186
+ const body = kind === "audits" ? renderAuditsTable(entries) : renderIssuesTable(entries);
187
+
188
+ const readmePath = join(dir, "README.md");
189
+ const exists = existsSync(readmePath);
190
+ const before = exists ? readFileSync(readmePath, "utf8") : "";
191
+ const displayPath = relative(REPO_ROOT, readmePath);
192
+
193
+ let status: IndexStatus;
194
+ let after: string;
195
+ if (!exists) {
196
+ after = buildNewReadme(kind, body);
197
+ status = "created";
198
+ } else if (!hasMarkers(before)) {
199
+ // Hands off: the human-written README has no markers. Don't touch it.
200
+ after = before;
201
+ status = "needs-markers";
202
+ } else {
203
+ after = spliceBetweenMarkers(before, body);
204
+ status = before === after ? "unchanged" : "updated";
205
+ }
206
+
207
+ results.push({ path: displayPath, status, before, after });
208
+
209
+ if (!opts.dryRun && (status === "updated" || status === "created")) {
210
+ writeFileSync(readmePath, after, "utf8");
211
+ }
212
+ }
213
+ }
214
+
215
+ return results;
216
+ }
@@ -0,0 +1,413 @@
1
+ import { existsSync as __existsSyncForDocs } from "node:fs";
2
+ import { resolve as __resolveForDocs } from "node:path";
3
+ import { sh } from "./exec.ts";
4
+
5
+ // Module-level docs context, initialized by initDocsContext() before any
6
+ // other function in this file is called. Consumers pass repo metadata
7
+ // + an optional list of extra excluded path prefixes for project-specific
8
+ // directories that shouldn't be subject to doc-lint conventions.
9
+ let REPO_ROOT = "";
10
+ let SUBMODULES: readonly string[] = [];
11
+ let EXTRA_EXCLUDED_PREFIXES: readonly string[] = [];
12
+
13
+ export function initDocsContext(opts: {
14
+ repoRoot: string;
15
+ submodules: readonly string[];
16
+ extraExcludedPrefixes?: readonly string[];
17
+ }): void {
18
+ REPO_ROOT = opts.repoRoot;
19
+ SUBMODULES = opts.submodules;
20
+ EXTRA_EXCLUDED_PREFIXES = opts.extraExcludedPrefixes ?? [];
21
+ }
22
+
23
+ function submodulePath(name: string): string {
24
+ return __resolveForDocs(REPO_ROOT, name);
25
+ }
26
+
27
+ function isSubmoduleInitialized(name: string): boolean {
28
+ return __existsSyncForDocs(__resolveForDocs(REPO_ROOT, name, ".git"));
29
+ }
30
+
31
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
32
+ import { basename, join } from "node:path";
33
+
34
+ /**
35
+ * Documentation linter. Enforces the docs directory-layout + naming contract.
36
+ *
37
+ * Each violation carries a severity: `error` fails the lint, `warning` is
38
+ * informational. `--fast` mode skips content-reading checks so the pre-commit
39
+ * hook stays cheap.
40
+ */
41
+
42
+ export type Severity = "error" | "warning";
43
+
44
+ export interface Violation {
45
+ severity: Severity;
46
+ repo: string;
47
+ path: string; // relative to monorepo root
48
+ rule: string;
49
+ message: string;
50
+ }
51
+
52
+ export interface LintOpts {
53
+ fast?: boolean;
54
+ repo?: string; // limit to one submodule or "." for parent
55
+ }
56
+
57
+ /** Files allowed at a submodule root level. Includes:
58
+ * - In-repo conventions: README.md, CLAUDE.md, LLM-BRIEFING.md, AGENTS.md
59
+ * - GitHub OSS conventions: CHANGELOG.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md,
60
+ * SECURITY.md, SUPPORT.md, AUTHORS.md, MAINTAINERS.md, PULL_REQUEST_TEMPLATE.md
61
+ * (these are recognized by the GitHub UI, and renaming them breaks the integration)
62
+ */
63
+ const ROOT_FILE_ALLOWLIST = new Set([
64
+ "README.md",
65
+ "CLAUDE.md",
66
+ "LLM-BRIEFING.md",
67
+ "AGENTS.md",
68
+ // GitHub-recognized OSS package files
69
+ "CHANGELOG.md",
70
+ "CONTRIBUTING.md",
71
+ "CODE_OF_CONDUCT.md",
72
+ "SECURITY.md",
73
+ "SUPPORT.md",
74
+ "AUTHORS.md",
75
+ "MAINTAINERS.md",
76
+ "PULL_REQUEST_TEMPLATE.md",
77
+ ]);
78
+
79
+ /** Paths that are excluded from markdown discipline even when git-tracked.
80
+ *
81
+ * Covers auto-generated reference dumps and vendored content pages that happen
82
+ * to be checked into git but aren't subject to doc conventions. Paths are
83
+ * relative to the scanned repo root; any file under one of these prefixes is
84
+ * ignored.
85
+ *
86
+ * Note: git-ignored directories (node_modules, .venv, vendor, dbt_packages,
87
+ * target, dist, build) are already excluded because we use `git ls-files`.
88
+ */
89
+ // Built-in exclusions: auto-generated/framework dirs that ship in any
90
+ // project. Consumers extend this via `extraExcludedPrefixes` in
91
+ // initDocsContext for their own project-specific dirs (auto-generated
92
+ // API references, vendored content, etc.).
93
+ const EXCLUDED_PREFIXES = [
94
+ ".agents/", // canonical AI-config sources (skills, subagents, rules, claude-addendum)
95
+ ".claude/", // Claude Code framework files (SKILL.md, agents)
96
+ ".harnery/", // harnery coord/skill state
97
+ ".codex/", // OpenAI Codex framework files (skills/, agents/)
98
+ ".cursor/", // auto-generated Cursor rules
99
+ ];
100
+
101
+ /** Filename patterns that should never exist */
102
+ const FORBIDDEN_ROOT_NAMES = new Set([
103
+ "TODO.md",
104
+ "PROJECT.md",
105
+ "VISION.md",
106
+ "NOTES.md",
107
+ "DECISIONS.md", // should be docs/decisions.md
108
+ "CHANGELOG.md", // should be docs/changelogs/YYYY-MM.md
109
+ ]);
110
+
111
+ /** YYYY-MM-DD_<slug>.md */
112
+ const DATED_FILE_PATTERN = /^\d{4}-\d{2}-\d{2}_[a-z0-9][a-z0-9_-]*\.md$/i;
113
+
114
+ /** YYYY-MM.md for changelogs */
115
+ const CHANGELOG_PATTERN = /^\d{4}-\d{2}\.md$/;
116
+
117
+ /** SCREAMING_SNAKE_CASE.md, excluding allowlisted entry files */
118
+ const SCREAMING_SNAKE_PATTERN = /^[A-Z][A-Z0-9_]+\.md$/;
119
+
120
+ /** kebab-case.md: lowercase letters, digits, hyphens */
121
+ const KEBAB_CASE_PATTERN = /^[a-z0-9][a-z0-9-]*\.md$/;
122
+
123
+ /** Target repos to lint: parent + every initialized submodule */
124
+ function getTargetRepos(opts: LintOpts): { name: string; path: string; isSubmodule: boolean }[] {
125
+ const all: { name: string; path: string; isSubmodule: boolean }[] = [
126
+ { name: "(root)", path: REPO_ROOT, isSubmodule: false },
127
+ ];
128
+ for (const name of SUBMODULES) {
129
+ if (!isSubmoduleInitialized(name)) continue;
130
+ all.push({ name, path: submodulePath(name), isSubmodule: true });
131
+ }
132
+ if (opts.repo) {
133
+ const filter = opts.repo === "." ? "(root)" : opts.repo;
134
+ return all.filter((r) => r.name === filter);
135
+ }
136
+ return all;
137
+ }
138
+
139
+ /** List all tracked .md files in the given repo via `git ls-files`.
140
+ * Returns paths relative to the repo root. Automatically skips node_modules,
141
+ * vendor, .venv, dbt_packages, build/ etc. because they're gitignored.
142
+ */
143
+ async function findMarkdownFiles(root: string): Promise<string[]> {
144
+ const result = await sh('git ls-files --cached "**/*.md" "*.md"', { cwd: root });
145
+ if (result.exitCode !== 0 || !result.stdout) return [];
146
+ return result.stdout
147
+ .split("\n")
148
+ .filter((f) => f.endsWith(".md"))
149
+ .filter(
150
+ (f) =>
151
+ // Framework dirs are excluded at any depth, not just repo root;
152
+ // in-tree repos (monorepos like harnery) nest .claude/.agents/etc.
153
+ !EXCLUDED_PREFIXES.some((p) => f.startsWith(p) || f.includes(`/${p}`)) &&
154
+ !EXTRA_EXCLUDED_PREFIXES.some((p) => f.startsWith(p) || f.includes(`/${p}`)),
155
+ );
156
+ }
157
+
158
+ /** Read first N lines of a file for header inspection */
159
+ function readHead(path: string, lines = 20): string {
160
+ try {
161
+ const content = readFileSync(path, "utf8");
162
+ return content.split("\n").slice(0, lines).join("\n");
163
+ } catch {
164
+ return "";
165
+ }
166
+ }
167
+
168
+ /** Detect whether a file declares itself an intentional monolith */
169
+ function isDeclaredMonolith(path: string): boolean {
170
+ const head = readHead(path, 10);
171
+ return /INTENTIONAL-MONOLITH/i.test(head);
172
+ }
173
+
174
+ /** Detect whether a file carries a Status line in its opening block */
175
+ function hasStatusHeader(path: string): boolean {
176
+ const head = readHead(path, 15);
177
+ return /\*\*Status:\*\*/i.test(head);
178
+ }
179
+
180
+ // --- Individual checks ---
181
+
182
+ /** Entry tier files exist at the repo root */
183
+ function checkEntryTier(repoName: string, repoPath: string, isSubmodule: boolean): Violation[] {
184
+ const violations: Violation[] = [];
185
+ // README.md is required for any repo
186
+ if (!existsSync(join(repoPath, "README.md"))) {
187
+ violations.push({
188
+ severity: "error",
189
+ repo: repoName,
190
+ path: join(repoName === "(root)" ? "" : repoName, "README.md"),
191
+ rule: "entry-tier",
192
+ message: "README.md missing at repo root",
193
+ });
194
+ }
195
+ // CLAUDE.md required for submodules (primary LLM context)
196
+ if (isSubmodule && !existsSync(join(repoPath, "CLAUDE.md"))) {
197
+ violations.push({
198
+ severity: "error",
199
+ repo: repoName,
200
+ path: join(repoName, "CLAUDE.md"),
201
+ rule: "entry-tier",
202
+ message: "CLAUDE.md missing: primary LLM context file",
203
+ });
204
+ }
205
+ return violations;
206
+ }
207
+
208
+ /** No forbidden root-level files */
209
+ function checkRootAllowlist(repoName: string, repoPath: string): Violation[] {
210
+ const violations: Violation[] = [];
211
+ let entries: string[];
212
+ try {
213
+ entries = readdirSync(repoPath);
214
+ } catch {
215
+ return violations;
216
+ }
217
+ for (const entry of entries) {
218
+ if (!entry.endsWith(".md")) continue;
219
+ if (ROOT_FILE_ALLOWLIST.has(entry)) continue;
220
+ const displayPath = join(repoName === "(root)" ? "" : repoName, entry);
221
+ if (FORBIDDEN_ROOT_NAMES.has(entry)) {
222
+ violations.push({
223
+ severity: "error",
224
+ repo: repoName,
225
+ path: displayPath,
226
+ rule: "forbidden-root-file",
227
+ message: `${entry} is not allowed at repo root`,
228
+ });
229
+ } else if (SCREAMING_SNAKE_PATTERN.test(entry)) {
230
+ violations.push({
231
+ severity: "error",
232
+ repo: repoName,
233
+ path: displayPath,
234
+ rule: "root-caps-file",
235
+ message: `${entry} is an ad-hoc caps file at repo root: entry tier is reserved for README.md / CLAUDE.md / LLM-BRIEFING.md / AGENTS.md`,
236
+ });
237
+ }
238
+ }
239
+ return violations;
240
+ }
241
+
242
+ /** No SCREAMING_SNAKE_CASE filenames anywhere */
243
+ function checkNamingConvention(repoName: string, _repoPath: string, files: string[]): Violation[] {
244
+ const violations: Violation[] = [];
245
+ for (const rel of files) {
246
+ const name = basename(rel);
247
+ // Allowlisted names
248
+ if (ROOT_FILE_ALLOWLIST.has(name)) continue;
249
+ // Dated files (audits/issues)
250
+ if (DATED_FILE_PATTERN.test(name)) continue;
251
+ // Changelogs
252
+ if (CHANGELOG_PATTERN.test(name)) continue;
253
+ // decisions.md, runbook.md: explicit
254
+ if (name === "decisions.md" || name === "runbook.md") continue;
255
+ // Known-good kebab-case
256
+ if (KEBAB_CASE_PATTERN.test(name)) continue;
257
+ // README.md inside a subdir is fine
258
+ if (name === "README.md") continue;
259
+ // SCREAMING_SNAKE_CASE violations
260
+ if (SCREAMING_SNAKE_PATTERN.test(name)) {
261
+ violations.push({
262
+ severity: "error",
263
+ repo: repoName,
264
+ path: join(repoName === "(root)" ? "" : repoName, rel),
265
+ rule: "screaming-snake-case",
266
+ message: `filename ${name} uses SCREAMING_SNAKE_CASE; rename to kebab-case`,
267
+ });
268
+ continue;
269
+ }
270
+ // Anything else that's not kebab-case is a warning (Title Case, mixed)
271
+ if (!/^[a-z0-9]/.test(name)) {
272
+ violations.push({
273
+ severity: "warning",
274
+ repo: repoName,
275
+ path: join(repoName === "(root)" ? "" : repoName, rel),
276
+ rule: "non-kebab-filename",
277
+ message: `filename ${name} is not kebab-case`,
278
+ });
279
+ }
280
+ }
281
+ return violations;
282
+ }
283
+
284
+ /** Files in docs/audits/ and docs/issues/ must match YYYY-MM-DD_<slug>.md */
285
+ function checkDatedDirs(repoName: string, _repoPath: string, files: string[]): Violation[] {
286
+ const violations: Violation[] = [];
287
+ const datedDirs = ["docs/audits/", "docs/issues/"];
288
+ for (const rel of files) {
289
+ for (const d of datedDirs) {
290
+ if (!rel.startsWith(d)) continue;
291
+ const name = basename(rel);
292
+ // README.md is the index file, allowed
293
+ if (name === "README.md") continue;
294
+ if (!DATED_FILE_PATTERN.test(name)) {
295
+ violations.push({
296
+ severity: "error",
297
+ repo: repoName,
298
+ path: join(repoName === "(root)" ? "" : repoName, rel),
299
+ rule: "undated-in-dated-dir",
300
+ message: `${d} file must match YYYY-MM-DD_<slug>.md; got ${name}`,
301
+ });
302
+ }
303
+ }
304
+ }
305
+ return violations;
306
+ }
307
+
308
+ /** Changelog files must match YYYY-MM.md */
309
+ function checkChangelogNames(repoName: string, _repoPath: string, files: string[]): Violation[] {
310
+ const violations: Violation[] = [];
311
+ for (const rel of files) {
312
+ if (!rel.startsWith("docs/changelogs/")) continue;
313
+ const name = basename(rel);
314
+ if (name === "README.md") continue;
315
+ if (!CHANGELOG_PATTERN.test(name)) {
316
+ violations.push({
317
+ severity: "error",
318
+ repo: repoName,
319
+ path: join(repoName === "(root)" ? "" : repoName, rel),
320
+ rule: "bad-changelog-name",
321
+ message: `changelog must match YYYY-MM.md; got ${name}`,
322
+ });
323
+ }
324
+ }
325
+ return violations;
326
+ }
327
+
328
+ /** Plans and issues must carry a Status header (content check, slow) */
329
+ function checkStatusHeaders(repoName: string, repoPath: string, files: string[]): Violation[] {
330
+ const violations: Violation[] = [];
331
+ const targetDirs = ["docs/plans/", "docs/issues/"];
332
+ for (const rel of files) {
333
+ const dirMatch = targetDirs.some((d) => rel.startsWith(d));
334
+ if (!dirMatch) continue;
335
+ const name = basename(rel);
336
+ if (name === "README.md") continue;
337
+ // Skip archive subdir
338
+ if (rel.includes("/archive/")) continue;
339
+ const full = join(repoPath, rel);
340
+ if (!hasStatusHeader(full)) {
341
+ const kind = rel.startsWith("docs/plans/") ? "plan" : "issue";
342
+ violations.push({
343
+ severity: "warning",
344
+ repo: repoName,
345
+ path: join(repoName === "(root)" ? "" : repoName, rel),
346
+ rule: "missing-status-header",
347
+ message: `${kind} missing **Status:** line in opening block`,
348
+ });
349
+ }
350
+ }
351
+ return violations;
352
+ }
353
+
354
+ /** Intentional monoliths >30KB need a declaration banner */
355
+ function checkMonolithDeclaration(
356
+ repoName: string,
357
+ repoPath: string,
358
+ files: string[],
359
+ ): Violation[] {
360
+ const violations: Violation[] = [];
361
+ const SIZE_THRESHOLD = 30 * 1024;
362
+ for (const rel of files) {
363
+ // Only flag top-level docs/ files, not per-repo entry tier (LLM-BRIEFING
364
+ // files are monoliths by convention, no banner needed).
365
+ if (repoName !== "(root)") continue;
366
+ if (!rel.startsWith("docs/")) continue;
367
+ if (rel.startsWith("docs/plans/")) continue;
368
+ if (rel.startsWith("docs/audits/")) continue;
369
+ if (rel.startsWith("docs/issues/")) continue;
370
+ if (rel.startsWith("docs/changelogs/")) continue;
371
+ const full = join(repoPath, rel);
372
+ let size: number;
373
+ try {
374
+ size = statSync(full).size;
375
+ } catch {
376
+ continue;
377
+ }
378
+ if (size < SIZE_THRESHOLD) continue;
379
+ if (isDeclaredMonolith(full)) continue;
380
+ violations.push({
381
+ severity: "warning",
382
+ repo: repoName,
383
+ path: rel,
384
+ rule: "undeclared-monolith",
385
+ message: `${(size / 1024).toFixed(0)} KB file has no INTENTIONAL-MONOLITH banner; add one or split`,
386
+ });
387
+ }
388
+ return violations;
389
+ }
390
+
391
+ // --- Runner ---
392
+
393
+ export async function runLint(opts: LintOpts): Promise<Violation[]> {
394
+ const violations: Violation[] = [];
395
+ const repos = getTargetRepos(opts);
396
+
397
+ for (const { name, path, isSubmodule } of repos) {
398
+ violations.push(...checkEntryTier(name, path, isSubmodule));
399
+ violations.push(...checkRootAllowlist(name, path));
400
+
401
+ const files = await findMarkdownFiles(path);
402
+ violations.push(...checkNamingConvention(name, path, files));
403
+ violations.push(...checkDatedDirs(name, path, files));
404
+ violations.push(...checkChangelogNames(name, path, files));
405
+
406
+ if (!opts.fast) {
407
+ violations.push(...checkStatusHeaders(name, path, files));
408
+ violations.push(...checkMonolithDeclaration(name, path, files));
409
+ }
410
+ }
411
+
412
+ return violations;
413
+ }