@quickchatai/quickreview 1.1.0 → 1.3.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 (471) hide show
  1. package/README.md +137 -57
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +60 -14
  4. package/deploy/ansible/README.md +20 -12
  5. package/deploy/ansible/inventory.example.ini +1 -1
  6. package/deploy/collect-secrets.sh +1 -1
  7. package/deploy/qcr-update.sh +1 -1
  8. package/dist/extensions/cli/home.js +14 -0
  9. package/dist/extensions/cli/home.js.map +1 -0
  10. package/dist/extensions/cli/login-route.js +5 -0
  11. package/dist/extensions/cli/login-route.js.map +1 -0
  12. package/dist/extensions/cli/selfimprove.js +18 -0
  13. package/dist/extensions/cli/selfimprove.js.map +1 -0
  14. package/dist/extensions/home.js +350 -0
  15. package/dist/extensions/home.js.map +1 -0
  16. package/dist/extensions/login-route.js +180 -0
  17. package/dist/extensions/login-route.js.map +1 -0
  18. package/dist/extensions/onboarding.js +35 -11
  19. package/dist/extensions/onboarding.js.map +1 -1
  20. package/dist/extensions/selfimprove.js +49 -0
  21. package/dist/extensions/selfimprove.js.map +1 -0
  22. package/dist/extensions/setup-scan.js +23 -592
  23. package/dist/extensions/setup-scan.js.map +1 -1
  24. package/dist/extensions/setup.js +2 -47
  25. package/dist/extensions/setup.js.map +1 -1
  26. package/dist/src/archive/restore.js +18 -15
  27. package/dist/src/archive/restore.js.map +1 -1
  28. package/dist/src/botworker.js +13 -2
  29. package/dist/src/botworker.js.map +1 -1
  30. package/dist/src/channelfs.js +3 -26
  31. package/dist/src/channelfs.js.map +1 -1
  32. package/dist/src/ciretry.js +2 -1
  33. package/dist/src/ciretry.js.map +1 -1
  34. package/dist/src/cli.js +54 -61
  35. package/dist/src/cli.js.map +1 -1
  36. package/dist/src/cliops.js +3 -2
  37. package/dist/src/cliops.js.map +1 -1
  38. package/dist/src/commands.js +25 -2
  39. package/dist/src/commands.js.map +1 -1
  40. package/dist/src/config.js +32 -2
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/daemon.js +50 -8
  43. package/dist/src/daemon.js.map +1 -1
  44. package/dist/src/debuground.js +1 -0
  45. package/dist/src/debuground.js.map +1 -1
  46. package/dist/src/debugroundpi.js +6 -1
  47. package/dist/src/debugroundpi.js.map +1 -1
  48. package/dist/src/dispatch.js +5 -4
  49. package/dist/src/dispatch.js.map +1 -1
  50. package/dist/src/engine/caserunner-sdk.js +15 -251
  51. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  52. package/dist/src/engine/debugpi.js +17 -5
  53. package/dist/src/engine/debugpi.js.map +1 -1
  54. package/dist/src/execboundary.js +10 -3
  55. package/dist/src/execboundary.js.map +1 -1
  56. package/dist/src/github.js +51 -16
  57. package/dist/src/github.js.map +1 -1
  58. package/dist/src/githubtoken.js +346 -0
  59. package/dist/src/githubtoken.js.map +1 -0
  60. package/dist/src/home/adapters.js +349 -0
  61. package/dist/src/home/adapters.js.map +1 -0
  62. package/dist/src/home/advanced.js +318 -0
  63. package/dist/src/home/advanced.js.map +1 -0
  64. package/dist/src/home/advancedtools.js +57 -0
  65. package/dist/src/home/advancedtools.js.map +1 -0
  66. package/dist/src/home/autoreview.js +325 -0
  67. package/dist/src/home/autoreview.js.map +1 -0
  68. package/dist/src/home/daemonstatus.js +32 -0
  69. package/dist/src/home/daemonstatus.js.map +1 -0
  70. package/dist/src/home/devicelogin.js +61 -0
  71. package/dist/src/home/devicelogin.js.map +1 -0
  72. package/dist/src/home/launch.js +202 -0
  73. package/dist/src/home/launch.js.map +1 -0
  74. package/dist/src/home/loginowner.js +205 -0
  75. package/dist/src/home/loginowner.js.map +1 -0
  76. package/dist/src/home/memory.js +122 -0
  77. package/dist/src/home/memory.js.map +1 -0
  78. package/dist/src/home/model.js +811 -0
  79. package/dist/src/home/model.js.map +1 -0
  80. package/dist/src/home/panel.js +520 -0
  81. package/dist/src/home/panel.js.map +1 -0
  82. package/dist/src/home/ports.js +19 -0
  83. package/dist/src/home/ports.js.map +1 -0
  84. package/dist/src/home/review.js +163 -0
  85. package/dist/src/home/review.js.map +1 -0
  86. package/dist/src/home/reviews.js +478 -0
  87. package/dist/src/home/reviews.js.map +1 -0
  88. package/dist/src/home/setup.js +507 -0
  89. package/dist/src/home/setup.js.map +1 -0
  90. package/dist/src/home/setupadapters.js +214 -0
  91. package/dist/src/home/setupadapters.js.map +1 -0
  92. package/dist/src/home/setupcard.js +199 -0
  93. package/dist/src/home/setupcard.js.map +1 -0
  94. package/dist/src/home/setuptools.js +45 -0
  95. package/dist/src/home/setuptools.js.map +1 -0
  96. package/dist/src/home/spend.js +160 -0
  97. package/dist/src/home/spend.js.map +1 -0
  98. package/dist/src/home/switches.js +209 -0
  99. package/dist/src/home/switches.js.map +1 -0
  100. package/dist/src/home/switchon.js +439 -0
  101. package/dist/src/home/switchon.js.map +1 -0
  102. package/dist/src/home/toolnames.js +41 -0
  103. package/dist/src/home/toolnames.js.map +1 -0
  104. package/dist/src/home/tune.js +341 -0
  105. package/dist/src/home/tune.js.map +1 -0
  106. package/dist/src/home/words.js +7 -0
  107. package/dist/src/home/words.js.map +1 -0
  108. package/dist/src/homes.js +14 -0
  109. package/dist/src/homes.js.map +1 -1
  110. package/dist/src/install.js +1 -1
  111. package/dist/src/install.js.map +1 -1
  112. package/dist/src/ipc/workerspec.js +8 -0
  113. package/dist/src/ipc/workerspec.js.map +1 -1
  114. package/dist/src/live/publish.js +1 -1
  115. package/dist/src/live/publish.js.map +1 -1
  116. package/dist/src/livecli.js +1 -1
  117. package/dist/src/livecli.js.map +1 -1
  118. package/dist/src/loginhint.js +8 -3
  119. package/dist/src/loginhint.js.map +1 -1
  120. package/dist/src/loginrefresh/handover.js +121 -0
  121. package/dist/src/loginrefresh/handover.js.map +1 -0
  122. package/dist/src/loginrefresh/lock.js +54 -3
  123. package/dist/src/loginrefresh/lock.js.map +1 -1
  124. package/dist/src/loginrefresh/pirefresh.js +3 -0
  125. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  126. package/dist/src/loginrefresh/refresher.js +33 -5
  127. package/dist/src/loginrefresh/refresher.js.map +1 -1
  128. package/dist/src/loginrefresh/supervisor.js +18 -1
  129. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  130. package/dist/src/npmverbs.js +46 -14
  131. package/dist/src/npmverbs.js.map +1 -1
  132. package/dist/src/onboard/choices.js +35 -4
  133. package/dist/src/onboard/choices.js.map +1 -1
  134. package/dist/src/onboard/engine.js +32 -306
  135. package/dist/src/onboard/engine.js.map +1 -1
  136. package/dist/src/onboard/pipin.js +22 -0
  137. package/dist/src/onboard/pipin.js.map +1 -0
  138. package/dist/src/onboard/stepforward.js +41 -0
  139. package/dist/src/onboard/stepforward.js.map +1 -0
  140. package/dist/src/onboard/steps/appslug.js +4 -1
  141. package/dist/src/onboard/steps/appslug.js.map +1 -1
  142. package/dist/src/onboard/steps/bringup.js +3 -2
  143. package/dist/src/onboard/steps/bringup.js.map +1 -1
  144. package/dist/src/onboard/steps/compose.js +29 -0
  145. package/dist/src/onboard/steps/compose.js.map +1 -1
  146. package/dist/src/onboard/steps/configfill.js +120 -39
  147. package/dist/src/onboard/steps/configfill.js.map +1 -1
  148. package/dist/src/onboard/steps/githubaccount.js +595 -0
  149. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  150. package/dist/src/onboard/steps/githubapp.js +65 -7
  151. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  152. package/dist/src/onboard/steps/golive.js +2 -0
  153. package/dist/src/onboard/steps/golive.js.map +1 -1
  154. package/dist/src/onboard/steps/index.js +11 -0
  155. package/dist/src/onboard/steps/index.js.map +1 -1
  156. package/dist/src/onboard/steps/ingress.js +80 -25
  157. package/dist/src/onboard/steps/ingress.js.map +1 -1
  158. package/dist/src/onboard/steps/llmauth.js +103 -30
  159. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  160. package/dist/src/onboard/steps/loginshare.js +213 -0
  161. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  162. package/dist/src/onboard/steps/memory.js +67 -3
  163. package/dist/src/onboard/steps/memory.js.map +1 -1
  164. package/dist/src/onboard/steps/proof.js +366 -56
  165. package/dist/src/onboard/steps/proof.js.map +1 -1
  166. package/dist/src/onboard/steps/repoconfig.js +72 -0
  167. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  168. package/dist/src/onboard/steps/topology.js +6 -6
  169. package/dist/src/onboard/steps/topology.js.map +1 -1
  170. package/dist/src/onboard/steps/transport.js +9 -1
  171. package/dist/src/onboard/steps/transport.js.map +1 -1
  172. package/dist/src/onboard/steps/tuiroute.js +102 -0
  173. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  174. package/dist/src/onboard/steps/verifyup.js +17 -1
  175. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  176. package/dist/src/onboard/toolnames.js +30 -0
  177. package/dist/src/onboard/toolnames.js.map +1 -0
  178. package/dist/src/operatorwrite.js +23 -0
  179. package/dist/src/operatorwrite.js.map +1 -0
  180. package/dist/src/outcomes.js +4 -1
  181. package/dist/src/outcomes.js.map +1 -1
  182. package/dist/src/piagentdir.js +13 -0
  183. package/dist/src/piagentdir.js.map +1 -0
  184. package/dist/src/pilogin.js +31 -16
  185. package/dist/src/pilogin.js.map +1 -1
  186. package/dist/src/posting-journal.js +2 -1
  187. package/dist/src/posting-journal.js.map +1 -1
  188. package/dist/src/posting-outbox.js +6 -2
  189. package/dist/src/posting-outbox.js.map +1 -1
  190. package/dist/src/posting-receipt.js +14 -1
  191. package/dist/src/posting-receipt.js.map +1 -1
  192. package/dist/src/qcrbin.js +16 -11
  193. package/dist/src/qcrbin.js.map +1 -1
  194. package/dist/src/recheck.js +2 -1
  195. package/dist/src/recheck.js.map +1 -1
  196. package/dist/src/reconcile.js +22 -2
  197. package/dist/src/reconcile.js.map +1 -1
  198. package/dist/src/reviewargs.js +122 -0
  199. package/dist/src/reviewargs.js.map +1 -0
  200. package/dist/src/reviewcli.js +7 -161
  201. package/dist/src/reviewcli.js.map +1 -1
  202. package/dist/src/reviewdetach.js +76 -11
  203. package/dist/src/reviewdetach.js.map +1 -1
  204. package/dist/src/reviewhistory.js +32 -6
  205. package/dist/src/reviewhistory.js.map +1 -1
  206. package/dist/src/reviewnow.js +45 -0
  207. package/dist/src/reviewnow.js.map +1 -0
  208. package/dist/src/scancli.js +39 -691
  209. package/dist/src/scancli.js.map +1 -1
  210. package/dist/src/selfimprove.js +66 -0
  211. package/dist/src/selfimprove.js.map +1 -0
  212. package/dist/src/setup/adapters.js +5 -223
  213. package/dist/src/setup/adapters.js.map +1 -1
  214. package/dist/src/setup/caserunner.js +15 -249
  215. package/dist/src/setup/caserunner.js.map +1 -1
  216. package/dist/src/setup/engine.js +32 -341
  217. package/dist/src/setup/engine.js.map +1 -1
  218. package/dist/src/setup/facts.js +3 -592
  219. package/dist/src/setup/facts.js.map +1 -1
  220. package/dist/src/setup/findings.js +3 -947
  221. package/dist/src/setup/findings.js.map +1 -1
  222. package/dist/src/setup/presentation.js +3 -8
  223. package/dist/src/setup/presentation.js.map +1 -1
  224. package/dist/src/setup/scan.js +3 -724
  225. package/dist/src/setup/scan.js.map +1 -1
  226. package/dist/src/setup/scanbuild.js +9 -92
  227. package/dist/src/setup/scanbuild.js.map +1 -1
  228. package/dist/src/setup/scanlease.js +3 -250
  229. package/dist/src/setup/scanlease.js.map +1 -1
  230. package/dist/src/setup/scanreport.js +3 -288
  231. package/dist/src/setup/scanreport.js.map +1 -1
  232. package/dist/src/setup/scanrun.js +3 -1732
  233. package/dist/src/setup/scanrun.js.map +1 -1
  234. package/dist/src/setup/scantree.js +3 -256
  235. package/dist/src/setup/scantree.js.map +1 -1
  236. package/dist/src/setup/stage2request.js +3 -258
  237. package/dist/src/setup/stage2request.js.map +1 -1
  238. package/dist/src/setup/unitcut.js +3 -436
  239. package/dist/src/setup/unitcut.js.map +1 -1
  240. package/dist/src/state.js +2 -0
  241. package/dist/src/state.js.map +1 -1
  242. package/dist/src/statemachine.js +4 -1
  243. package/dist/src/statemachine.js.map +1 -1
  244. package/dist/src/takeover.js +1 -0
  245. package/dist/src/takeover.js.map +1 -1
  246. package/dist/src/takeoverpi.js +15 -1
  247. package/dist/src/takeoverpi.js.map +1 -1
  248. package/dist/src/tui/app.js +49 -0
  249. package/dist/src/tui/app.js.map +1 -1
  250. package/dist/src/tui/board.js +88 -12
  251. package/dist/src/tui/board.js.map +1 -1
  252. package/dist/src/tui/cmdbar.js +4 -1
  253. package/dist/src/tui/cmdbar.js.map +1 -1
  254. package/dist/src/tui/companion.js +76 -11
  255. package/dist/src/tui/companion.js.map +1 -1
  256. package/dist/src/tui/connect.js +7 -1
  257. package/dist/src/tui/connect.js.map +1 -1
  258. package/dist/src/tui/deploytab.js +2 -2
  259. package/dist/src/tui/deploytab.js.map +1 -1
  260. package/dist/src/tui/filters.js +5 -2
  261. package/dist/src/tui/filters.js.map +1 -1
  262. package/dist/src/tui/ghread.js +61 -5
  263. package/dist/src/tui/ghread.js.map +1 -1
  264. package/dist/src/tui/index.js +109 -20
  265. package/dist/src/tui/index.js.map +1 -1
  266. package/dist/src/tui/memforward.js +39 -2
  267. package/dist/src/tui/memforward.js.map +1 -1
  268. package/dist/src/tui/memory.js +3 -16
  269. package/dist/src/tui/memory.js.map +1 -1
  270. package/dist/src/tui/memoryurl.js +22 -0
  271. package/dist/src/tui/memoryurl.js.map +1 -0
  272. package/dist/src/tui/model.js +25 -0
  273. package/dist/src/tui/model.js.map +1 -1
  274. package/dist/src/tui/prpause.js +7 -0
  275. package/dist/src/tui/prpause.js.map +1 -1
  276. package/dist/src/tui/remote.js +7 -1
  277. package/dist/src/tui/remote.js.map +1 -1
  278. package/dist/src/tui/route.js +143 -0
  279. package/dist/src/tui/route.js.map +1 -0
  280. package/dist/src/tui/setupstate.js +32 -0
  281. package/dist/src/tui/setupstate.js.map +1 -0
  282. package/dist/src/tui/sshtriage.js +102 -5
  283. package/dist/src/tui/sshtriage.js.map +1 -1
  284. package/dist/src/tui/theme.js +2 -1
  285. package/dist/src/tui/theme.js.map +1 -1
  286. package/dist/src/tui/unwedge.js +2 -12
  287. package/dist/src/tui/unwedge.js.map +1 -1
  288. package/dist/src/verdicts.js +17 -5
  289. package/dist/src/verdicts.js.map +1 -1
  290. package/dist/src/version.js +1 -1
  291. package/dist/src/version.js.map +1 -1
  292. package/dist/src/webhook.js +12 -1
  293. package/dist/src/webhook.js.map +1 -1
  294. package/dist/src/wire.js +34 -8
  295. package/dist/src/wire.js.map +1 -1
  296. package/dist/src/workerworld.js +11 -1
  297. package/dist/src/workerworld.js.map +1 -1
  298. package/docker/README.md +1 -1
  299. package/docker/companion/entrypoint.sh +20 -1
  300. package/docker/entrypoint.sh +57 -6
  301. package/docker-compose.yml +16 -1
  302. package/extensions/cli/home.ts +14 -0
  303. package/extensions/cli/login-route.ts +10 -0
  304. package/extensions/cli/selfimprove.ts +18 -0
  305. package/extensions/home.ts +484 -0
  306. package/extensions/login-route.ts +220 -0
  307. package/extensions/onboarding.ts +41 -11
  308. package/extensions/selfimprove.ts +65 -0
  309. package/extensions/setup-scan.ts +79 -766
  310. package/extensions/setup.ts +14 -70
  311. package/package.json +6 -4
  312. package/prompts/onboarding.md +58 -18
  313. package/schemas/review-result.v1.json +60 -0
  314. package/scripts/deploy.sh +8 -6
  315. package/scripts/mint-token.sh +25 -4
  316. package/scripts/qcr-host.sh +28 -5
  317. package/scripts/vm-ghcr-login.sh +7 -4
  318. package/src/archive/restore.ts +18 -21
  319. package/src/botworker.ts +13 -2
  320. package/src/channelfs.ts +3 -26
  321. package/src/ciretry.ts +2 -1
  322. package/src/cli.ts +59 -67
  323. package/src/cliops.ts +3 -2
  324. package/src/commands.ts +28 -2
  325. package/src/config.ts +62 -4
  326. package/src/daemon.ts +58 -7
  327. package/src/debuground.ts +3 -0
  328. package/src/debugroundpi.ts +12 -1
  329. package/src/dispatch.ts +5 -4
  330. package/src/engine/caserunner-sdk.ts +29 -314
  331. package/src/engine/debugpi.ts +17 -5
  332. package/src/execboundary.ts +16 -4
  333. package/src/github.ts +57 -15
  334. package/src/githubtoken.ts +435 -0
  335. package/src/home/adapters.ts +418 -0
  336. package/src/home/advanced.ts +421 -0
  337. package/src/home/advancedtools.ts +96 -0
  338. package/src/home/autoreview.ts +385 -0
  339. package/src/home/daemonstatus.ts +55 -0
  340. package/src/home/devicelogin.ts +107 -0
  341. package/src/home/launch.ts +257 -0
  342. package/src/home/loginowner.ts +248 -0
  343. package/src/home/memory.ts +176 -0
  344. package/src/home/model.ts +965 -0
  345. package/src/home/panel.ts +666 -0
  346. package/src/home/ports.ts +338 -0
  347. package/src/home/review.ts +206 -0
  348. package/src/home/reviews.ts +565 -0
  349. package/src/home/setup.ts +689 -0
  350. package/src/home/setupadapters.ts +217 -0
  351. package/src/home/setupcard.ts +288 -0
  352. package/src/home/setuptools.ts +57 -0
  353. package/src/home/spend.ts +194 -0
  354. package/src/home/switches.ts +293 -0
  355. package/src/home/switchon.ts +554 -0
  356. package/src/home/toolnames.ts +46 -0
  357. package/src/home/tune.ts +405 -0
  358. package/src/home/words.ts +8 -0
  359. package/src/homes.ts +17 -0
  360. package/src/install.ts +1 -1
  361. package/src/ipc/workerspec.ts +15 -0
  362. package/src/live/publish.ts +4 -1
  363. package/src/livecli.ts +6 -1
  364. package/src/loginhint.ts +8 -3
  365. package/src/loginrefresh/handover.ts +138 -0
  366. package/src/loginrefresh/lock.ts +72 -3
  367. package/src/loginrefresh/pirefresh.ts +3 -0
  368. package/src/loginrefresh/refresher.ts +34 -5
  369. package/src/loginrefresh/supervisor.ts +25 -2
  370. package/src/npmverbs.ts +55 -13
  371. package/src/onboard/choices.ts +40 -4
  372. package/src/onboard/engine.ts +70 -421
  373. package/src/onboard/pipin.ts +31 -0
  374. package/src/onboard/stepforward.ts +61 -0
  375. package/src/onboard/steps/appslug.ts +4 -1
  376. package/src/onboard/steps/bringup.ts +3 -2
  377. package/src/onboard/steps/compose.ts +31 -0
  378. package/src/onboard/steps/configfill.ts +144 -40
  379. package/src/onboard/steps/githubaccount.ts +716 -0
  380. package/src/onboard/steps/githubapp.ts +92 -8
  381. package/src/onboard/steps/golive.ts +2 -0
  382. package/src/onboard/steps/index.ts +11 -0
  383. package/src/onboard/steps/ingress.ts +98 -31
  384. package/src/onboard/steps/llmauth.ts +114 -29
  385. package/src/onboard/steps/loginshare.ts +258 -0
  386. package/src/onboard/steps/memory.ts +76 -3
  387. package/src/onboard/steps/proof.ts +473 -55
  388. package/src/onboard/steps/repoconfig.ts +85 -0
  389. package/src/onboard/steps/topology.ts +7 -6
  390. package/src/onboard/steps/transport.ts +15 -0
  391. package/src/onboard/steps/tuiroute.ts +119 -0
  392. package/src/onboard/steps/verifyup.ts +20 -1
  393. package/src/onboard/toolnames.ts +34 -0
  394. package/src/operatorwrite.ts +25 -0
  395. package/src/outcomes.ts +8 -1
  396. package/src/piagentdir.ts +12 -0
  397. package/src/pilogin.ts +32 -16
  398. package/src/posting-journal.ts +6 -1
  399. package/src/posting-outbox.ts +10 -2
  400. package/src/posting-receipt.ts +17 -0
  401. package/src/qcrbin.ts +16 -11
  402. package/src/recheck.ts +2 -1
  403. package/src/reconcile.ts +29 -3
  404. package/src/reviewargs.ts +119 -0
  405. package/src/reviewcli.ts +6 -167
  406. package/src/reviewdetach.ts +107 -13
  407. package/src/reviewhistory.ts +20 -6
  408. package/src/reviewnow.ts +76 -0
  409. package/src/scancli.ts +75 -826
  410. package/src/selfimprove.ts +90 -0
  411. package/src/setup/adapters.ts +13 -221
  412. package/src/setup/caserunner.ts +34 -273
  413. package/src/setup/engine.ts +62 -496
  414. package/src/setup/facts.ts +3 -627
  415. package/src/setup/findings.ts +3 -1264
  416. package/src/setup/presentation.ts +3 -18
  417. package/src/setup/scan.ts +3 -1033
  418. package/src/setup/scanbuild.ts +20 -114
  419. package/src/setup/scanlease.ts +3 -297
  420. package/src/setup/scanreport.ts +3 -373
  421. package/src/setup/scanrun.ts +3 -2268
  422. package/src/setup/scantree.ts +3 -259
  423. package/src/setup/stage2request.ts +3 -347
  424. package/src/setup/unitcut.ts +3 -523
  425. package/src/state.ts +43 -0
  426. package/src/statemachine.ts +4 -1
  427. package/src/takeover.ts +5 -0
  428. package/src/takeoverpi.ts +16 -2
  429. package/src/tui/app.ts +47 -0
  430. package/src/tui/board.ts +111 -14
  431. package/src/tui/cmdbar.ts +4 -1
  432. package/src/tui/companion.ts +80 -11
  433. package/src/tui/connect.ts +7 -1
  434. package/src/tui/deploytab.ts +2 -2
  435. package/src/tui/filters.ts +7 -3
  436. package/src/tui/ghread.ts +74 -7
  437. package/src/tui/index.ts +117 -23
  438. package/src/tui/memforward.ts +52 -3
  439. package/src/tui/memory.ts +3 -15
  440. package/src/tui/memoryurl.ts +22 -0
  441. package/src/tui/model.ts +36 -2
  442. package/src/tui/prpause.ts +11 -2
  443. package/src/tui/remote.ts +9 -1
  444. package/src/tui/route.ts +173 -0
  445. package/src/tui/setupstate.ts +56 -0
  446. package/src/tui/sshtriage.ts +127 -6
  447. package/src/tui/theme.ts +2 -1
  448. package/src/tui/unwedge.ts +2 -14
  449. package/src/verdicts.ts +23 -5
  450. package/src/version.ts +1 -1
  451. package/src/webhook.ts +10 -1
  452. package/src/wire.ts +42 -9
  453. package/src/workerworld.ts +16 -2
  454. package/dist/src/live/projection.js +0 -450
  455. package/dist/src/live/projection.js.map +0 -1
  456. package/dist/src/live/read.js +0 -60
  457. package/dist/src/live/read.js.map +0 -1
  458. package/dist/src/onboard/launch.js +0 -83
  459. package/dist/src/onboard/launch.js.map +0 -1
  460. package/dist/src/setup/ledger.js +0 -207
  461. package/dist/src/setup/ledger.js.map +0 -1
  462. package/dist/src/setup/stage2.js +0 -617
  463. package/dist/src/setup/stage2.js.map +0 -1
  464. package/dist/src/setup/textsniff.js +0 -89
  465. package/dist/src/setup/textsniff.js.map +0 -1
  466. package/src/live/projection.ts +0 -491
  467. package/src/live/read.ts +0 -86
  468. package/src/onboard/launch.ts +0 -115
  469. package/src/setup/ledger.ts +0 -324
  470. package/src/setup/stage2.ts +0 -926
  471. package/src/setup/textsniff.ts +0 -95
@@ -1,627 +1,3 @@
1
- // Deterministic repo facts: what a repo says about itself in files, read
2
- // without a model — the stack, frameworks, CI checks, linters and formatters,
3
- // the design docs and style guides, the agent-directive files, and a
4
- // stack-aware first guess at the unit cut. Free facts must not cost model
5
- // time, and the survey lens and the portrait start from these rather than
6
- // rediscovering them.
7
- //
8
- // Pure: a listing of repo-relative paths plus a reader that answers null for
9
- // anything it cannot read. Nothing here touches a filesystem, so the engine's
10
- // repo port decides what "the repo" is (git-tracked files of the reviewed
11
- // checkout, in production) and tests hand over a map.
12
- //
13
- // Every detection is evidence-carrying: each fact names the file that proves
14
- // it, so the operator can check the facts and the companion can cite them.
15
- // Nothing is inferred from absence — a repo with no CI config has `ci: []`,
16
- // which says "no config file found", not "no CI".
17
- import { parse as parseToml } from "smol-toml";
18
- import { DEFAULT_DIRECTIVE_VOCABULARY } from "@quickchatai/pi-quickreview/src/severity.js";
19
- import { styleGuideRefsIn } from "@quickchatai/pi-quickreview/src/styleguide.js";
20
- import { escapeControls } from "./textsniff.js";
21
-
22
- export type RepoRead = (rel: string) => string | null;
23
-
24
- export interface Evidence {
25
- name: string;
26
- /** Repo-relative path of the file that shows it. */
27
- evidence: string;
28
- }
29
-
30
- export interface LanguageCount {
31
- language: string;
32
- files: number;
33
- }
34
-
35
- export interface CiFact {
36
- system: string;
37
- path: string;
38
- /** The workflow's `name:` when the file states one. */
39
- name: string | null;
40
- }
41
-
42
- /** Every unit kind — the type and the runtime check (`unitcut.ts`) read this
43
- * one list. */
44
- export const UNIT_KINDS = [
45
- "django-app",
46
- "js-package",
47
- "python-package",
48
- "go-module",
49
- "rust-crate",
50
- "directory",
51
- "rest",
52
- "custom",
53
- ] as const;
54
- export type UnitKind = (typeof UNIT_KINDS)[number];
55
-
56
- /** One candidate unit: a name, the path prefixes it owns, and why. */
57
- export interface UnitCandidate {
58
- name: string;
59
- roots: string[];
60
- kind: UnitKind;
61
- reason: string;
62
- }
63
-
64
- export interface RepoFacts {
65
- files: number;
66
- languages: LanguageCount[];
67
- /** Package-manager manifests, as evidence per ecosystem. */
68
- manifests: Evidence[];
69
- frameworks: Evidence[];
70
- ci: CiFact[];
71
- /** Linters, formatters and type checkers whose config is in the repo. */
72
- linters: Evidence[];
73
- /** Design docs read in full by the survey: README, CONTRIBUTING, DESIGN… */
74
- guides: string[];
75
- /** Written style guides, in `findStyleGuide`'s reference grammar. */
76
- style_guides: string[];
77
- /** First-party agent-directive files (AGENTS.md, CLAUDE.md, rules dirs…). */
78
- agent_files: string[];
79
- /** The deterministic first guess at the unit cut — the companion's input. */
80
- unit_candidates: UnitCandidate[];
81
- }
82
-
83
- /** Stable item text shared by facts and portrait renderers. */
84
- export const evidenceText = (item: Evidence): string => `${item.name} — ${item.evidence}`;
85
- export const ciText = (item: CiFact): string =>
86
- `${item.system} — ${item.path}${item.name ? ` ("${item.name}")` : ""}`;
87
- export const displayRoot = (root: string): string => root || "(repo root)";
88
-
89
- // ---- helpers -----------------------------------------------------------------
90
-
91
- function base(path: string): string {
92
- return path.slice(path.lastIndexOf("/") + 1);
93
- }
94
-
95
- function dirOf(path: string): string {
96
- const i = path.lastIndexOf("/");
97
- return i < 0 ? "" : path.slice(0, i);
98
- }
99
-
100
- /** Migration files, whatever the stack calls their directory. */
101
- const MIGRATION_RE = /(^|\/)(migrations?|alembic\/versions|db\/migrate)\//i;
102
-
103
- export function isMigrationFile(path: string): boolean {
104
- return MIGRATION_RE.test(path);
105
- }
106
-
107
- /** Vendored trees are someone else's repo: never evidence about this one. */
108
- export function isVendored(path: string): boolean {
109
- return path
110
- .split("/")
111
- .some((s) => DEFAULT_DIRECTIVE_VOCABULARY.vendoredSegments.has(s.toLowerCase()));
112
- }
113
-
114
- const LANGUAGE_BY_EXT: Record<string, string> = {
115
- py: "Python",
116
- pyi: "Python",
117
- js: "JavaScript",
118
- jsx: "JavaScript",
119
- mjs: "JavaScript",
120
- cjs: "JavaScript",
121
- ts: "TypeScript",
122
- tsx: "TypeScript",
123
- mts: "TypeScript",
124
- cts: "TypeScript",
125
- go: "Go",
126
- rs: "Rust",
127
- rb: "Ruby",
128
- java: "Java",
129
- kt: "Kotlin",
130
- kts: "Kotlin",
131
- scala: "Scala",
132
- php: "PHP",
133
- cs: "C#",
134
- c: "C",
135
- h: "C",
136
- cc: "C++",
137
- cpp: "C++",
138
- cxx: "C++",
139
- hh: "C++",
140
- hxx: "C++",
141
- hpp: "C++",
142
- swift: "Swift",
143
- m: "Objective-C",
144
- mm: "Objective-C",
145
- dart: "Dart",
146
- ex: "Elixir",
147
- exs: "Elixir",
148
- erl: "Erlang",
149
- hs: "Haskell",
150
- clj: "Clojure",
151
- lua: "Lua",
152
- sh: "Shell",
153
- bash: "Shell",
154
- sql: "SQL",
155
- vue: "Vue",
156
- svelte: "Svelte",
157
- html: "HTML",
158
- css: "CSS",
159
- scss: "CSS",
160
- less: "CSS",
161
- tf: "Terraform",
162
- };
163
-
164
- function languages(files: readonly string[]): LanguageCount[] {
165
- const counts = new Map<string, number>();
166
- for (const f of files) {
167
- const b = base(f);
168
- const dot = b.lastIndexOf(".");
169
- if (dot <= 0) continue;
170
- const lang = LANGUAGE_BY_EXT[b.slice(dot + 1).toLowerCase()];
171
- if (lang) counts.set(lang, (counts.get(lang) ?? 0) + 1);
172
- }
173
- return [...counts.entries()]
174
- .map(([language, n]) => ({ language, files: n }))
175
- .sort((a, b) => b.files - a.files || a.language.localeCompare(b.language));
176
- }
177
-
178
- /** Manifest basename → ecosystem. */
179
- const MANIFESTS: Record<string, string> = {
180
- "package.json": "npm",
181
- "pnpm-workspace.yaml": "pnpm workspace",
182
- "pyproject.toml": "python (pyproject)",
183
- "setup.py": "python (setuptools)",
184
- "setup.cfg": "python (setuptools)",
185
- "requirements.txt": "python (pip)",
186
- Pipfile: "python (pipenv)",
187
- "go.mod": "go modules",
188
- "Cargo.toml": "cargo",
189
- Gemfile: "bundler",
190
- "pom.xml": "maven",
191
- "build.gradle": "gradle",
192
- "build.gradle.kts": "gradle",
193
- "composer.json": "composer",
194
- "mix.exs": "mix",
195
- "pubspec.yaml": "pub",
196
- "Package.swift": "swiftpm",
197
- };
198
-
199
- const isRequirementsName = (name: string): boolean =>
200
- /^requirements([-_.][\w.-]*)?\.txt$/.test(name);
201
-
202
- function manifests(files: readonly string[]): Evidence[] {
203
- const out: Evidence[] = [];
204
- for (const f of files) {
205
- const b = base(f);
206
- const eco = MANIFESTS[b] ?? (isRequirementsName(b) ? "python (pip)" : null);
207
- if (eco) out.push({ name: eco, evidence: f });
208
- }
209
- return out;
210
- }
211
-
212
- /** A Python dependency line naming exactly `dep` (any version spec, extras,
213
- * or a Poetry `dep = "…"` table entry) — `flask-cors` is not Flask. */
214
- function pyDep(dep: string): RegExp {
215
- return new RegExp(`^\\s*${dep}(\\[[^\\]]*\\])?\\s*([<>=~!;,"'@]|$)`, "im");
216
- }
217
-
218
- /** Framework → the dependency spelling that proves it, per manifest family. */
219
- const PY_FRAMEWORKS: Array<[string, RegExp]> = [
220
- ["Django", pyDep("django")],
221
- ["Django REST framework", pyDep("djangorestframework")],
222
- ["Flask", pyDep("flask")],
223
- ["FastAPI", pyDep("fastapi")],
224
- ["Celery", pyDep("celery")],
225
- ["SQLAlchemy", pyDep("sqlalchemy")],
226
- ["pytest", pyDep("pytest")],
227
- ["Pydantic", pyDep("pydantic")],
228
- ];
229
-
230
- const JS_FRAMEWORKS: Array<[string, string]> = [
231
- ["React", "react"],
232
- ["Next.js", "next"],
233
- ["Vue", "vue"],
234
- ["Nuxt", "nuxt"],
235
- ["Svelte", "svelte"],
236
- ["SvelteKit", "@sveltejs/kit"],
237
- ["Angular", "@angular/core"],
238
- ["Express", "express"],
239
- ["NestJS", "@nestjs/core"],
240
- ["Fastify", "fastify"],
241
- ["Tailwind CSS", "tailwindcss"],
242
- ["Vite", "vite"],
243
- ["Jest", "jest"],
244
- ["Vitest", "vitest"],
245
- ["Playwright", "@playwright/test"],
246
- ["Electron", "electron"],
247
- ["React Native", "react-native"],
248
- ];
249
-
250
- const SPRING_SIGNATURE = /spring-boot|springframework/;
251
-
252
- const OTHER_FRAMEWORKS: Array<[string, string, RegExp]> = [
253
- ["Rails", "Gemfile", /^\s*gem\s+["']rails["']/m],
254
- ["Spring", "pom.xml", SPRING_SIGNATURE],
255
- ["Spring", "build.gradle", SPRING_SIGNATURE],
256
- ["Spring", "build.gradle.kts", SPRING_SIGNATURE],
257
- ["Laravel", "composer.json", /"laravel\/framework"/],
258
- ["Phoenix", "mix.exs", /:phoenix\b/],
259
- ["Gin", "go.mod", /github\.com\/gin-gonic\/gin/],
260
- ["Echo", "go.mod", /github\.com\/labstack\/echo/],
261
- ["Actix", "Cargo.toml", /^\s*actix-web\s*=/m],
262
- ["Axum", "Cargo.toml", /^\s*axum\s*=/m],
263
- ];
264
-
265
- function packageDeps(text: string): Set<string> {
266
- try {
267
- const pkg = JSON.parse(text) as Record<string, unknown>;
268
- const deps = new Set<string>();
269
- for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
270
- const block = pkg[key];
271
- if (block && typeof block === "object") for (const d of Object.keys(block)) deps.add(d);
272
- }
273
- return deps;
274
- } catch {
275
- return new Set();
276
- }
277
- }
278
-
279
- /** Python dependency declarations only; project metadata is never evidence. */
280
- function pythonDependencies(name: string, text: string): string {
281
- if (isRequirementsName(name)) return text;
282
- if (name === "setup.py") {
283
- const declarations = [
284
- ...text.matchAll(
285
- /\b(install_requires|tests_require|extras_require)\s*=\s*(\[[\s\S]*?\]|\{[\s\S]*?\})/g,
286
- ),
287
- ];
288
- return declarations
289
- .flatMap((m) => {
290
- const lists =
291
- m[1] === "extras_require"
292
- ? [...m[2]!.matchAll(/:\s*(\[[\s\S]*?\])/g)].map((v) => v[1]!)
293
- : [m[2]!];
294
- return lists.flatMap((list) => [...list.matchAll(/["']([^"'\n]+)["']/g)].map((v) => v[1]!));
295
- })
296
- .join("\n");
297
- }
298
- if (name === "setup.cfg") {
299
- let section = "";
300
- let collecting = false;
301
- const out: string[] = [];
302
- for (const line of text.split("\n")) {
303
- const header = /^\s*\[([^\]]+)\]/.exec(line);
304
- if (header) {
305
- section = header[1]!;
306
- collecting = false;
307
- continue;
308
- }
309
- const key = /^([\w.-]+)\s*=\s*(.*)$/.exec(line);
310
- if (key) {
311
- collecting =
312
- section === "options.extras_require" ||
313
- (section === "options" && ["install_requires", "tests_require"].includes(key[1]!));
314
- if (collecting) out.push(key[2]!);
315
- } else if (collecting && /^\s+/.test(line)) out.push(line.trim());
316
- }
317
- return out.join("\n");
318
- }
319
- try {
320
- const table = (v: unknown): Record<string, unknown> =>
321
- v !== null && typeof v === "object" && !Array.isArray(v)
322
- ? (v as Record<string, unknown>)
323
- : {};
324
- const doc = table(parseToml(text));
325
- const out: string[] = [];
326
- const strings = (v: unknown): void => {
327
- if (Array.isArray(v)) for (const item of v) if (typeof item === "string") out.push(item);
328
- };
329
- const tableNames = (v: unknown): void => {
330
- out.push(...Object.keys(table(v)));
331
- };
332
- if (name === "Pipfile") {
333
- tableNames(doc.packages);
334
- tableNames(doc["dev-packages"]);
335
- } else {
336
- const project = table(doc.project);
337
- strings(project.dependencies);
338
- for (const values of Object.values(table(project["optional-dependencies"]))) strings(values);
339
- for (const values of Object.values(table(doc["dependency-groups"]))) strings(values);
340
- const poetry = table(table(doc.tool).poetry);
341
- tableNames(poetry.dependencies);
342
- tableNames(poetry["dev-dependencies"]);
343
- for (const group of Object.values(table(poetry.group))) tableNames(table(group).dependencies);
344
- }
345
- return out.join("\n");
346
- } catch {
347
- return "";
348
- }
349
- }
350
-
351
- function frameworks(files: readonly string[], read: RepoRead): Evidence[] {
352
- const seen = new Map<string, string>();
353
- const note = (name: string, evidence: string): void => {
354
- if (!seen.has(name)) seen.set(name, evidence);
355
- };
356
- for (const f of files) {
357
- const b = base(f);
358
- if (b === "manage.py") {
359
- const text = read(f);
360
- if (text !== null && /django/i.test(text)) note("Django", f);
361
- } else if (
362
- b === "pyproject.toml" ||
363
- b === "Pipfile" ||
364
- b === "setup.py" ||
365
- b === "setup.cfg" ||
366
- isRequirementsName(b)
367
- ) {
368
- const text = read(f);
369
- if (text === null) continue;
370
- const lines = pythonDependencies(b, text);
371
- for (const [name, re] of PY_FRAMEWORKS) if (re.test(lines)) note(name, f);
372
- } else if (b === "package.json") {
373
- const text = read(f);
374
- if (text === null) continue;
375
- const deps = packageDeps(text);
376
- for (const [name, dep] of JS_FRAMEWORKS) if (deps.has(dep)) note(name, f);
377
- } else {
378
- for (const [name, file, re] of OTHER_FRAMEWORKS) {
379
- if (b !== file) continue;
380
- const text = read(f);
381
- if (text !== null && re.test(text)) note(name, f);
382
- }
383
- }
384
- }
385
- return [...seen.entries()].map(([name, evidence]) => ({ name, evidence }));
386
- }
387
-
388
- function ciFacts(files: readonly string[], read: RepoRead): CiFact[] {
389
- const out: CiFact[] = [];
390
- for (const f of files) {
391
- let system: string | null = null;
392
- if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(f)) system = "GitHub Actions";
393
- else if (f === ".gitlab-ci.yml") system = "GitLab CI";
394
- else if (f === ".circleci/config.yml") system = "CircleCI";
395
- else if (f === "Jenkinsfile") system = "Jenkins";
396
- else if (f === "azure-pipelines.yml") system = "Azure Pipelines";
397
- else if (f === ".travis.yml") system = "Travis CI";
398
- else if (f === "bitbucket-pipelines.yml") system = "Bitbucket Pipelines";
399
- else if (/^\.buildkite\/[^/]+\.ya?ml$/.test(f)) system = "Buildkite";
400
- if (!system) continue;
401
- const text = system === "GitHub Actions" ? read(f) : null;
402
- // The name reaches the portrait and the companion's prompt: a control
403
- // byte in the workflow file (a stray NUL is still text) is escaped.
404
- const raw = text === null ? null : (/^name:\s*["']?(.+?)["']?\s*$/m.exec(text)?.[1] ?? null);
405
- const name = raw === null ? null : escapeControls(raw);
406
- out.push({ system, path: f, name });
407
- }
408
- return out;
409
- }
410
-
411
- /** Tool → config basenames that prove it (anywhere in the tree). */
412
- const LINTER_FILES: Array<[string, RegExp]> = [
413
- ["Ruff", /^\.?ruff\.toml$/],
414
- ["Flake8", /^\.flake8$/],
415
- ["Pylint", /^\.?pylintrc$/],
416
- ["mypy", /^\.?mypy\.ini$/],
417
- ["Black", /^\.black$/],
418
- ["isort", /^\.isort\.cfg$/],
419
- ["ESLint", /^(\.eslintrc(\.(js|cjs|json|ya?ml))?|eslint\.config\.(js|mjs|cjs|ts))$/],
420
- ["Prettier", /^(\.prettierrc(\.(js|cjs|json|ya?ml|toml))?|prettier\.config\.(js|mjs|cjs))$/],
421
- ["Biome", /^biome\.jsonc?$/],
422
- ["Stylelint", /^(\.stylelintrc(\.(js|json|ya?ml))?|stylelint\.config\.(js|cjs|mjs))$/],
423
- ["TypeScript compiler", /^tsconfig\.json$/],
424
- ["golangci-lint", /^\.golangci\.ya?ml$/],
425
- ["rustfmt", /^\.?rustfmt\.toml$/],
426
- ["Clippy", /^\.?clippy\.toml$/],
427
- ["RuboCop", /^\.rubocop\.yml$/],
428
- ["EditorConfig", /^\.editorconfig$/],
429
- ["pre-commit", /^\.pre-commit-config\.yaml$/],
430
- ["Checkstyle", /^checkstyle\.xml$/],
431
- ["Spotless", /^\.spotless$/],
432
- ];
433
-
434
- /** `[tool.X]` sections in a pyproject.toml / setup.cfg. */
435
- const PY_TOOL_SECTIONS: Array<[string, RegExp]> = [
436
- ["Ruff", /^\[tool\.ruff[\].]/m],
437
- ["Black", /^\[tool\.black\]/m],
438
- ["isort", /^\[(tool\.)?isort\]/m],
439
- ["mypy", /^\[(tool\.)?mypy[\].-]/m],
440
- ["Pylint", /^\[tool\.pylint[\].]/m],
441
- ["Flake8", /^\[flake8\]/m],
442
- ["pyright", /^\[tool\.pyright\]/m],
443
- ];
444
-
445
- function linters(files: readonly string[], read: RepoRead): Evidence[] {
446
- const seen = new Map<string, string>();
447
- const note = (name: string, evidence: string): void => {
448
- if (!seen.has(name)) seen.set(name, evidence);
449
- };
450
- for (const f of files) {
451
- const b = base(f);
452
- for (const [name, re] of LINTER_FILES) if (re.test(b)) note(name, f);
453
- if (b === "pyproject.toml" || b === "setup.cfg" || b === "tox.ini") {
454
- const text = read(f);
455
- if (text !== null)
456
- for (const [name, re] of PY_TOOL_SECTIONS) if (re.test(text)) note(name, f);
457
- } else if (b === "package.json") {
458
- const text = read(f);
459
- if (text === null) continue;
460
- try {
461
- const pkg = JSON.parse(text) as Record<string, unknown>;
462
- if (pkg.eslintConfig) note("ESLint", f);
463
- if (pkg.prettier) note("Prettier", f);
464
- } catch {
465
- // Not JSON: nothing to read, and no reason to fail the whole facts call.
466
- }
467
- }
468
- }
469
- return [...seen.entries()].map(([name, evidence]) => ({ name, evidence }));
470
- }
471
-
472
- /** Design docs the survey reads in full — the repo's stated intent. */
473
- const GUIDE_RE =
474
- /^(readme|contributing|design|product|architecture|overview|development|hacking|security|conventions|styleguide|style[-_]guide)(\.[a-z]+)?$/i;
475
-
476
- function guides(files: readonly string[]): string[] {
477
- return files
478
- .filter((f) => {
479
- const d = dirOf(f);
480
- return (d === "" || d === "docs" || d === "doc" || d === ".github") && GUIDE_RE.test(base(f));
481
- })
482
- .sort();
483
- }
484
-
485
- /** Agent-directive files: what the repo's team already told agents. Broader
486
- * than the review's `isFirstPartyDirectivePath` on purpose — that predicate
487
- * decides a finding's provenance and keeps its shipped vocabulary; this one is
488
- * a reading list, so it also takes the other tools' rule files. */
489
- const AGENT_BASENAMES = new Set([
490
- "AGENTS.md",
491
- "AGENT.md",
492
- "CLAUDE.md",
493
- "GEMINI.md",
494
- ".cursorrules",
495
- ".windsurfrules",
496
- ".clinerules",
497
- ]);
498
-
499
- export function isAgentFile(path: string): boolean {
500
- if (isVendored(path)) return false;
501
- if (AGENT_BASENAMES.has(base(path))) return true;
502
- if (path === ".github/copilot-instructions.md") return true;
503
- if (/^\.github\/instructions\/.+\.md$/.test(path)) return true;
504
- if (/(^|\/)\.cursor\/rules\//.test(path)) return true;
505
- if (/(^|\/)\.clinerules\//.test(path)) return true;
506
- if (/(^|\/)\.claude\/.+\.md$/.test(path)) return true;
507
- return false;
508
- }
509
-
510
- /** The shallowest vendored directories in the listing (`web/node_modules`,
511
- * `third_party`) — what a proposed cut ignores by default. */
512
- export function vendoredRoots(files: readonly string[]): string[] {
513
- const roots = new Set<string>();
514
- for (const f of files) {
515
- const segs = f.split("/");
516
- const i = segs.findIndex((s) =>
517
- DEFAULT_DIRECTIVE_VOCABULARY.vendoredSegments.has(s.toLowerCase()),
518
- );
519
- if (i >= 0 && i < segs.length - 1) roots.add(segs.slice(0, i + 1).join("/"));
520
- }
521
- return [...roots].filter((r) => ![...roots].some((o) => o !== r && r.startsWith(`${o}/`))).sort();
522
- }
523
-
524
- // ---- unit candidates ---------------------------------------------------------
525
-
526
- /** The longest unit name. */
527
- export const UNIT_NAME_MAX = 64;
528
-
529
- /** A unit name: one plain segment (the state files and reports key on it). */
530
- export const UNIT_NAME_RE = new RegExp(`^[A-Za-z0-9][A-Za-z0-9._-]{0,${UNIT_NAME_MAX - 1}}$`);
531
-
532
- function slug(path: string): string {
533
- const s = path
534
- .replace(/[^A-Za-z0-9._-]+/g, "-")
535
- .replace(/\.{2,}/g, "-")
536
- .replace(/^[-._]+|-+$/g, "");
537
- return (s || "root").slice(0, UNIT_NAME_MAX);
538
- }
539
-
540
- /** Directories never offered as units of their own (they ride "rest"). */
541
- const NON_UNIT_TOP =
542
- /^(\..*|docs?|scripts?|tools?|bin|ci|examples?|fixtures|assets|static|public)$/i;
543
-
544
- /**
545
- * The stack-aware first guess at the unit cut. Markers, most specific first:
546
- * a Django app (`apps.py`), a JS package (a nested `package.json`), a Python
547
- * package with its own `pyproject.toml`/`setup.py`, a Go module, a Rust crate.
548
- * Top-level directories holding code that no marker claimed become
549
- * `directory` units, and a `rest` unit at the repo root catches the remainder
550
- * so the guess is always a complete partition. The companion reshapes this;
551
- * it is evidence, not the answer.
552
- */
553
- export function unitCandidates(files: readonly string[]): UnitCandidate[] {
554
- const out: UnitCandidate[] = [];
555
- const roots = new Set<string>();
556
- const add = (root: string, kind: UnitKind, reason: string): void => {
557
- if (roots.has(root)) return;
558
- roots.add(root);
559
- out.push({ name: "", roots: [root], kind, reason });
560
- };
561
- const sorted = [...files].filter((f) => !isVendored(f)).sort();
562
- for (const f of sorted) {
563
- const b = base(f);
564
- const d = dirOf(f);
565
- if (d === "") continue;
566
- if (b === "apps.py") add(d, "django-app", `${f} declares a Django app`);
567
- else if (b === "package.json") add(d, "js-package", `${f} declares a package`);
568
- else if (b === "pyproject.toml" || b === "setup.py")
569
- add(d, "python-package", `${f} declares a Python package`);
570
- else if (b === "go.mod") add(d, "go-module", `${f} declares a Go module`);
571
- else if (b === "Cargo.toml") add(d, "rust-crate", `${f} declares a crate`);
572
- }
573
- const covered = (f: string): boolean => [...roots].some((r) => f === r || f.startsWith(`${r}/`));
574
- const tops = new Map<string, number>();
575
- for (const f of sorted) {
576
- const i = f.indexOf("/");
577
- if (i < 0 || covered(f) || !/\.[A-Za-z0-9]+$/.test(f)) continue;
578
- const top = f.slice(0, i);
579
- if (NON_UNIT_TOP.test(top)) continue;
580
- const ext = f.slice(f.lastIndexOf(".") + 1).toLowerCase();
581
- if (LANGUAGE_BY_EXT[ext]) tops.set(top, (tops.get(top) ?? 0) + 1);
582
- }
583
- for (const [top, n] of [...tops.entries()].sort()) {
584
- add(
585
- top,
586
- "directory",
587
- `top-level directory with ${n} code file${n === 1 ? "" : "s"} no package marker claims`,
588
- );
589
- }
590
- // Names: the last segment, widened with its parents only where two collide.
591
- const byLast = new Map<string, number>();
592
- for (const c of out) {
593
- const last = base(c.roots[0]!);
594
- byLast.set(last, (byLast.get(last) ?? 0) + 1);
595
- }
596
- const taken = new Set<string>(["rest"]);
597
- for (const c of out) {
598
- const root = c.roots[0]!;
599
- let name = slug(byLast.get(base(root))! > 1 ? root : base(root));
600
- // The suffix must fit UNIT_NAME_MAX too, or the whole candidate cut is
601
- // refused for one long colliding path.
602
- for (let i = 2; taken.has(name); i++)
603
- name = `${slug(root).slice(0, UNIT_NAME_MAX - `-${i}`.length)}-${i}`;
604
- taken.add(name);
605
- c.name = name;
606
- }
607
- out.push({ name: "rest", roots: [""], kind: "rest", reason: "everything no other unit claims" });
608
- return out;
609
- }
610
-
611
- // ---- the whole read ----------------------------------------------------------
612
-
613
- export function repoFacts(files: readonly string[], read: RepoRead): RepoFacts {
614
- const own = files.filter((f) => !isVendored(f));
615
- return {
616
- files: files.length,
617
- languages: languages(own),
618
- manifests: manifests(own),
619
- frameworks: frameworks(own, read),
620
- ci: ciFacts(own, read),
621
- linters: linters(own, read),
622
- guides: guides(own),
623
- style_guides: styleGuideRefsIn(own, read),
624
- agent_files: own.filter(isAgentFile).sort(),
625
- unit_candidates: unitCandidates(own),
626
- };
627
- }
1
+ // Moved to the plugin (packages/pi-plugin/src/scan/facts.ts), the scan engine's
2
+ // home: this name stays for the app's importers.
3
+ export * from "@quickchatai/pi-quickreview/src/scan/facts.js";