@nanonets/graft 0.24.0 → 0.25.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 (791) hide show
  1. package/README.md +26 -21
  2. package/dist/ask/ask.d.ts +8 -0
  3. package/dist/ask/ask.d.ts.map +1 -1
  4. package/dist/ask/ask.js +33 -12
  5. package/dist/ask/ask.js.map +1 -1
  6. package/dist/blast/render.js +1 -1
  7. package/dist/blast/render.js.map +1 -1
  8. package/dist/brand.js +1 -1
  9. package/dist/brand.js.map +1 -1
  10. package/dist/check/learning-edits.d.ts +18 -3
  11. package/dist/check/learning-edits.d.ts.map +1 -1
  12. package/dist/check/learning-edits.js +54 -10
  13. package/dist/check/learning-edits.js.map +1 -1
  14. package/dist/check/local-check.d.ts +7 -3
  15. package/dist/check/local-check.d.ts.map +1 -1
  16. package/dist/check/local-check.js +55 -11
  17. package/dist/check/local-check.js.map +1 -1
  18. package/dist/check/types.d.ts +11 -2
  19. package/dist/check/types.d.ts.map +1 -1
  20. package/dist/claude/format.d.ts +11 -7
  21. package/dist/claude/format.d.ts.map +1 -1
  22. package/dist/claude/format.js +50 -28
  23. package/dist/claude/format.js.map +1 -1
  24. package/dist/claude/hook-io.d.ts +44 -0
  25. package/dist/claude/hook-io.d.ts.map +1 -0
  26. package/dist/claude/hook-io.js +144 -0
  27. package/dist/claude/hook-io.js.map +1 -0
  28. package/dist/claude/hook-layers.d.ts +4 -2
  29. package/dist/claude/hook-layers.d.ts.map +1 -1
  30. package/dist/claude/hook-layers.js +21 -4
  31. package/dist/claude/hook-layers.js.map +1 -1
  32. package/dist/claude/hooks.d.ts +9 -32
  33. package/dist/claude/hooks.d.ts.map +1 -1
  34. package/dist/claude/hooks.js +154 -350
  35. package/dist/claude/hooks.js.map +1 -1
  36. package/dist/claude/init.d.ts.map +1 -1
  37. package/dist/claude/init.js +56 -18
  38. package/dist/claude/init.js.map +1 -1
  39. package/dist/claude/note-signals.d.ts +6 -0
  40. package/dist/claude/note-signals.d.ts.map +1 -1
  41. package/dist/claude/note-signals.js +14 -3
  42. package/dist/claude/note-signals.js.map +1 -1
  43. package/dist/claude/post-bash.d.ts +26 -0
  44. package/dist/claude/post-bash.d.ts.map +1 -0
  45. package/dist/claude/post-bash.js +191 -0
  46. package/dist/claude/post-bash.js.map +1 -0
  47. package/dist/claude/pre-bash-skills.d.ts +21 -0
  48. package/dist/claude/pre-bash-skills.d.ts.map +1 -0
  49. package/dist/claude/pre-bash-skills.js +253 -0
  50. package/dist/claude/pre-bash-skills.js.map +1 -0
  51. package/dist/claude/pre-bash.d.ts +50 -0
  52. package/dist/claude/pre-bash.d.ts.map +1 -0
  53. package/dist/claude/pre-bash.js +245 -0
  54. package/dist/claude/pre-bash.js.map +1 -0
  55. package/dist/claude/prompt-consent.d.ts +19 -0
  56. package/dist/claude/prompt-consent.d.ts.map +1 -0
  57. package/dist/claude/prompt-consent.js +14 -0
  58. package/dist/claude/prompt-consent.js.map +1 -0
  59. package/dist/claude/prompt-hook.d.ts +76 -0
  60. package/dist/claude/prompt-hook.d.ts.map +1 -0
  61. package/dist/claude/prompt-hook.js +303 -0
  62. package/dist/claude/prompt-hook.js.map +1 -0
  63. package/dist/claude/prompt-skills.d.ts +19 -0
  64. package/dist/claude/prompt-skills.d.ts.map +1 -0
  65. package/dist/claude/prompt-skills.js +305 -0
  66. package/dist/claude/prompt-skills.js.map +1 -0
  67. package/dist/claude/session-metrics.js +4 -4
  68. package/dist/claude/session-metrics.js.map +1 -1
  69. package/dist/claude/settings-merge.d.ts +10 -0
  70. package/dist/claude/settings-merge.d.ts.map +1 -1
  71. package/dist/claude/settings-merge.js +46 -17
  72. package/dist/claude/settings-merge.js.map +1 -1
  73. package/dist/claude/shim-template.d.ts.map +1 -1
  74. package/dist/claude/shim-template.js +86 -28
  75. package/dist/claude/shim-template.js.map +1 -1
  76. package/dist/claude/skill-template.d.ts +2 -0
  77. package/dist/claude/skill-template.d.ts.map +1 -1
  78. package/dist/claude/skill-template.js +53 -34
  79. package/dist/claude/skill-template.js.map +1 -1
  80. package/dist/claude/skill-use.d.ts +16 -0
  81. package/dist/claude/skill-use.d.ts.map +1 -0
  82. package/dist/claude/skill-use.js +78 -0
  83. package/dist/claude/skill-use.js.map +1 -0
  84. package/dist/claude/skills-hook-util.d.ts +25 -0
  85. package/dist/claude/skills-hook-util.d.ts.map +1 -0
  86. package/dist/claude/skills-hook-util.js +71 -0
  87. package/dist/claude/skills-hook-util.js.map +1 -0
  88. package/dist/claude/skills-wiring.d.ts +26 -0
  89. package/dist/claude/skills-wiring.d.ts.map +1 -0
  90. package/dist/claude/skills-wiring.js +110 -0
  91. package/dist/claude/skills-wiring.js.map +1 -0
  92. package/dist/claude/start-init.d.ts +13 -0
  93. package/dist/claude/start-init.d.ts.map +1 -0
  94. package/dist/claude/start-init.js +75 -0
  95. package/dist/claude/start-init.js.map +1 -0
  96. package/dist/claude/start-skills.d.ts +31 -0
  97. package/dist/claude/start-skills.d.ts.map +1 -0
  98. package/dist/claude/start-skills.js +274 -0
  99. package/dist/claude/start-skills.js.map +1 -0
  100. package/dist/claude/state.d.ts +19 -7
  101. package/dist/claude/state.d.ts.map +1 -1
  102. package/dist/claude/state.js +59 -4
  103. package/dist/claude/state.js.map +1 -1
  104. package/dist/claude/stop-skills.d.ts +23 -0
  105. package/dist/claude/stop-skills.d.ts.map +1 -0
  106. package/dist/claude/stop-skills.js +233 -0
  107. package/dist/claude/stop-skills.js.map +1 -0
  108. package/dist/claude/stop.d.ts +23 -0
  109. package/dist/claude/stop.d.ts.map +1 -0
  110. package/dist/claude/stop.js +219 -0
  111. package/dist/claude/stop.js.map +1 -0
  112. package/dist/claude/tally.d.ts +29 -41
  113. package/dist/claude/tally.d.ts.map +1 -1
  114. package/dist/claude/tally.js +111 -165
  115. package/dist/claude/tally.js.map +1 -1
  116. package/dist/claude/walls-hook.d.ts +52 -0
  117. package/dist/claude/walls-hook.d.ts.map +1 -0
  118. package/dist/claude/walls-hook.js +279 -0
  119. package/dist/claude/walls-hook.js.map +1 -0
  120. package/dist/cli-epilogue.d.ts.map +1 -1
  121. package/dist/cli-epilogue.js +24 -14
  122. package/dist/cli-epilogue.js.map +1 -1
  123. package/dist/cli-meta.js +3 -3
  124. package/dist/cli-meta.js.map +1 -1
  125. package/dist/cli-picker.d.ts +24 -0
  126. package/dist/cli-picker.d.ts.map +1 -1
  127. package/dist/cli-picker.js +44 -11
  128. package/dist/cli-picker.js.map +1 -1
  129. package/dist/cli.d.ts.map +1 -1
  130. package/dist/cli.js +475 -390
  131. package/dist/cli.js.map +1 -1
  132. package/dist/cloud/head-learnings.d.ts +13 -0
  133. package/dist/cloud/head-learnings.d.ts.map +1 -0
  134. package/dist/cloud/head-learnings.js +84 -0
  135. package/dist/cloud/head-learnings.js.map +1 -0
  136. package/dist/cloud/lookup-activity.d.ts +18 -0
  137. package/dist/cloud/lookup-activity.d.ts.map +1 -0
  138. package/dist/cloud/lookup-activity.js +56 -0
  139. package/dist/cloud/lookup-activity.js.map +1 -0
  140. package/dist/cloud/nudge.d.ts +15 -7
  141. package/dist/cloud/nudge.d.ts.map +1 -1
  142. package/dist/cloud/nudge.js +45 -33
  143. package/dist/cloud/nudge.js.map +1 -1
  144. package/dist/cloud/onboarding.d.ts +273 -0
  145. package/dist/cloud/onboarding.d.ts.map +1 -0
  146. package/dist/cloud/onboarding.js +612 -0
  147. package/dist/cloud/onboarding.js.map +1 -0
  148. package/dist/cloud/other-repos.d.ts +39 -0
  149. package/dist/cloud/other-repos.d.ts.map +1 -0
  150. package/dist/cloud/other-repos.js +137 -0
  151. package/dist/cloud/other-repos.js.map +1 -0
  152. package/dist/cloud/pair.d.ts +86 -0
  153. package/dist/cloud/pair.d.ts.map +1 -0
  154. package/dist/cloud/pair.js +270 -0
  155. package/dist/cloud/pair.js.map +1 -0
  156. package/dist/cloud/workspace-sync.d.ts +75 -11
  157. package/dist/cloud/workspace-sync.d.ts.map +1 -1
  158. package/dist/cloud/workspace-sync.js +409 -53
  159. package/dist/cloud/workspace-sync.js.map +1 -1
  160. package/dist/cloud/workspace.d.ts +95 -10
  161. package/dist/cloud/workspace.d.ts.map +1 -1
  162. package/dist/cloud/workspace.js +112 -9
  163. package/dist/cloud/workspace.js.map +1 -1
  164. package/dist/graph/native.d.ts +2 -0
  165. package/dist/graph/native.d.ts.map +1 -1
  166. package/dist/graph/native.js +29 -4
  167. package/dist/graph/native.js.map +1 -1
  168. package/dist/hosts/claude-global.d.ts +14 -0
  169. package/dist/hosts/claude-global.d.ts.map +1 -1
  170. package/dist/hosts/claude-global.js +39 -4
  171. package/dist/hosts/claude-global.js.map +1 -1
  172. package/dist/hosts/codex-hooks.d.ts +6 -0
  173. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  174. package/dist/hosts/codex-hooks.js +89 -16
  175. package/dist/hosts/codex-hooks.js.map +1 -1
  176. package/dist/hosts/codex-trust.d.ts +28 -0
  177. package/dist/hosts/codex-trust.d.ts.map +1 -0
  178. package/dist/hosts/codex-trust.js +280 -0
  179. package/dist/hosts/codex-trust.js.map +1 -0
  180. package/dist/hosts/cursor-hooks.d.ts.map +1 -1
  181. package/dist/hosts/cursor-hooks.js +3 -1
  182. package/dist/hosts/cursor-hooks.js.map +1 -1
  183. package/dist/init/candidates.d.ts +60 -0
  184. package/dist/init/candidates.d.ts.map +1 -0
  185. package/dist/init/candidates.js +202 -0
  186. package/dist/init/candidates.js.map +1 -0
  187. package/dist/init/claude-call.d.ts +51 -0
  188. package/dist/init/claude-call.d.ts.map +1 -0
  189. package/dist/init/claude-call.js +199 -0
  190. package/dist/init/claude-call.js.map +1 -0
  191. package/dist/init/discover-worker.d.ts +18 -0
  192. package/dist/init/discover-worker.d.ts.map +1 -0
  193. package/dist/init/discover-worker.js +111 -0
  194. package/dist/init/discover-worker.js.map +1 -0
  195. package/dist/init/handover.d.ts +16 -0
  196. package/dist/init/handover.d.ts.map +1 -0
  197. package/dist/init/handover.js +92 -0
  198. package/dist/init/handover.js.map +1 -0
  199. package/dist/init/howtos.d.ts +20 -0
  200. package/dist/init/howtos.d.ts.map +1 -0
  201. package/dist/init/howtos.js +90 -0
  202. package/dist/init/howtos.js.map +1 -0
  203. package/dist/init/init-record.d.ts +134 -0
  204. package/dist/init/init-record.d.ts.map +1 -0
  205. package/dist/init/init-record.js +339 -0
  206. package/dist/init/init-record.js.map +1 -0
  207. package/dist/init/install-paths.d.ts +6 -0
  208. package/dist/init/install-paths.d.ts.map +1 -0
  209. package/dist/init/install-paths.js +87 -0
  210. package/dist/init/install-paths.js.map +1 -0
  211. package/dist/init/install-record.d.ts +21 -0
  212. package/dist/init/install-record.d.ts.map +1 -0
  213. package/dist/init/install-record.js +144 -0
  214. package/dist/init/install-record.js.map +1 -0
  215. package/dist/init/keep-answer.d.ts +43 -0
  216. package/dist/init/keep-answer.d.ts.map +1 -0
  217. package/dist/init/keep-answer.js +118 -0
  218. package/dist/init/keep-answer.js.map +1 -0
  219. package/dist/init/keep-gate.d.ts +30 -0
  220. package/dist/init/keep-gate.d.ts.map +1 -0
  221. package/dist/init/keep-gate.js +178 -0
  222. package/dist/init/keep-gate.js.map +1 -0
  223. package/dist/init/keep-list.d.ts +67 -0
  224. package/dist/init/keep-list.d.ts.map +1 -0
  225. package/dist/init/keep-list.js +232 -0
  226. package/dist/init/keep-list.js.map +1 -0
  227. package/dist/init/keep.d.ts +52 -0
  228. package/dist/init/keep.d.ts.map +1 -0
  229. package/dist/init/keep.js +393 -0
  230. package/dist/init/keep.js.map +1 -0
  231. package/dist/init/look-back-mine.d.ts +37 -0
  232. package/dist/init/look-back-mine.d.ts.map +1 -0
  233. package/dist/init/look-back-mine.js +215 -0
  234. package/dist/init/look-back-mine.js.map +1 -0
  235. package/dist/init/look-back.d.ts +81 -0
  236. package/dist/init/look-back.d.ts.map +1 -0
  237. package/dist/init/look-back.js +170 -0
  238. package/dist/init/look-back.js.map +1 -0
  239. package/dist/init/miners.d.ts +46 -0
  240. package/dist/init/miners.d.ts.map +1 -0
  241. package/dist/init/miners.js +141 -0
  242. package/dist/init/miners.js.map +1 -0
  243. package/dist/init/name-call.d.ts +37 -0
  244. package/dist/init/name-call.d.ts.map +1 -0
  245. package/dist/init/name-call.js +151 -0
  246. package/dist/init/name-call.js.map +1 -0
  247. package/dist/init/plain-file.d.ts +11 -0
  248. package/dist/init/plain-file.d.ts.map +1 -0
  249. package/dist/init/plain-file.js +83 -0
  250. package/dist/init/plain-file.js.map +1 -0
  251. package/dist/init/prefs.d.ts +70 -0
  252. package/dist/init/prefs.d.ts.map +1 -0
  253. package/dist/init/prefs.js +282 -0
  254. package/dist/init/prefs.js.map +1 -0
  255. package/dist/init/relay.d.ts +15 -0
  256. package/dist/init/relay.d.ts.map +1 -0
  257. package/dist/init/relay.js +15 -0
  258. package/dist/init/relay.js.map +1 -0
  259. package/dist/init/replay-runs.d.ts +121 -0
  260. package/dist/init/replay-runs.d.ts.map +1 -0
  261. package/dist/init/replay-runs.js +457 -0
  262. package/dist/init/replay-runs.js.map +1 -0
  263. package/dist/init/replay-tamper.d.ts +18 -0
  264. package/dist/init/replay-tamper.d.ts.map +1 -0
  265. package/dist/init/replay-tamper.js +87 -0
  266. package/dist/init/replay-tamper.js.map +1 -0
  267. package/dist/init/review-screen.d.ts +74 -0
  268. package/dist/init/review-screen.d.ts.map +1 -0
  269. package/dist/init/review-screen.js +258 -0
  270. package/dist/init/review-screen.js.map +1 -0
  271. package/dist/init/review-tty.d.ts +29 -0
  272. package/dist/init/review-tty.d.ts.map +1 -0
  273. package/dist/init/review-tty.js +218 -0
  274. package/dist/init/review-tty.js.map +1 -0
  275. package/dist/init/review.d.ts +12 -0
  276. package/dist/init/review.d.ts.map +1 -0
  277. package/dist/init/review.js +12 -0
  278. package/dist/init/review.js.map +1 -0
  279. package/dist/init/run.d.ts +87 -0
  280. package/dist/init/run.d.ts.map +1 -0
  281. package/dist/init/run.js +347 -0
  282. package/dist/init/run.js.map +1 -0
  283. package/dist/init/sessions.d.ts +63 -0
  284. package/dist/init/sessions.d.ts.map +1 -0
  285. package/dist/init/sessions.js +377 -0
  286. package/dist/init/sessions.js.map +1 -0
  287. package/dist/init/transcript.d.ts +81 -0
  288. package/dist/init/transcript.d.ts.map +1 -0
  289. package/dist/init/transcript.js +216 -0
  290. package/dist/init/transcript.js.map +1 -0
  291. package/dist/init/types.d.ts +139 -0
  292. package/dist/init/types.d.ts.map +1 -0
  293. package/dist/init/types.js +2 -0
  294. package/dist/init/types.js.map +1 -0
  295. package/dist/mcp/brains.d.ts +72 -0
  296. package/dist/mcp/brains.d.ts.map +1 -0
  297. package/dist/mcp/brains.js +220 -0
  298. package/dist/mcp/brains.js.map +1 -0
  299. package/dist/mcp/server.d.ts +3 -1
  300. package/dist/mcp/server.d.ts.map +1 -1
  301. package/dist/mcp/server.js +40 -5
  302. package/dist/mcp/server.js.map +1 -1
  303. package/dist/mcp/tools.d.ts.map +1 -1
  304. package/dist/mcp/tools.js +7 -2
  305. package/dist/mcp/tools.js.map +1 -1
  306. package/dist/notes/cold-start.d.ts +5 -184
  307. package/dist/notes/cold-start.d.ts.map +1 -1
  308. package/dist/notes/cold-start.js +5 -575
  309. package/dist/notes/cold-start.js.map +1 -1
  310. package/dist/notes/edit-overlap.d.ts.map +1 -1
  311. package/dist/notes/edit-overlap.js +4 -1
  312. package/dist/notes/edit-overlap.js.map +1 -1
  313. package/dist/notes/every-repo.d.ts +18 -0
  314. package/dist/notes/every-repo.d.ts.map +1 -0
  315. package/dist/notes/every-repo.js +86 -0
  316. package/dist/notes/every-repo.js.map +1 -0
  317. package/dist/notes/guardrails.d.ts +8 -0
  318. package/dist/notes/guardrails.d.ts.map +1 -0
  319. package/dist/notes/guardrails.js +38 -0
  320. package/dist/notes/guardrails.js.map +1 -0
  321. package/dist/notes/lesson-index.d.ts +13 -0
  322. package/dist/notes/lesson-index.d.ts.map +1 -0
  323. package/dist/notes/lesson-index.js +135 -0
  324. package/dist/notes/lesson-index.js.map +1 -0
  325. package/dist/notes/linked-learnings.d.ts +8 -0
  326. package/dist/notes/linked-learnings.d.ts.map +1 -0
  327. package/dist/notes/linked-learnings.js +113 -0
  328. package/dist/notes/linked-learnings.js.map +1 -0
  329. package/dist/notes/notes.d.ts +8 -4
  330. package/dist/notes/notes.d.ts.map +1 -1
  331. package/dist/notes/notes.js +9 -7
  332. package/dist/notes/notes.js.map +1 -1
  333. package/dist/notes/pr-reviews.d.ts +4 -0
  334. package/dist/notes/pr-reviews.d.ts.map +1 -1
  335. package/dist/notes/pr-reviews.js +6 -5
  336. package/dist/notes/pr-reviews.js.map +1 -1
  337. package/dist/notes/replace.d.ts +14 -0
  338. package/dist/notes/replace.d.ts.map +1 -0
  339. package/dist/notes/replace.js +38 -0
  340. package/dist/notes/replace.js.map +1 -0
  341. package/dist/notes/repo-notes-dir.d.ts +2 -0
  342. package/dist/notes/repo-notes-dir.d.ts.map +1 -0
  343. package/dist/notes/repo-notes-dir.js +16 -0
  344. package/dist/notes/repo-notes-dir.js.map +1 -0
  345. package/dist/notes/repo-trail.d.ts +1 -1
  346. package/dist/notes/repo-trail.d.ts.map +1 -1
  347. package/dist/notes/repo-trail.js +24 -4
  348. package/dist/notes/repo-trail.js.map +1 -1
  349. package/dist/search/grep.d.ts.map +1 -1
  350. package/dist/search/grep.js +4 -2
  351. package/dist/search/grep.js.map +1 -1
  352. package/dist/search/user-regexp.d.ts +26 -0
  353. package/dist/search/user-regexp.d.ts.map +1 -0
  354. package/dist/search/user-regexp.js +150 -0
  355. package/dist/search/user-regexp.js.map +1 -0
  356. package/dist/skills/anchors-cli.d.ts +38 -0
  357. package/dist/skills/anchors-cli.d.ts.map +1 -0
  358. package/dist/skills/anchors-cli.js +279 -0
  359. package/dist/skills/anchors-cli.js.map +1 -0
  360. package/dist/skills/anchors-git.d.ts +29 -0
  361. package/dist/skills/anchors-git.d.ts.map +1 -0
  362. package/dist/skills/anchors-git.js +241 -0
  363. package/dist/skills/anchors-git.js.map +1 -0
  364. package/dist/skills/anchors-wide.d.ts +25 -0
  365. package/dist/skills/anchors-wide.d.ts.map +1 -0
  366. package/dist/skills/anchors-wide.js +481 -0
  367. package/dist/skills/anchors-wide.js.map +1 -0
  368. package/dist/skills/anchors.d.ts +32 -0
  369. package/dist/skills/anchors.d.ts.map +1 -0
  370. package/dist/skills/anchors.js +485 -0
  371. package/dist/skills/anchors.js.map +1 -0
  372. package/dist/skills/check-result.d.ts +26 -0
  373. package/dist/skills/check-result.d.ts.map +1 -0
  374. package/dist/skills/check-result.js +46 -0
  375. package/dist/skills/check-result.js.map +1 -0
  376. package/dist/skills/cli-skills-1b.d.ts +17 -0
  377. package/dist/skills/cli-skills-1b.d.ts.map +1 -0
  378. package/dist/skills/cli-skills-1b.js +166 -0
  379. package/dist/skills/cli-skills-1b.js.map +1 -0
  380. package/dist/skills/cli-skills-2.d.ts +35 -0
  381. package/dist/skills/cli-skills-2.d.ts.map +1 -0
  382. package/dist/skills/cli-skills-2.js +129 -0
  383. package/dist/skills/cli-skills-2.js.map +1 -0
  384. package/dist/skills/cli-skills-report-1b.d.ts +49 -0
  385. package/dist/skills/cli-skills-report-1b.d.ts.map +1 -0
  386. package/dist/skills/cli-skills-report-1b.js +191 -0
  387. package/dist/skills/cli-skills-report-1b.js.map +1 -0
  388. package/dist/skills/cli-skills-report.d.ts +86 -0
  389. package/dist/skills/cli-skills-report.d.ts.map +1 -0
  390. package/dist/skills/cli-skills-report.js +121 -0
  391. package/dist/skills/cli-skills-report.js.map +1 -0
  392. package/dist/skills/cli-skills.d.ts +30 -0
  393. package/dist/skills/cli-skills.d.ts.map +1 -0
  394. package/dist/skills/cli-skills.js +379 -0
  395. package/dist/skills/cli-skills.js.map +1 -0
  396. package/dist/skills/codex-sandbox.d.ts +6 -0
  397. package/dist/skills/codex-sandbox.d.ts.map +1 -0
  398. package/dist/skills/codex-sandbox.js +55 -0
  399. package/dist/skills/codex-sandbox.js.map +1 -0
  400. package/dist/skills/compile-split.d.ts +57 -0
  401. package/dist/skills/compile-split.d.ts.map +1 -0
  402. package/dist/skills/compile-split.js +550 -0
  403. package/dist/skills/compile-split.js.map +1 -0
  404. package/dist/skills/compile-version.d.ts +37 -0
  405. package/dist/skills/compile-version.d.ts.map +1 -0
  406. package/dist/skills/compile-version.js +56 -0
  407. package/dist/skills/compile-version.js.map +1 -0
  408. package/dist/skills/compile-waits.d.ts +12 -0
  409. package/dist/skills/compile-waits.d.ts.map +1 -0
  410. package/dist/skills/compile-waits.js +52 -0
  411. package/dist/skills/compile-waits.js.map +1 -0
  412. package/dist/skills/compile-words.d.ts +85 -0
  413. package/dist/skills/compile-words.d.ts.map +1 -0
  414. package/dist/skills/compile-words.js +445 -0
  415. package/dist/skills/compile-words.js.map +1 -0
  416. package/dist/skills/compile.d.ts +42 -0
  417. package/dist/skills/compile.d.ts.map +1 -0
  418. package/dist/skills/compile.js +505 -0
  419. package/dist/skills/compile.js.map +1 -0
  420. package/dist/skills/credit-facts.d.ts +8 -0
  421. package/dist/skills/credit-facts.d.ts.map +1 -0
  422. package/dist/skills/credit-facts.js +32 -0
  423. package/dist/skills/credit-facts.js.map +1 -0
  424. package/dist/skills/credit.d.ts +86 -0
  425. package/dist/skills/credit.d.ts.map +1 -0
  426. package/dist/skills/credit.js +259 -0
  427. package/dist/skills/credit.js.map +1 -0
  428. package/dist/skills/directive-words.d.ts +11 -0
  429. package/dist/skills/directive-words.d.ts.map +1 -0
  430. package/dist/skills/directive-words.js +33 -0
  431. package/dist/skills/directive-words.js.map +1 -0
  432. package/dist/skills/fidelity.d.ts +42 -0
  433. package/dist/skills/fidelity.d.ts.map +1 -0
  434. package/dist/skills/fidelity.js +138 -0
  435. package/dist/skills/fidelity.js.map +1 -0
  436. package/dist/skills/fold-gated.d.ts +3 -0
  437. package/dist/skills/fold-gated.d.ts.map +1 -0
  438. package/dist/skills/fold-gated.js +78 -0
  439. package/dist/skills/fold-gated.js.map +1 -0
  440. package/dist/skills/fold.d.ts +87 -0
  441. package/dist/skills/fold.d.ts.map +1 -0
  442. package/dist/skills/fold.js +348 -0
  443. package/dist/skills/fold.js.map +1 -0
  444. package/dist/skills/follow-learnings.d.ts +128 -0
  445. package/dist/skills/follow-learnings.d.ts.map +1 -0
  446. package/dist/skills/follow-learnings.js +278 -0
  447. package/dist/skills/follow-learnings.js.map +1 -0
  448. package/dist/skills/forget-notice.d.ts +44 -0
  449. package/dist/skills/forget-notice.d.ts.map +1 -0
  450. package/dist/skills/forget-notice.js +131 -0
  451. package/dist/skills/forget-notice.js.map +1 -0
  452. package/dist/skills/front-version.d.ts +12 -0
  453. package/dist/skills/front-version.d.ts.map +1 -0
  454. package/dist/skills/front-version.js +87 -0
  455. package/dist/skills/front-version.js.map +1 -0
  456. package/dist/skills/host-facts.d.ts +51 -0
  457. package/dist/skills/host-facts.d.ts.map +1 -0
  458. package/dist/skills/host-facts.js +134 -0
  459. package/dist/skills/host-facts.js.map +1 -0
  460. package/dist/skills/learning-steps.d.ts +43 -0
  461. package/dist/skills/learning-steps.d.ts.map +1 -0
  462. package/dist/skills/learning-steps.js +294 -0
  463. package/dist/skills/learning-steps.js.map +1 -0
  464. package/dist/skills/ledger.d.ts +43 -0
  465. package/dist/skills/ledger.d.ts.map +1 -0
  466. package/dist/skills/ledger.js +336 -0
  467. package/dist/skills/ledger.js.map +1 -0
  468. package/dist/skills/lesson-gate.d.ts +25 -0
  469. package/dist/skills/lesson-gate.d.ts.map +1 -0
  470. package/dist/skills/lesson-gate.js +316 -0
  471. package/dist/skills/lesson-gate.js.map +1 -0
  472. package/dist/skills/lessons.d.ts +60 -0
  473. package/dist/skills/lessons.d.ts.map +1 -0
  474. package/dist/skills/lessons.js +396 -0
  475. package/dist/skills/lessons.js.map +1 -0
  476. package/dist/skills/offer-answers.d.ts +20 -0
  477. package/dist/skills/offer-answers.d.ts.map +1 -0
  478. package/dist/skills/offer-answers.js +51 -0
  479. package/dist/skills/offer-answers.js.map +1 -0
  480. package/dist/skills/offer-drift.d.ts +35 -0
  481. package/dist/skills/offer-drift.d.ts.map +1 -0
  482. package/dist/skills/offer-drift.js +98 -0
  483. package/dist/skills/offer-drift.js.map +1 -0
  484. package/dist/skills/offer-lesson.d.ts +31 -0
  485. package/dist/skills/offer-lesson.d.ts.map +1 -0
  486. package/dist/skills/offer-lesson.js +123 -0
  487. package/dist/skills/offer-lesson.js.map +1 -0
  488. package/dist/skills/offer-lines.d.ts +40 -0
  489. package/dist/skills/offer-lines.d.ts.map +1 -0
  490. package/dist/skills/offer-lines.js +80 -0
  491. package/dist/skills/offer-lines.js.map +1 -0
  492. package/dist/skills/offer-moment.d.ts +102 -0
  493. package/dist/skills/offer-moment.d.ts.map +1 -0
  494. package/dist/skills/offer-moment.js +260 -0
  495. package/dist/skills/offer-moment.js.map +1 -0
  496. package/dist/skills/offer-repair.d.ts +36 -0
  497. package/dist/skills/offer-repair.d.ts.map +1 -0
  498. package/dist/skills/offer-repair.js +212 -0
  499. package/dist/skills/offer-repair.js.map +1 -0
  500. package/dist/skills/offer-retire.d.ts +53 -0
  501. package/dist/skills/offer-retire.d.ts.map +1 -0
  502. package/dist/skills/offer-retire.js +149 -0
  503. package/dist/skills/offer-retire.js.map +1 -0
  504. package/dist/skills/offer-update.d.ts +54 -0
  505. package/dist/skills/offer-update.d.ts.map +1 -0
  506. package/dist/skills/offer-update.js +115 -0
  507. package/dist/skills/offer-update.js.map +1 -0
  508. package/dist/skills/offer-words-1b.d.ts +32 -0
  509. package/dist/skills/offer-words-1b.d.ts.map +1 -0
  510. package/dist/skills/offer-words-1b.js +80 -0
  511. package/dist/skills/offer-words-1b.js.map +1 -0
  512. package/dist/skills/offer-words-2.d.ts +123 -0
  513. package/dist/skills/offer-words-2.d.ts.map +1 -0
  514. package/dist/skills/offer-words-2.js +194 -0
  515. package/dist/skills/offer-words-2.js.map +1 -0
  516. package/dist/skills/offer-words.d.ts +65 -0
  517. package/dist/skills/offer-words.d.ts.map +1 -0
  518. package/dist/skills/offer-words.js +205 -0
  519. package/dist/skills/offer-words.js.map +1 -0
  520. package/dist/skills/offers.d.ts +66 -0
  521. package/dist/skills/offers.d.ts.map +1 -0
  522. package/dist/skills/offers.js +284 -0
  523. package/dist/skills/offers.js.map +1 -0
  524. package/dist/skills/own-folder.d.ts +9 -0
  525. package/dist/skills/own-folder.d.ts.map +1 -0
  526. package/dist/skills/own-folder.js +36 -0
  527. package/dist/skills/own-folder.js.map +1 -0
  528. package/dist/skills/person.d.ts +34 -0
  529. package/dist/skills/person.d.ts.map +1 -0
  530. package/dist/skills/person.js +102 -0
  531. package/dist/skills/person.js.map +1 -0
  532. package/dist/skills/probe-clean.d.ts +9 -0
  533. package/dist/skills/probe-clean.d.ts.map +1 -0
  534. package/dist/skills/probe-clean.js +48 -0
  535. package/dist/skills/probe-clean.js.map +1 -0
  536. package/dist/skills/probes.d.ts +26 -0
  537. package/dist/skills/probes.d.ts.map +1 -0
  538. package/dist/skills/probes.js +225 -0
  539. package/dist/skills/probes.js.map +1 -0
  540. package/dist/skills/recall.d.ts +119 -0
  541. package/dist/skills/recall.d.ts.map +1 -0
  542. package/dist/skills/recall.js +370 -0
  543. package/dist/skills/recall.js.map +1 -0
  544. package/dist/skills/receipts.d.ts +44 -0
  545. package/dist/skills/receipts.d.ts.map +1 -0
  546. package/dist/skills/receipts.js +223 -0
  547. package/dist/skills/receipts.js.map +1 -0
  548. package/dist/skills/run-checkout.d.ts +4 -0
  549. package/dist/skills/run-checkout.d.ts.map +1 -0
  550. package/dist/skills/run-checkout.js +37 -0
  551. package/dist/skills/run-checkout.js.map +1 -0
  552. package/dist/skills/runblock.d.ts +47 -0
  553. package/dist/skills/runblock.d.ts.map +1 -0
  554. package/dist/skills/runblock.js +227 -0
  555. package/dist/skills/runblock.js.map +1 -0
  556. package/dist/skills/save-gate.d.ts +64 -0
  557. package/dist/skills/save-gate.d.ts.map +1 -0
  558. package/dist/skills/save-gate.js +354 -0
  559. package/dist/skills/save-gate.js.map +1 -0
  560. package/dist/skills/save-parse.d.ts +42 -0
  561. package/dist/skills/save-parse.d.ts.map +1 -0
  562. package/dist/skills/save-parse.js +172 -0
  563. package/dist/skills/save-parse.js.map +1 -0
  564. package/dist/skills/save-store.d.ts +12 -0
  565. package/dist/skills/save-store.d.ts.map +1 -0
  566. package/dist/skills/save-store.js +22 -0
  567. package/dist/skills/save-store.js.map +1 -0
  568. package/dist/skills/save-verify.d.ts +59 -0
  569. package/dist/skills/save-verify.d.ts.map +1 -0
  570. package/dist/skills/save-verify.js +390 -0
  571. package/dist/skills/save-verify.js.map +1 -0
  572. package/dist/skills/settings.d.ts +48 -0
  573. package/dist/skills/settings.d.ts.map +1 -0
  574. package/dist/skills/settings.js +155 -0
  575. package/dist/skills/settings.js.map +1 -0
  576. package/dist/skills/skill-diff.d.ts +40 -0
  577. package/dist/skills/skill-diff.d.ts.map +1 -0
  578. package/dist/skills/skill-diff.js +191 -0
  579. package/dist/skills/skill-diff.js.map +1 -0
  580. package/dist/skills/skill-file.d.ts +24 -0
  581. package/dist/skills/skill-file.d.ts.map +1 -0
  582. package/dist/skills/skill-file.js +97 -0
  583. package/dist/skills/skill-file.js.map +1 -0
  584. package/dist/skills/skill-rebuild.d.ts +27 -0
  585. package/dist/skills/skill-rebuild.d.ts.map +1 -0
  586. package/dist/skills/skill-rebuild.js +85 -0
  587. package/dist/skills/skill-rebuild.js.map +1 -0
  588. package/dist/skills/skillmd.d.ts +17 -0
  589. package/dist/skills/skillmd.d.ts.map +1 -0
  590. package/dist/skills/skillmd.js +219 -0
  591. package/dist/skills/skillmd.js.map +1 -0
  592. package/dist/skills/skills.d.ts +28 -8
  593. package/dist/skills/skills.d.ts.map +1 -1
  594. package/dist/skills/skills.js +106 -53
  595. package/dist/skills/skills.js.map +1 -1
  596. package/dist/skills/staleness.d.ts +27 -0
  597. package/dist/skills/staleness.d.ts.map +1 -0
  598. package/dist/skills/staleness.js +178 -0
  599. package/dist/skills/staleness.js.map +1 -0
  600. package/dist/skills/state.d.ts +36 -0
  601. package/dist/skills/state.d.ts.map +1 -0
  602. package/dist/skills/state.js +259 -0
  603. package/dist/skills/state.js.map +1 -0
  604. package/dist/skills/store-fs.d.ts +77 -0
  605. package/dist/skills/store-fs.d.ts.map +1 -0
  606. package/dist/skills/store-fs.js +194 -0
  607. package/dist/skills/store-fs.js.map +1 -0
  608. package/dist/skills/store-held.d.ts +44 -0
  609. package/dist/skills/store-held.d.ts.map +1 -0
  610. package/dist/skills/store-held.js +147 -0
  611. package/dist/skills/store-held.js.map +1 -0
  612. package/dist/skills/store.d.ts +73 -0
  613. package/dist/skills/store.d.ts.map +1 -0
  614. package/dist/skills/store.js +279 -0
  615. package/dist/skills/store.js.map +1 -0
  616. package/dist/skills/types.d.ts +8 -0
  617. package/dist/skills/types.d.ts.map +1 -1
  618. package/dist/skills/update-gate.d.ts +36 -0
  619. package/dist/skills/update-gate.d.ts.map +1 -0
  620. package/dist/skills/update-gate.js +357 -0
  621. package/dist/skills/update-gate.js.map +1 -0
  622. package/dist/skills/update-plan.d.ts +40 -0
  623. package/dist/skills/update-plan.d.ts.map +1 -0
  624. package/dist/skills/update-plan.js +196 -0
  625. package/dist/skills/update-plan.js.map +1 -0
  626. package/dist/skills/validate.d.ts +11 -0
  627. package/dist/skills/validate.d.ts.map +1 -0
  628. package/dist/skills/validate.js +112 -0
  629. package/dist/skills/validate.js.map +1 -0
  630. package/dist/skills/value-line.d.ts +94 -0
  631. package/dist/skills/value-line.d.ts.map +1 -0
  632. package/dist/skills/value-line.js +355 -0
  633. package/dist/skills/value-line.js.map +1 -0
  634. package/dist/skills/versions-fs.d.ts +45 -0
  635. package/dist/skills/versions-fs.d.ts.map +1 -0
  636. package/dist/skills/versions-fs.js +234 -0
  637. package/dist/skills/versions-fs.js.map +1 -0
  638. package/dist/skills/versions.d.ts +56 -0
  639. package/dist/skills/versions.d.ts.map +1 -0
  640. package/dist/skills/versions.js +351 -0
  641. package/dist/skills/versions.js.map +1 -0
  642. package/dist/telemetry/contract.d.ts +15 -1
  643. package/dist/telemetry/contract.d.ts.map +1 -1
  644. package/dist/telemetry/contract.js +80 -11
  645. package/dist/telemetry/contract.js.map +1 -1
  646. package/dist/telemetry/gate.js +4 -4
  647. package/dist/telemetry/gate.js.map +1 -1
  648. package/dist/telemetry/index.d.ts +1 -1
  649. package/dist/telemetry/index.d.ts.map +1 -1
  650. package/dist/telemetry/index.js +1 -1
  651. package/dist/telemetry/index.js.map +1 -1
  652. package/dist/telemetry/notice.js +5 -5
  653. package/dist/telemetry/notice.js.map +1 -1
  654. package/dist/telemetry/queue.d.ts.map +1 -1
  655. package/dist/telemetry/queue.js +7 -7
  656. package/dist/telemetry/queue.js.map +1 -1
  657. package/dist/telemetry/track.d.ts.map +1 -1
  658. package/dist/telemetry/track.js +4 -3
  659. package/dist/telemetry/track.js.map +1 -1
  660. package/dist/trace/checks.d.ts +63 -0
  661. package/dist/trace/checks.d.ts.map +1 -0
  662. package/dist/trace/checks.js +495 -0
  663. package/dist/trace/checks.js.map +1 -0
  664. package/dist/trace/claude.d.ts +124 -0
  665. package/dist/trace/claude.d.ts.map +1 -0
  666. package/dist/trace/claude.js +303 -0
  667. package/dist/trace/claude.js.map +1 -0
  668. package/dist/trace/codex-exec.d.ts +22 -0
  669. package/dist/trace/codex-exec.d.ts.map +1 -0
  670. package/dist/trace/codex-exec.js +129 -0
  671. package/dist/trace/codex-exec.js.map +1 -0
  672. package/dist/trace/codex-meta.d.ts +23 -0
  673. package/dist/trace/codex-meta.d.ts.map +1 -0
  674. package/dist/trace/codex-meta.js +112 -0
  675. package/dist/trace/codex-meta.js.map +1 -0
  676. package/dist/trace/codex.d.ts +33 -0
  677. package/dist/trace/codex.d.ts.map +1 -0
  678. package/dist/trace/codex.js +485 -0
  679. package/dist/trace/codex.js.map +1 -0
  680. package/dist/trace/cursor.d.ts +16 -0
  681. package/dist/trace/cursor.d.ts.map +1 -0
  682. package/dist/trace/cursor.js +133 -0
  683. package/dist/trace/cursor.js.map +1 -0
  684. package/dist/trace/dialect.d.ts +43 -0
  685. package/dist/trace/dialect.d.ts.map +1 -0
  686. package/dist/trace/dialect.js +95 -0
  687. package/dist/trace/dialect.js.map +1 -0
  688. package/dist/trace/epochs.d.ts +10 -0
  689. package/dist/trace/epochs.d.ts.map +1 -0
  690. package/dist/trace/epochs.js +16 -0
  691. package/dist/trace/epochs.js.map +1 -0
  692. package/dist/trace/failure.d.ts +15 -0
  693. package/dist/trace/failure.d.ts.map +1 -0
  694. package/dist/trace/failure.js +69 -0
  695. package/dist/trace/failure.js.map +1 -0
  696. package/dist/trace/gate-file.d.ts +71 -0
  697. package/dist/trace/gate-file.d.ts.map +1 -0
  698. package/dist/trace/gate-file.js +329 -0
  699. package/dist/trace/gate-file.js.map +1 -0
  700. package/dist/trace/guard-fast.d.ts +22 -0
  701. package/dist/trace/guard-fast.d.ts.map +1 -0
  702. package/dist/trace/guard-fast.js +213 -0
  703. package/dist/trace/guard-fast.js.map +1 -0
  704. package/dist/trace/housekeeping.d.ts +5 -0
  705. package/dist/trace/housekeeping.d.ts.map +1 -0
  706. package/dist/trace/housekeeping.js +93 -0
  707. package/dist/trace/housekeeping.js.map +1 -0
  708. package/dist/trace/jobs.d.ts +52 -0
  709. package/dist/trace/jobs.d.ts.map +1 -0
  710. package/dist/trace/jobs.js +182 -0
  711. package/dist/trace/jobs.js.map +1 -0
  712. package/dist/trace/known-repo.d.ts +3 -0
  713. package/dist/trace/known-repo.d.ts.map +1 -0
  714. package/dist/trace/known-repo.js +55 -0
  715. package/dist/trace/known-repo.js.map +1 -0
  716. package/dist/trace/pool.d.ts +40 -0
  717. package/dist/trace/pool.d.ts.map +1 -0
  718. package/dist/trace/pool.js +154 -0
  719. package/dist/trace/pool.js.map +1 -0
  720. package/dist/trace/repairs.d.ts +27 -0
  721. package/dist/trace/repairs.d.ts.map +1 -0
  722. package/dist/trace/repairs.js +303 -0
  723. package/dist/trace/repairs.js.map +1 -0
  724. package/dist/trace/run-calls.d.ts +82 -0
  725. package/dist/trace/run-calls.d.ts.map +1 -0
  726. package/dist/trace/run-calls.js +331 -0
  727. package/dist/trace/run-calls.js.map +1 -0
  728. package/dist/trace/run-join.d.ts +20 -0
  729. package/dist/trace/run-join.d.ts.map +1 -0
  730. package/dist/trace/run-join.js +99 -0
  731. package/dist/trace/run-join.js.map +1 -0
  732. package/dist/trace/run-sheets.d.ts +26 -0
  733. package/dist/trace/run-sheets.d.ts.map +1 -0
  734. package/dist/trace/run-sheets.js +145 -0
  735. package/dist/trace/run-sheets.js.map +1 -0
  736. package/dist/trace/run-turns.d.ts +24 -0
  737. package/dist/trace/run-turns.d.ts.map +1 -0
  738. package/dist/trace/run-turns.js +67 -0
  739. package/dist/trace/run-turns.js.map +1 -0
  740. package/dist/trace/run-verdict.d.ts +24 -0
  741. package/dist/trace/run-verdict.d.ts.map +1 -0
  742. package/dist/trace/run-verdict.js +176 -0
  743. package/dist/trace/run-verdict.js.map +1 -0
  744. package/dist/trace/runs.d.ts +54 -0
  745. package/dist/trace/runs.d.ts.map +1 -0
  746. package/dist/trace/runs.js +501 -0
  747. package/dist/trace/runs.js.map +1 -0
  748. package/dist/trace/skill-session.d.ts +64 -0
  749. package/dist/trace/skill-session.d.ts.map +1 -0
  750. package/dist/trace/skill-session.js +232 -0
  751. package/dist/trace/skill-session.js.map +1 -0
  752. package/dist/trace/tamper.d.ts +12 -0
  753. package/dist/trace/tamper.d.ts.map +1 -0
  754. package/dist/trace/tamper.js +405 -0
  755. package/dist/trace/tamper.js.map +1 -0
  756. package/dist/trace/typed.d.ts +30 -0
  757. package/dist/trace/typed.d.ts.map +1 -0
  758. package/dist/trace/typed.js +132 -0
  759. package/dist/trace/typed.js.map +1 -0
  760. package/dist/trace/wall-state.d.ts +86 -0
  761. package/dist/trace/wall-state.d.ts.map +1 -0
  762. package/dist/trace/wall-state.js +279 -0
  763. package/dist/trace/wall-state.js.map +1 -0
  764. package/dist/trace/walls.d.ts +40 -0
  765. package/dist/trace/walls.d.ts.map +1 -0
  766. package/dist/trace/walls.js +318 -0
  767. package/dist/trace/walls.js.map +1 -0
  768. package/dist/upkeep-run.d.ts.map +1 -1
  769. package/dist/upkeep-run.js +2 -23
  770. package/dist/upkeep-run.js.map +1 -1
  771. package/dist/upkeep.d.ts.map +1 -1
  772. package/dist/upkeep.js +2 -3
  773. package/dist/upkeep.js.map +1 -1
  774. package/dist/util/private-fs.d.ts +6 -0
  775. package/dist/util/private-fs.d.ts.map +1 -1
  776. package/dist/util/private-fs.js +9 -1
  777. package/dist/util/private-fs.js.map +1 -1
  778. package/dist/util/state.d.ts.map +1 -1
  779. package/dist/util/state.js +17 -3
  780. package/dist/util/state.js.map +1 -1
  781. package/dist/vendor/tree-sitter-swift/LICENSE +21 -0
  782. package/dist/vendor/tree-sitter-swift/node-types.json +30694 -0
  783. package/dist/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
  784. package/dist/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
  785. package/dist/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
  786. package/dist/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
  787. package/dist/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
  788. package/dist/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
  789. package/package.json +6 -3
  790. package/scripts/stamp-telemetry-key.mjs +8 -5
  791. package/scripts/vendor-grammars.mjs +35 -0
package/dist/cli.js CHANGED
@@ -11,8 +11,8 @@
11
11
  import { spawnSync } from "node:child_process";
12
12
  import "dotenv/config";
13
13
  import { Command } from "commander";
14
- import { statSync } from "node:fs";
15
- import { basename, dirname, join, relative, resolve } from "node:path";
14
+ import { readFileSync, statSync } from "node:fs";
15
+ import { basename, join, relative, resolve } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { Graft } from "./engine.js";
18
18
  import { resolveConfig } from "./ai/providers.js";
@@ -21,12 +21,13 @@ import { formatGraphCheckReport } from "./graph/check.js";
21
21
  import { buildGraphIfMissing, runInit } from "./claude/init.js";
22
22
  import { statuslineWanted } from "./claude/settings-merge.js";
23
23
  import { runHostsInit } from "./hosts/init.js";
24
+ import { codexStatusLines, codexTrustLine } from "./hosts/codex-trust.js";
24
25
  import { hostIds } from "./hosts/registry.js";
25
- import { checkoutRoot, ensureRepoHome, keepsNotes, rememberRepo, repoName, repoPlace, shownPath } from "./notes/home.js";
26
+ import { checkoutRoot, ensureRepoHome, keepsNotes, rememberRepo, repoName, repoPlace } from "./notes/home.js";
26
27
  import { brand, cmd, graftNotice, strandedRepoNotice, tag } from "./brand.js";
27
28
  import { branchDiff } from "./cloud/diff.js";
28
29
  import { openBrowser } from "./util/browser.js";
29
- import { learningsAtBase, teammateEdits, withBaseLearnings } from "./check/learning-edits.js";
30
+ import { committedAtBase, learningsAtBase, teammateEdits, withBaseLearnings } from "./check/learning-edits.js";
30
31
  import { contextDirFor } from "./context/node-file.js";
31
32
  import { loadGraphCached } from "./graph/load.js";
32
33
  import { ensureFreshChildren, ensureFreshGraph, refreshNote } from "./graph/refresh.js";
@@ -39,8 +40,9 @@ import { runWorkspaceAsk, runWorkspaceBuild, runWorkspaceCallers, runWorkspaceCh
39
40
  import { formatInitEpilogue } from "./cli-epilogue.js";
40
41
  import { planInit, selectedWrites } from "./hosts/plan.js";
41
42
  import { planRetract, runRetract, changed } from "./hosts/retract.js";
42
- import { compactWrites, formatNonInteractiveHelp, formatPlan, pickLearnings, runPicker } from "./cli-picker.js";
43
+ import { compactWrites, formatNonInteractiveHelp, formatPlan, runPicker } from "./cli-picker.js";
43
44
  import { buildGraphWithProgress } from "./claude/build-progress.js";
45
+ import { installBrainsServer, removeBrainsServer } from "./hosts/claude-global.js";
44
46
  import { homedir } from "node:os";
45
47
  import { formatUpgradeReport, formatVersionReport, getNpmViewVersion, readCurrentVersion, readPackageName, runUpgrade } from "./cli-meta.js";
46
48
  import { nativeFallbackNotice } from "./graph/native.js";
@@ -48,25 +50,31 @@ import { patchBuildConfig } from "./util/state.js";
48
50
  import { normalizePathPrefix } from "./util/paths.js";
49
51
  import { latestSession, formatSessionStats, sessionInputRate } from "./claude/session-metrics.js";
50
52
  import { listSessionIds, readSession, sessionDir } from "./claude/state.js";
51
- import { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, savedByNote, commitLines, PERSONAL_SAVED_LINE, PR_SPEEDUP, SECTIONS, tokensWorthSaying, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
52
- import { findLearning, liveLearnings } from "./notes/learning-fields.js";
53
+ import { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, commitLines, PERSONAL_SAVED_LINE, PR_SPEEDUP, SECTIONS, tokensWorthSaying, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
53
54
  import { aboutTerms, currentSessionCost } from "./notes/session-cost.js";
54
- import { beginPastRead, finishPastRead } from "./notes/cold-start.js";
55
- import { anchoredReviews, beginReviewRead, finishReviewRead } from "./notes/pr-reviews.js";
56
- import { finishMemoryRead, memoryCandidates, offeredBefore } from "./notes/memory-notes.js";
55
+ import { flatLine, unmarkedWords } from "./notes/flatten.js";
56
+ import { beginLookBack, finishLookBack } from "./init/run.js";
57
+ import { keepCommand } from "./init/keep.js";
58
+ import { recordInit, snapInit } from "./init/install-paths.js";
59
+ import { finishPair, PairRun, validPairCode } from "./cloud/pair.js";
57
60
  import { currentBranch, noteAuthor, teammatesOn } from "./notes/git-facts.js";
61
+ import { listSkills } from "./skills/skills.js";
62
+ import { registerSkillCommands } from "./skills/cli-skills.js";
58
63
  import { gateNote, namesRepoSymbol, symbolCandidates, trackedFiles } from "./notes/note-gate.js";
59
- import { confirm, fold, getSkill, learn, listSkills, publish, skillForAgent, skillName, skillStatus } from "./skills/skills.js";
64
+ import { recordLookup } from "./cloud/lookup-activity.js";
60
65
  import { clearPending, cloudLines, loginLink, sessionKey, waitForConnect } from "./cloud/nudge.js";
61
- import { addRepo, clearCloud, cloudEnabled, fetchMe, fetchTeam, isError, readCloud, writeCloud } from "./cloud/workspace.js";
62
- import { inWorkspace, maybeSyncInBackground, recordActivity, runSyncChild, syncRepo } from "./cloud/workspace-sync.js";
66
+ import { addRepo, clearCloud, cloudEnabled, fetchMe, fetchTeam, isError, readCloud, serverUrl, trailUrl, useServer, writeCloud } from "./cloud/workspace.js";
67
+ import { claimConnectCode, connectAgain, offlineLine, learningsSummary, machineName, normalizeCode, OnboardingReporter, repoRef, teammatesChecked, teammatesFromGit, tokensOnOneLookup, } from "./cloud/onboarding.js";
68
+ import { inWorkspace, maybeSyncInBackground, recordActivity, runSyncChild, syncRepo, workspaceLearnings } from "./cloud/workspace-sync.js";
63
69
  import { checkLocally } from "./check/local-check.js";
70
+ import { findLearning, learningFileName, liveLearnings } from "./notes/learning-fields.js";
71
+ import { savedIdLine } from "./notes/replace.js";
64
72
  import { formatOverlap, maybeRefreshBranches, overlapsWith, pushedWhen, readBranchCache, refreshBranches } from "./notes/branches.js";
65
73
  import { ensureRepoTrail, LEARNINGS_DIR } from "./notes/repo-trail.js";
66
74
  import { setInputRate } from "./context/savings.js";
67
75
  import { formatUpdateNudge, maybeRefreshInBackground, readStamp, readUpdateCache, refreshUpdateCache, wiredHostIds, wiringOpts, writeStamp } from "./upkeep.js";
68
76
  import { shouldMoveToTrail } from "./upkeep-run.js";
69
- import { countBucket, savedTokensBucket, errorCode, filesBucket, durationBucket, formatDebug, formatStatus, firstRunNotice, isTrackedCommand, langsValue, offReason, maybeFlushInBackground, patchState, runFlush, track, trackFirstRunIfNew, } from "./telemetry/index.js";
77
+ import { countBucket, errorCode, filesBucket, durationBucket, formatDebug, formatStatus, firstRunNotice, isTrackedCommand, langsValue, offReason, maybeFlushInBackground, patchState, runFlush, track, trackFirstRunIfNew, } from "./telemetry/index.js";
70
78
  const program = new Command();
71
79
  const currentVersion = readCurrentVersion(import.meta.url);
72
80
  /**
@@ -80,7 +88,7 @@ const TRAIL = brand() === "trail";
80
88
  /** Help-screen groups under trail; graft keeps its one flat list. */
81
89
  const GROUP = {
82
90
  find: "Find code · your agent runs these:",
83
- memory: "Team memory:",
91
+ memory: "Learnings:",
84
92
  setup: "Setup:",
85
93
  cloud: "Workspace · your team's view, across repos:",
86
94
  ci: "CI:",
@@ -118,8 +126,8 @@ function noteHit(found) {
118
126
  program
119
127
  .name(brand())
120
128
  .description(TRAIL
121
- ? "Memory for your coding agents: a code map, and the learnings each session\n" +
122
- "leaves in your repo's .trail/, so your whole team's Claude starts from them."
129
+ ? "A code map for your coding agents, and the learnings each session leaves\n" +
130
+ "in your repo's .trail/, so everyone's Claude in the repo starts from them."
123
131
  : "Build a repo's context graph as linked markdown, and keep it in sync with the code.")
124
132
  .version(currentVersion, "-v, --version")
125
133
  .option("--dir <path>", "context graph directory (default: <repo>/graft)")
@@ -152,7 +160,7 @@ function warnUnsupportedExtensions(exts) {
152
160
  return;
153
161
  for (const e of bad) {
154
162
  const shown = e.trim().startsWith(".") ? e.trim() : `.${e.trim()}`;
155
- console.error(`⚠ -e "${shown}": no parser registered for this extension — ignoring it.`);
163
+ console.error(`⚠ -e "${shown}": no parser registered for this extension, so it is ignored.`);
156
164
  }
157
165
  console.error(` supported: ${supportedExtensions().join(" ")}`);
158
166
  }
@@ -169,7 +177,7 @@ function queryRoot(dir) {
169
177
  return resolve(dir);
170
178
  const { root, levels } = nearestGraftRoot(process.cwd(), program.opts().dir);
171
179
  if (levels > 0)
172
- console.error(`${tag()} no code map here — answering from ${contextDirFor(root, program.opts().dir)}`);
180
+ console.error(`${tag()} no code map here: answering from ${contextDirFor(root, program.opts().dir)}`);
173
181
  return root;
174
182
  }
175
183
  /**
@@ -192,7 +200,7 @@ async function refreshBefore(dir, opts) {
192
200
  }
193
201
  /** Attached to every query command: `--no-refresh` answers from the graph exactly
194
202
  * as it is on disk, no rebuild. */
195
- const NO_REFRESH_FLAG = ["--no-refresh", "skip the freshness check — answer from the graph as-is"];
203
+ const NO_REFRESH_FLAG = ["--no-refresh", "skip the freshness check: answer from the graph as-is"];
196
204
  const VIZ_TABS = ["context", "code", "outline"];
197
205
  /**
198
206
  * `--tabs context,code` → the tabs to write into an exported page.
@@ -208,7 +216,7 @@ function parseTabs(raw) {
208
216
  const want = raw.split(",").map((t) => t.trim()).filter(Boolean);
209
217
  const bad = want.filter((t) => !VIZ_TABS.includes(t));
210
218
  if (bad.length > 0 || want.length === 0) {
211
- console.error(`✗ --tabs takes a comma-separated subset of ${VIZ_TABS.join(", ")}${bad.length ? ` — got "${bad.join('", "')}"` : ""}`);
219
+ console.error(`✗ --tabs takes a comma-separated subset of ${VIZ_TABS.join(", ")}${bad.length ? `, not "${bad.join('", "')}"` : ""}`);
212
220
  process.exit(1);
213
221
  }
214
222
  return want;
@@ -218,7 +226,7 @@ function parseTabs(raw) {
218
226
  * not editorialize on stderr at startup (`mcp` runs its own upkeep at boot, and
219
227
  * `_update-check` IS the fetch).
220
228
  */
221
- const UPKEEP_SKIP = new Set(["version", "upgrade", "_update-check", "_trail-past-read", "mcp"]);
229
+ const UPKEEP_SKIP = new Set(["version", "upgrade", "_update-check", "mcp"]);
222
230
  /**
223
231
  * Every other command: top up the cached registry answer in the background and,
224
232
  * if a newer graft is out, say so once on stderr. This is what makes the CLI the
@@ -272,20 +280,6 @@ function spelling(action) {
272
280
  }
273
281
  // Hidden: spawned detached by maybeSyncInBackground when a workspace repo's
274
282
  // learnings changed, or uses are waiting to go. Running it by hand syncs now.
275
- // A repo that moved from graft to trail at session start reads its past
276
- // sessions and PR reviews here, detached, so the session doesn't wait on it.
277
- program
278
- .command("_trail-past-read", { hidden: true })
279
- .argument("<dir>")
280
- .action(async (dir) => {
281
- const repo = resolve(dir);
282
- const read = beginPastRead(repo);
283
- const reviews = beginReviewRead(repo);
284
- if (read)
285
- await finishPastRead(repo, read);
286
- if (reviews)
287
- await finishReviewRead(repo, reviews);
288
- });
289
283
  program
290
284
  .command("_trail-sync", { hidden: true })
291
285
  .description("internal: send this repo's learnings and the waiting uses to the Trail workspace")
@@ -307,6 +301,18 @@ program
307
301
  .action(async () => {
308
302
  await waitForConnect();
309
303
  });
304
+ program
305
+ .command("_trail-pair", { hidden: true })
306
+ .description("internal: after `init --pair`, wait for the connect and the review in the browser, then upload the repo's learnings")
307
+ .argument("<state>")
308
+ .argument("<dir>")
309
+ .argument("[server]", "the Trail the run reported to (init's --server)")
310
+ .action(async (state, dir, server) => {
311
+ const base = serverUrl(server);
312
+ if (base)
313
+ useServer(base);
314
+ await finishPair(state, resolve(dir));
315
+ });
310
316
  program
311
317
  .command("_update-check", { hidden: true })
312
318
  .description("internal: refresh the cached latest-version answer")
@@ -331,7 +337,7 @@ grouped(program.command("telemetry"), GROUP.setup)
331
337
  return;
332
338
  case "enable":
333
339
  patchState({ enabled: true });
334
- console.log(`telemetry: on — anonymous, aggregate-only. \`${cmd("graft telemetry status")}\` for details.`);
340
+ console.log(`telemetry: on, anonymous and aggregate-only. \`${cmd("graft telemetry status")}\` for details.`);
335
341
  return;
336
342
  case "disable":
337
343
  // Also stamp the notice as shown: someone who has just opted out should
@@ -343,7 +349,7 @@ grouped(program.command("telemetry"), GROUP.setup)
343
349
  console.log(formatDebug());
344
350
  return;
345
351
  default:
346
- console.error(`✗ unknown action "${action}" — expected status, enable, disable, or debug`);
352
+ console.error(`✗ unknown action "${action}": expected status, enable, disable, or debug`);
347
353
  process.exit(1);
348
354
  }
349
355
  });
@@ -385,7 +391,7 @@ function moveHereToTrail(dir) {
385
391
  grouped(program.command("build"), GROUP.setup)
386
392
  .description(TRAIL
387
393
  ? "Rebuild the code map in .trail/cache/ ($0, no key). --check fails if it's stale; --deep adds LLM summaries."
388
- : "Build graft/ from your code — wiring graph + per-file cards ($0, no key). " +
394
+ : "Build graft/ from your code: wiring graph + per-file cards ($0, no key). " +
389
395
  "Add --deep for the LLM concept map + per-symbol summaries/crux.")
390
396
  .argument("[dir]", "repository root", ".")
391
397
  .option("--check", "build nothing: fail if graft/ is stale relative to the code (for CI)")
@@ -403,9 +409,9 @@ grouped(program.command("build"), GROUP.setup)
403
409
  .option("--follow-nested-repos", "include nested Git clones the index does not track (a multi-repo manifest checkout, or any repo cloned into the tree) " +
404
410
  "as ONE graph, so imports across them resolve; persisted for later builds and automatic refreshes")
405
411
  .option("--no-follow-nested-repos", "exclude untracked nested Git clones; persisted for later builds and automatic refreshes (default)")
406
- .option("--include-dir <name>", "override SKIP_DIRS for this repo's walks — repeatable (e.g. --include-dir build --include-dir tools); " +
412
+ .option("--include-dir <name>", "override SKIP_DIRS for this repo's walks, repeatable (e.g. --include-dir build --include-dir tools); " +
407
413
  "persisted, so a later build (and the hooks/refresh path) include it without the flag; dot-dirs are never overridable", (val, prev) => [...prev, val], [])
408
- .option("--only-dir <path>", "only index files under this repo-relative path — repeatable; the wiring walk and the --deep " +
414
+ .option("--only-dir <path>", "only index files under this repo-relative path, repeatable; the wiring walk and the --deep " +
409
415
  "concept pass both honor it. Recorded in the graph fingerprint so a later build " +
410
416
  "(and the hooks/refresh path) walks the same set; everything outside the list is skipped", (val, prev) => [...prev, val], [])
411
417
  .option("--no-gitignore", "skip writing graft/ into .gitignore (same as GRAFT_NO_GITIGNORE=1)")
@@ -494,12 +500,12 @@ grouped(program.command("build"), GROUP.setup)
494
500
  const resolved = resolveConfig(cliConfig());
495
501
  if (deep && !resolved.apiKey) {
496
502
  deep = false;
497
- console.error("⚠ no API key set — falling back to the structural build (no LLM summaries).\n" +
503
+ console.error("⚠ no API key set, so falling back to the structural build (no LLM summaries).\n" +
498
504
  " Set GRAFT_API_KEY (and GRAFT_PROVIDER / GRAFT_BASE_URL / GRAFT_MODEL for your\n" +
499
505
  ` provider) and re-run \`${cmd("graft build --deep")}\` to add concept nodes and summaries.`);
500
506
  }
501
507
  if (deep && resolved.usedLegacyEnv) {
502
- console.error("⚠ using OPENROUTER_API_KEY (deprecated) — prefer GRAFT_API_KEY + GRAFT_BASE_URL.");
508
+ console.error("⚠ using OPENROUTER_API_KEY (deprecated): prefer GRAFT_API_KEY + GRAFT_BASE_URL.");
503
509
  }
504
510
  // Workspace parent: build each child into its OWN graft/ + a workspace index.
505
511
  const buildRoot = resolve(dir);
@@ -585,10 +591,10 @@ grouped(program.command("build"), GROUP.setup)
585
591
  console.error(fallbackNotice);
586
592
  const rel = relative(process.cwd(), g.contextDir) || "graft";
587
593
  if (process.env.GRAFT_NO_GITIGNORE) {
588
- console.log(` ${rel}/ is a local cache — add it to your gitignore if you want it untracked.`);
594
+ console.log(` ${rel}/ is a local cache: add it to your gitignore if you want it untracked.`);
589
595
  }
590
596
  else {
591
- console.log(` ${rel}/ is git-ignored (added automatically) — a local cache; teammates run \`${cmd("graft build")}\` to get their own.`);
597
+ console.log(` ${rel}/ is git-ignored (added automatically), a local cache; teammates run \`${cmd("graft build")}\` to get their own.`);
592
598
  }
593
599
  // #127: a --deep run whose LLM calls failed used to print the same success
594
600
  // footer and exit 0, so a quota-exhausted build looked identical to a clean
@@ -603,7 +609,7 @@ grouped(program.command("build"), GROUP.setup)
603
609
  const total = ready + m.stale + m.pending;
604
610
  const pct = total > 0 ? Math.round((ready / total) * 100) : 0;
605
611
  console.error("");
606
- console.error(`✗ the deep pass did not complete — the meaning tier is incomplete.`);
612
+ console.error(`✗ the deep pass did not complete, so the meaning tier is incomplete.`);
607
613
  if (conceptFatal)
608
614
  console.error(` concepts: ${conceptFatal}`);
609
615
  if (m.fatal)
@@ -622,56 +628,45 @@ grouped(program.command("build"), GROUP.setup)
622
628
  }
623
629
  }
624
630
  });
625
- /** The file part of a hit's `file:Lx-Ly` pointer. */
626
- function pointerFile(pointer) {
627
- return pointer.replace(/:L\d+(-L?\d+)?$/, "");
628
- }
629
631
  /**
630
632
  * After `ask`, under trail: which shown learnings are a teammate's, and the
631
- * shown learnings recorded as a use, which is what the team page counts as
632
- * reuse. Then the background upkeep that keeps branches and the workspace
633
- * current. Spawns no git itself. Teammates' branches come up after an edit
633
+ * lookup recorded (the learnings shown are a use, which is what the team page
634
+ * counts as reuse; the tokens saved ride along). Then the background upkeep
635
+ * that keeps branches and the workspace current. Spawns no git itself. Teammates' branches come up after an edit
634
636
  * instead, once there's a function to compare (notes/edit-overlap.ts).
635
637
  */
636
638
  function afterAsk(r, repo) {
639
+ // Every lookup is reported, not only ones that showed a learning: the tokens
640
+ // the map saved are what the team page's running total counts. A no-op unless
641
+ // this machine is connected and the repo is the workspace's.
642
+ recordLookup(r, repo);
637
643
  if (!r.notesChecked)
638
644
  return;
639
645
  rememberRepo(repo);
640
646
  maybeRefreshBranches(repo);
641
647
  const me = noteAuthor(repo);
642
- const files = [...new Set(r.hits.map((h) => pointerFile(h.pointer)))];
643
648
  const shown = r.notes ?? [];
644
649
  if (shown.length) {
645
650
  for (const h of shown)
646
651
  h.note.teammate = !!h.note.author && !!me && h.note.author !== me;
647
- recordActivity({
648
- repo: repoPlace(repo).key,
649
- kind: "search",
650
- asker: me,
651
- session: sessionKey(repo) ?? `${me}-${new Date().toISOString().slice(0, 10)}`,
652
- query: r.query,
653
- files,
654
- shown_paths: shown.map((h) => noteName(h.note)),
655
- shown_authors: shown.map((h) => h.note.author),
656
- at: new Date().toISOString(),
657
- });
658
- const saved = shown.reduce((sum, h) => sum + savedByNote(h.note), 0);
659
- track("learning_reused", { teammate: String(shown.some((h) => h.note.teammate)), saved_tokens_bucket: savedTokensBucket(saved) }, { repo });
660
652
  }
661
653
  maybeSyncInBackground(repo);
662
654
  }
663
655
  grouped(program.command("ask"), GROUP.find)
664
- .description("Query the graft/ graph — returns ranked nodes + exact file:line, routed to prose or wiring ($0, no key)")
665
- .argument("<query>", "what you want to understand, in plain words")
656
+ .description("Query the graft/ graph: returns ranked nodes + exact file:line, routed to prose or wiring ($0, no key)")
657
+ .argument("<query>", "what you want to understand, in plain words (- reads it from stdin)")
666
658
  .argument(...DIR_ARG)
667
659
  .option("-n, --limit <n>", "max results", "8")
668
- .option("--source", "inline the source at each file:line hit (retriever mode — the pack IS the answer, no need to re-open files)")
660
+ .option("--source", "inline the source at each file:line hit (retriever mode: the pack IS the answer, no need to re-open files)")
669
661
  .option("--full", "with --source: inline whole definition spans instead of the default ≤8-line crux excerpts")
670
662
  .option("--in <path>", "narrow to nodes under this path prefix, filtered before scoring (segment-aware, like scopeOf)")
671
663
  .option("--json", "output the result as JSON")
672
664
  .option("--no-graph-rank", "rank by lexical relevance only, without the graph-connectivity re-rank (ablation/eval)")
673
665
  .option(...NO_REFRESH_FLAG)
674
666
  .action(async (query, dirArg, opts) => {
667
+ // The prompt hook passes what the person typed on stdin, out of sight of `ps`.
668
+ if (query === "-")
669
+ query = readFileSync(0, "utf8").trim();
675
670
  const dir = noteQuery(queryRoot(dirArg));
676
671
  await refreshBefore(dir, opts);
677
672
  const askGlobalDir = program.opts().dir;
@@ -708,7 +703,7 @@ grouped(program.command("ask"), GROUP.find)
708
703
  }
709
704
  });
710
705
  grouped(program.command("skeleton"), GROUP.find)
711
- .description("Signatures-only view of one file from the wiring graph — the cheapest way to see a file's API surface")
706
+ .description("Signatures-only view of one file from the wiring graph: the cheapest way to see a file's API surface")
712
707
  .argument("<file>", "repo-relative path (or unique basename) of the file")
713
708
  .argument(...DIR_ARG)
714
709
  .option("--json", "output the result as JSON")
@@ -765,7 +760,7 @@ async function runCheckCommand(dirArg, opts, label) {
765
760
  }
766
761
  else {
767
762
  if (r.missing) {
768
- console.log(`deep layer: not built (run \`${cmd("graft build --deep")}\` for concept nodes) — wiring graph is the source of truth`);
763
+ console.log(`deep layer: not built (run \`${cmd("graft build --deep")}\` for concept nodes); the wiring graph is the source of truth`);
769
764
  }
770
765
  else {
771
766
  console.log(formatCheckReport(r, label));
@@ -819,7 +814,7 @@ program
819
814
  const globalOpts = program.opts();
820
815
  const contextDir = contextDirFor(root, globalOpts.dir);
821
816
  if (!existsSync(contextDir)) {
822
- console.error(`✗ no context graph at ${contextDir} — run \`${cmd("graft build --deep")}\` first`);
817
+ console.error(`✗ no context graph at ${contextDir}: run \`${cmd("graft build --deep")}\` first`);
823
818
  process.exit(1);
824
819
  }
825
820
  const viewerDir = fileURLToPath(new URL("./viewer/", import.meta.url)); // prebuilt
@@ -851,11 +846,16 @@ program
851
846
  });
852
847
  program
853
848
  .command("mcp", { hidden: TRAIL })
854
- .description("Serve the graph over MCP (stdio) — exposes graft_find_code, graft_trace_calls, graft_find_all, graft_file_api, graft_repo_map and graft_check_freshness as tools")
849
+ .description("Serve the graph over MCP (stdio): exposes graft_find_code, graft_trace_calls, graft_find_all, graft_file_api, graft_repo_map and graft_check_freshness as tools")
855
850
  .argument(...DIR_ARG)
856
- .action(async (dirArg) => {
857
- const dir = noteQuery(queryRoot(dirArg));
851
+ .option("--brains", "serve only the connected Trail workspace's brain tools (what Claude Code gets after init --connect)")
852
+ .action(async (dirArg, opts) => {
858
853
  const { startMcpServer } = await import("./mcp/server.js");
854
+ if (opts.brains) {
855
+ startMcpServer(process.cwd(), undefined, currentVersion, { brainsOnly: true });
856
+ return;
857
+ }
858
+ const dir = noteQuery(queryRoot(dirArg));
859
859
  const globalOpts = program.opts();
860
860
  startMcpServer(dir, globalOpts.dir, currentVersion);
861
861
  });
@@ -893,13 +893,13 @@ grouped(program.command("callers"), GROUP.find)
893
893
  });
894
894
  grouped(program.command("blast"), GROUP.ci)
895
895
  .description("Blast radius of a diff: what depends on the lines this change touched ($0, no LLM). " +
896
- "Built for CI — `--format markdown` is a PR comment with a Mermaid diagram.")
896
+ "Built for CI: `--format markdown` is a PR comment with a Mermaid diagram.")
897
897
  .argument(...DIR_ARG)
898
898
  .option("--base <ref>", "diff against this ref's merge base with HEAD (e.g. origin/main); default: the working tree vs HEAD")
899
899
  .option("-d, --depth <n>", 'hops to walk over incoming edges, or "all" for the full closure (default 2)')
900
900
  .option("--format <fmt>", "text (default) | markdown | mermaid | json")
901
901
  .option("--name", "name the affected areas with one cached LLM call (needs GRAFT_API_KEY); without it, areas are named after their hub symbol")
902
- .option("--export-viz <dir>", "also write the interactive page for this radius (one self-contained index.html — for CI, GitHub Pages, or an artifact)")
902
+ .option("--export-viz <dir>", "also write the interactive page for this radius (one self-contained index.html, for CI, GitHub Pages, or an artifact)")
903
903
  .option("--title <text>", "subtitle beside the repo name on the exported page (e.g. \"PR #171\")")
904
904
  .option("--no-owners", "do not suggest who to tag (by default, git history names the people behind each affected area)")
905
905
  .option("--pr-author <who...>", "GitHub login, git name or email of the PR author, so they are left out of their own suggestions")
@@ -949,7 +949,7 @@ grouped(program.command("grep"), GROUP.find)
949
949
  });
950
950
  });
951
951
  grouped(program.command("map"), GROUP.find)
952
- .description("Token-budgeted repo orientation — directory clusters, per-directory hubs, and global hotspots from the wiring graph ($0, no LLM)")
952
+ .description("Token-budgeted repo orientation: directory clusters, per-directory hubs, and global hotspots from the wiring graph ($0, no LLM)")
953
953
  .argument(...DIR_ARG)
954
954
  .option("--max-dirs <n>", "max directory entries shown, rest counted into dropped (default 16)")
955
955
  .option("--json", "output as JSON")
@@ -977,7 +977,7 @@ grouped(program.command("map"), GROUP.find)
977
977
  const contextDir = contextDirFor(root, globalOpts.dir);
978
978
  const graph = loadGraphCached(contextDir);
979
979
  if (!graph) {
980
- console.error(`✗ no graph — run ${cmd("graft build")} first`);
980
+ console.error(`✗ no graph: run ${cmd("graft build")} first`);
981
981
  process.exit(1);
982
982
  return;
983
983
  }
@@ -1007,154 +1007,12 @@ grouped(program.command("note", { hidden: !TRAIL }), GROUP.memory)
1007
1007
  .action(async (dir, opts) => {
1008
1008
  await runNoteCommand(resolve(dir), opts);
1009
1009
  });
1010
- // `trail learn` and `trail skills`: corrections become takeaways on a skill,
1011
- // kept on this machine, and takeaways fold into its SKILL.md. Unlisted under
1012
- // graft, like `note`, but they run.
1013
- // Still runs; unlisted while learnings are the one thing trail keeps.
1014
- grouped(program.command("learn", { hidden: true }), GROUP.memory)
1015
- .description("Turn a correction into a takeaway on a skill, kept in ~/.trail (the takeaway on stdin)")
1016
- .argument("<skill>", "the skill it belongs to, e.g. pdf-coords; a new name makes a new skill")
1017
- .argument("[dir]", "repository root", ".")
1018
- .option("-m, --text <text>", "the takeaway itself, instead of stdin")
1019
- .option("--description <text>", "for a new skill: what it covers")
1020
- .option("--section <heading>", "the SKILL.md heading it belongs under when folded")
1021
- .option("--author <name>", "who taught it (default: the first word of git config user.name)")
1022
- .action(async (skill, dir, opts) => {
1023
- const repo = resolve(dir);
1024
- const text = (opts.text ?? (await readStdin())).trim();
1025
- if (!text) {
1026
- console.error(`✗ a takeaway needs its text, on stdin or with -m:`);
1027
- console.error(` ${cmd("graft learn")} ${skill} <<'EOF'\n Read rotation with page.Rotation(), never pdfcpu: it drops /Rotate on linearized files.\n EOF`);
1028
- process.exitCode = 1;
1029
- return;
1030
- }
1031
- const author = opts.author?.trim() || noteAuthor(repo);
1032
- let r;
1033
- try {
1034
- r = learn(repo, { skill, text, author, date: new Date().toISOString().slice(0, 10), description: opts.description, section: opts.section });
1035
- }
1036
- catch (err) {
1037
- console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
1038
- process.exitCode = 1;
1039
- return;
1040
- }
1041
- track("takeaway_saved", { new_skill: String(r.created) }, { repo });
1042
- const kind = r.created ? "new skill" : r.skill.where === "repo" ? "the repo's skill" : "skill";
1043
- console.log(`✓ takeaway saved to ${kind} ${r.skill.name}${author ? ` · taught by ${author}` : ""}`);
1044
- console.log(`· your agent follows it from now on · kept on this machine, in ${shownPath(dirname(dirname(r.takeaway.path)))}`);
1045
- console.log(`· ${cmd("graft skills")} fold ${r.skill.name} writes it into SKILL.md`);
1046
- });
1047
- const skillsCmd = grouped(program.command("skills", { hidden: true }), GROUP.memory)
1048
- .description("List this repo's skills, and the takeaways waiting to fold into each")
1049
- .argument("[dir]", "repository root", ".")
1050
- .option("--json", "output as JSON")
1051
- .action((dir, opts) => {
1052
- const repo = resolve(dir);
1053
- const all = listSkills(repo);
1054
- if (opts.json) {
1055
- console.log(JSON.stringify(all, null, 2));
1056
- return;
1057
- }
1058
- if (all.length === 0) {
1059
- console.log(`no skills yet · ${cmd("graft learn")} <skill> saves the first takeaway and makes the skill`);
1060
- return;
1061
- }
1062
- const width = Math.max(...all.map((s) => s.name.length)) + 2;
1063
- for (const s of all)
1064
- console.log(`${s.name.padEnd(width)}${skillStatus(s)}`);
1065
- });
1066
- /** `✗ no skill called x` and the exit code, for a name that isn't one. */
1067
- function noSuchSkill(name) {
1068
- console.error(`✗ no skill called ${skillName(name)} · ${cmd("graft skills")} lists them`);
1069
- process.exitCode = 1;
1070
- }
1071
- skillsCmd
1072
- .command("show")
1073
- .description("Print a skill with every takeaway not yet folded in: what an agent reads before working in its area")
1074
- .argument("<skill>", "the skill")
1075
- .option("-C, --dir <path>", "repository root", ".")
1076
- .action((skill, opts) => {
1077
- const s = getSkill(resolve(opts.dir), skill);
1078
- if (!s)
1079
- return noSuchSkill(skill);
1080
- process.stdout.write(skillForAgent(s));
1081
- });
1082
- skillsCmd
1083
- .command("fold")
1084
- .description("Write a skill's waiting takeaways into its SKILL.md and bump its version")
1085
- .argument("[skill]", "one skill; leave out to fold every skill with takeaways waiting")
1086
- .option("--confirmed", "only the takeaways someone other than their teacher confirmed")
1087
- .option("-C, --dir <path>", "repository root", ".")
1088
- .action((skill, opts) => {
1089
- const repo = resolve(opts.dir);
1090
- if (skill && !getSkill(repo, skill))
1091
- return noSuchSkill(skill);
1092
- const names = skill ? [skill] : listSkills(repo).map((s) => s.name);
1093
- let inRepo = false;
1094
- let any = false;
1095
- for (const n of names) {
1096
- const r = fold(repo, n, { confirmed: opts.confirmed });
1097
- if (!r)
1098
- continue;
1099
- if (r.folded.length === 0) {
1100
- if (skill)
1101
- console.log(`· ${r.skill} v${r.from} · nothing to fold${r.stillWaiting ? `, ${r.stillWaiting} waiting for a second person` : ""}`);
1102
- continue;
1103
- }
1104
- any = true;
1105
- inRepo ||= r.where === "repo";
1106
- track("skills_folded", { takeaways_bucket: countBucket(r.folded.length) }, { repo });
1107
- console.log(`✓ ${r.skill} v${r.from} → v${r.to} · folded ${r.folded.length} takeaway${r.folded.length === 1 ? "" : "s"}${r.stillWaiting ? `, ${r.stillWaiting} still waiting` : ""}`);
1108
- console.log(` M ${r.where === "repo" ? shown(repo, r.file) : shownPath(r.file)} ${r.sections.map((h) => `+ ${h}`).join(" ")}`);
1109
- }
1110
- if (!any) {
1111
- if (!skill)
1112
- console.log("· nothing to fold");
1113
- return;
1114
- }
1115
- if (inRepo)
1116
- console.log("· review with git diff, then commit");
1117
- else
1118
- console.log(`· ${cmd("graft skills")} publish <skill> moves a skill into the repo's .claude/skills/, to commit for your team`);
1119
- });
1120
- skillsCmd
1121
- .command("publish")
1122
- .description("Move a skill kept on this machine into the repo's .claude/skills/, ready to commit")
1123
- .argument("<skill>", "the skill")
1124
- .option("-C, --dir <path>", "repository root", ".")
1125
- .action((skill, opts) => {
1126
- const repo = resolve(opts.dir);
1127
- const r = publish(repo, skill);
1128
- if (!r.ok) {
1129
- if (r.reason === "unknown")
1130
- return noSuchSkill(skill);
1131
- console.log(`· ${skillName(skill)} is already in the repo, in .claude/skills/${skillName(skill)}/`);
1132
- return;
1133
- }
1134
- console.log(`✓ ${skillName(skill)} → ${shown(repo, r.file)}`);
1135
- console.log("· commit it to share it: Claude Code loads it for everyone who pulls. its takeaways stay on this machine");
1136
- });
1137
- skillsCmd
1138
- .command("confirm")
1139
- .description("Say a teammate's takeaway is right, so `fold --confirmed` takes it")
1140
- .argument("<skill>", "the skill")
1141
- .argument("[takeaway]", "one takeaway file (default: every one waiting that someone else taught)")
1142
- .option("--author <name>", "who is confirming (default: the first word of git config user.name)")
1143
- .option("-C, --dir <path>", "repository root", ".")
1144
- .action((skill, takeaway, opts) => {
1145
- const repo = resolve(opts.dir);
1146
- if (!getSkill(repo, skill))
1147
- return noSuchSkill(skill);
1148
- const who = opts.author?.trim() || noteAuthor(repo);
1149
- const done = confirm(repo, skill, who, takeaway);
1150
- if (done.length === 0) {
1151
- console.log(`· nothing to confirm on ${skillName(skill)} — a takeaway needs someone other than its teacher`);
1152
- return;
1153
- }
1154
- for (const t of done)
1155
- console.log(`✓ confirmed ${t.taughtBy || "a"}'s takeaway from ${shortDate(t.date)} · ${shownPath(t.path)}`);
1156
- console.log(`· ${cmd("graft skills")} fold ${skillName(skill)} writes it into SKILL.md`);
1157
- });
1010
+ // `trail skills` (design 1.7): the skills Trail kept for you, held ones to keep, offers and the switch; and
1011
+ // `trail forget <name>`, the undo every save line names. The person-only ones refuse an agent (cli-skills.ts).
1012
+ registerSkillCommands(program);
1013
+ for (const c of program.commands)
1014
+ if (c.name() === "skills")
1015
+ grouped(c, GROUP.memory);
1158
1016
  grouped(program.command("init"), GROUP.setup)
1159
1017
  .description(TRAIL
1160
1018
  ? "Wire trail into your agents: instruction files and MCP for each, plus hooks and a statusline for Claude Code"
@@ -1172,8 +1030,36 @@ grouped(program.command("init"), GROUP.setup)
1172
1030
  .option("-y, --yes", "skip the picker and wire every detected agent (the pre-0.8 default)")
1173
1031
  .option("--no-global", "skip writes outside this repo (the ~/.codex/ config + hooks)")
1174
1032
  .option("--verbose", "print every file written, the graph build's own output and the closing banner")
1033
+ .option("--connect <code>", "connect this machine to your Trail workspace with the one-time code from team.trailhq.com, and send it a summary of this repo (titles, counts and names; never code)")
1034
+ .option("--pair <code>", "the pair code from Trail's onboarding page: that page shows this run live, then you review what it found")
1035
+ .option("--server <url>", "the Trail that gave the code (default: Trail cloud, https://team.trailhq.com)")
1036
+ .option("--plain", "look back without asking Claude to name what it finds")
1037
+ .option("--look-back", "look back at your past sessions again, for what isn't kept or closed yet")
1175
1038
  .action(async (dir, opts) => {
1176
- await runInitCommand(dir, opts);
1039
+ if (opts.connect !== undefined && opts.pair !== undefined) {
1040
+ console.error("✗ --connect and --pair are two ways in: use the one your Trail page gave you");
1041
+ process.exitCode = 1;
1042
+ return;
1043
+ }
1044
+ if (opts.connect !== undefined)
1045
+ await runConnectInit(dir, opts);
1046
+ else
1047
+ await runInitCommand(dir, opts);
1048
+ });
1049
+ // `trail init` prints this when there was no terminal to ask on (an agent ran
1050
+ // it) or with --yes: the person picks what to keep from what the look-back
1051
+ // found. An agent may run it only after the person answered (pre-bash checks).
1052
+ program
1053
+ .command("keep", { hidden: true })
1054
+ .description("keep learnings `trail init` found: trail keep 1 3, or trail keep none")
1055
+ .argument("<numbers...>", "the numbers from the list init printed, or none")
1056
+ .action((numbers) => {
1057
+ // The CLI half of the keep check (src/init/keep.ts): it needs the receipt the PreToolUse half wrote.
1058
+ const r = keepCommand(numbers, process.env);
1059
+ for (const line of r.lines)
1060
+ console.error(line);
1061
+ if (r.code)
1062
+ process.exitCode = r.code;
1177
1063
  });
1178
1064
  /** Everything on stdin, or "" when nothing is piped in. */
1179
1065
  async function readStdin() {
@@ -1266,11 +1152,12 @@ async function runNoteCommand(repo, opts) {
1266
1152
  }
1267
1153
  const author = opts.author?.trim() || noteAuthor(repo);
1268
1154
  const date = new Date().toISOString().slice(0, 10);
1155
+ // Saved by `trail note`, which is how an agent saves: src says so, and no flag changes it.
1269
1156
  const earlier = [];
1270
1157
  const saved = [];
1271
1158
  for (const t of targets) {
1272
1159
  earlier.push(...listNotes(t.dir));
1273
- saved.push(writeNote(t.dir, { title: opts.title, body, author, date, branch: currentBranch(t.dir), cost, touches: t.touches, replaces }, scope));
1160
+ saved.push(writeNote(t.dir, { title: opts.title, body, author, date, branch: currentBranch(t.dir), cost, touches: t.touches, replaces, src: "agent" }, scope));
1274
1161
  }
1275
1162
  const touches = targets.flatMap((t) => t.touches);
1276
1163
  track("note_saved", { has_cost: String(cost?.tokens !== undefined || cost?.minutes !== undefined), touches_bucket: countBucket(touches.length), scope: saved[0]?.scope ?? scope }, { repo });
@@ -1279,7 +1166,8 @@ async function runNoteCommand(repo, opts) {
1279
1166
  return;
1280
1167
  }
1281
1168
  for (const n of saved)
1282
- console.log(`✓ ${n.scope === "personal" ? "saved just for you" : "saved for your team"} · ${notePlace(n)}`);
1169
+ console.log(`✓ ${n.scope === "personal" ? "saved just for you" : "added to this repo"} · ${notePlace(n)}`);
1170
+ console.log(savedIdLine(saved));
1283
1171
  const note = saved[0];
1284
1172
  // Name a code file if the note touches one; docs are rarely what it's about.
1285
1173
  const about = touches.find((t) => !/\.(md|mdx|txt)$/i.test(t.split("#")[0])) ?? touches[0];
@@ -1294,7 +1182,7 @@ async function runNoteCommand(repo, opts) {
1294
1182
  }
1295
1183
  // Learnings about the same files, from someone else: this one builds on theirs.
1296
1184
  const files = new Set(touches.map((t) => t.split("#")[0]));
1297
- const prior = earlier.find((n) => n.author !== note.author && n.touches.some((t) => files.has(t.split("#")[0])));
1185
+ const prior = liveLearnings(earlier).find((n) => n !== old && n.author !== note.author && n.touches.some((t) => files.has(t.split("#")[0])));
1298
1186
  if (prior)
1299
1187
  console.log(`· builds on ${prior.author ? `${prior.author}'s` : "a"} learning from ${shortDate(prior.date)}`);
1300
1188
  if (!SECTIONS.some((s) => sectionLead(body, s.heading) !== null))
@@ -1334,19 +1222,56 @@ function printLines(lines) {
1334
1222
  if (lines.length)
1335
1223
  console.log(`\n${lines.join("\n")}`);
1336
1224
  }
1225
+ /**
1226
+ * `--server <url>` on `init --connect` and `--pair`: the Trail that gave the
1227
+ * code, for the rest of this run (`useServer`, never the environment). False,
1228
+ * after saying why, when it isn't a URL.
1229
+ */
1230
+ function useServerOption(raw) {
1231
+ if (raw === undefined || !raw.trim())
1232
+ return true;
1233
+ const server = serverUrl(raw);
1234
+ if (!server) {
1235
+ console.error("✗ --server takes a URL, like https://dev.trailhq.com");
1236
+ process.exitCode = 1;
1237
+ return false;
1238
+ }
1239
+ useServer(server);
1240
+ return true;
1241
+ }
1242
+ /**
1243
+ * What `init --pair` hears from init, as the onboarding page's steps: the map
1244
+ * is done when `mapped` fires, the past sessions when `learned` does. Nothing
1245
+ * to say for a run that read no sessions (that step is already marked).
1246
+ */
1247
+ function pairProgress(pair) {
1248
+ return {
1249
+ mapped: (m) => pair.phase("files", "done", m.files ? `${m.files.toLocaleString("en-US")} files` : "", m.files ? `Mapped ${m.files.toLocaleString("en-US")} files` : "Mapped the code"),
1250
+ learned: (l) => {
1251
+ if (l.sessions <= 0)
1252
+ return;
1253
+ pair.phase("decisions", "done", `${l.sessions} sessions`, `Read ${l.sessions} past Claude sessions`);
1254
+ },
1255
+ };
1256
+ }
1337
1257
  /** Repo-relative with forward slashes, for printing. */
1338
1258
  function shown(repo, path) {
1339
1259
  return relative(repo, path).split("\\").join("/");
1340
1260
  }
1261
+ /** Agents init knows by another id: both read AGENTS.md. */
1262
+ const AGENT_ALIASES = { codex: "agents", opencode: "agents" };
1341
1263
  /** `graft init`. */
1342
- async function runInitCommand(dir, opts) {
1264
+ async function runInitCommand(dir, opts, progress) {
1343
1265
  if (opts.listAgents) {
1344
1266
  for (const id of [...hostIds(), "claude"])
1345
1267
  console.log(id);
1346
1268
  return null;
1347
1269
  }
1348
1270
  const repo = resolve(dir);
1349
- const explicit = Array.isArray(opts.agents) ? opts.agents : undefined;
1271
+ // Codex and OpenCode read AGENTS.md: `--agents codex` means that wiring.
1272
+ // The name as given is kept for Trail's onboarding page.
1273
+ const asked = Array.isArray(opts.agents) ? opts.agents : undefined;
1274
+ const explicit = asked ? [...new Set(asked.map((id) => AGENT_ALIASES[id] ?? id))] : undefined;
1350
1275
  // Checked before anything is written: run from the home folder, init wired
1351
1276
  // it, mapped every code file under it, then said to commit.
1352
1277
  const refusal = homeFolderRefusal(repo);
@@ -1359,7 +1284,7 @@ async function runInitCommand(dir, opts) {
1359
1284
  const validIds = [...hostIds(), "claude"];
1360
1285
  const unknown = explicit.filter((id) => !validIds.includes(id));
1361
1286
  if (unknown.length) {
1362
- console.error(`✗ unknown agent id(s): ${unknown.join(", ")} — valid: ${validIds.join(", ")}`);
1287
+ console.error(`✗ unknown agent id(s): ${unknown.join(", ")}; valid: ${validIds.join(", ")}`);
1363
1288
  process.exit(1);
1364
1289
  }
1365
1290
  }
@@ -1391,7 +1316,7 @@ async function runInitCommand(dir, opts) {
1391
1316
  offerTelemetry: reason === null || reason === "disabled",
1392
1317
  });
1393
1318
  if (picked === null) {
1394
- console.error("· cancelled — nothing written");
1319
+ console.error("· cancelled, nothing written");
1395
1320
  return null;
1396
1321
  }
1397
1322
  ids = picked.hosts;
@@ -1419,20 +1344,42 @@ async function runInitCommand(dir, opts) {
1419
1344
  if (opts.dryRun) {
1420
1345
  console.error(formatPlan(plan, ids, repo, home));
1421
1346
  for (const child of children)
1422
- console.error(`\n— ${child}/ (workspace child)\n` + formatPlan(planInit(join(repo, child), { home }), ids, join(repo, child), home));
1347
+ console.error(`\n· ${child}/ (workspace child)\n` + formatPlan(planInit(join(repo, child), { home }), ids, join(repo, child), home));
1423
1348
  return null;
1424
1349
  }
1425
1350
  if (ids.length === 0) {
1426
- console.error("· no agents selected — nothing written");
1351
+ console.error("· no agents selected, nothing written");
1427
1352
  return null;
1428
1353
  }
1354
+ // With --pair, Trail's onboarding page is waiting on this run: tell it
1355
+ // first, so the person can sign up while the rest of init runs.
1356
+ let pair = null;
1357
+ if (opts.pair !== undefined) {
1358
+ if (!useServerOption(opts.server))
1359
+ return null;
1360
+ if (!validPairCode(opts.pair)) {
1361
+ console.error(`✗ --pair takes the code from Trail's onboarding page, got "${opts.pair}"`);
1362
+ process.exitCode = 1;
1363
+ return null;
1364
+ }
1365
+ const started = await PairRun.start(repo, opts.pair, { agent: asked?.[0] ?? ids[0], version: currentVersion });
1366
+ if ("error" in started) {
1367
+ console.error(`⚠ couldn't tell Trail about this run (${started.error}); setting up here anyway`);
1368
+ }
1369
+ else {
1370
+ pair = started;
1371
+ console.error(`· Trail's page is following along · ${started.url}`);
1372
+ }
1373
+ }
1429
1374
  const wantClaude = ids.includes("claude");
1430
1375
  const cliPath = fileURLToPath(import.meta.url);
1376
+ // D120 (2): what each checkout holds before init writes, so its install record names only init's own changes.
1377
+ const installSnaps = TRAIL ? snapInit(targets, home) : [];
1431
1378
  // Under trail, each repo gets its `.trail/learnings/`, committed with the
1432
1379
  // code, and its folder in ~/.trail for personal learnings and corrections.
1433
- // First, before the reads below send their first event: that makes the
1434
- // repo one on trail, so the event's repo id lands in `.trail/cache/` and
1435
- // not in a `graft/` that nothing ignores.
1380
+ // Before the look-back below sends its first event: that makes the repo one
1381
+ // on trail, so the event's repo id lands in `.trail/cache/` and not in a
1382
+ // `graft/` that nothing ignores.
1436
1383
  const notesHome = new Map();
1437
1384
  if (TRAIL)
1438
1385
  for (const target of targets) {
@@ -1442,31 +1389,39 @@ async function runInitCommand(dir, opts) {
1442
1389
  if (place.git)
1443
1390
  maybeRefreshBranches(place.checkout);
1444
1391
  }
1445
- // Under trail, with Claude Code picked: what the person kept telling Claude
1446
- // in this repo's past sessions, and what reviewers kept flagging on its
1447
- // merged PRs, read while the rest of init runs and saved as learnings at
1448
- // the end. Once per repo, and never asked about first.
1449
- const pastRead = TRAIL && wantClaude ? beginPastRead(repo) : null;
1450
- if (pastRead)
1451
- console.error(`· reading your ${pastRead.sessions} past Claude sessions here for what you keep telling Claude`);
1452
- const reviewRead = TRAIL && wantClaude ? beginReviewRead(repo) : null;
1453
- if (reviewRead)
1454
- console.error("· reading review comments on this repo's merged PRs for what reviewers keep flagging");
1392
+ // Under trail, with Claude Code picked: look back at this repo's past
1393
+ // sessions and PR reviews while the rest of init runs, and offer what came
1394
+ // up at the end. Nothing is kept without a tick (see src/init/review.ts).
1395
+ const tty = Boolean(process.stdin.isTTY && process.stderr.isTTY);
1396
+ const lookBack = TRAIL && wantClaude ? beginLookBack(repo, { plain: opts.plain === true, again: opts.lookBack === true }) : null;
1397
+ for (const line of lookBack?.lines ?? [])
1398
+ console.error(line);
1399
+ if (pair) {
1400
+ if (lookBack?.reads.lb)
1401
+ pair.phase("decisions", "running", "", "Reading past Claude sessions");
1402
+ else
1403
+ pair.phase("decisions", "done", "none here");
1404
+ if (lookBack?.reads.reviews)
1405
+ pair.phase("prs", "running", "", "Reading review comments on merged PRs");
1406
+ else
1407
+ pair.phase("prs", "done", "none here");
1408
+ }
1455
1409
  if (children.length)
1456
- console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s) — ${children.join(", ")}`);
1410
+ console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s): ${children.join(", ")}`);
1457
1411
  // Today's output, file by file, behind --verbose. Everything else gets one
1458
1412
  // line per step: the graph, then one per agent.
1459
1413
  const verbose = opts.verbose === true;
1460
1414
  const reports = [];
1461
1415
  for (const target of targets) {
1462
1416
  if (verbose && target !== repo)
1463
- console.error(`\n— ${relative(repo, target)}/`);
1417
+ console.error(`\n· ${relative(repo, target)}/`);
1464
1418
  reports.push(wireTarget(target, ids, { home, cliPath, plan, opts, wantClaude, quiet: !verbose }));
1465
1419
  }
1466
1420
  // The graph, built once after the wiring: a workspace parent's build is
1467
1421
  // the workspace build, so it covers every child.
1468
1422
  let graphLine = null;
1469
1423
  if (!verbose) {
1424
+ pair?.phase("files", "running", "", "Mapping the code");
1470
1425
  const res = await buildGraphWithProgress(repo, { build: opts.build, cliPath });
1471
1426
  for (const m of res.messages)
1472
1427
  console.error(m);
@@ -1479,7 +1434,7 @@ async function runInitCommand(dir, opts) {
1479
1434
  graphLine = "✓ graph built";
1480
1435
  }
1481
1436
  else if (res.failed) {
1482
- graphLine = `⚠ the graph build failed — run ${cmd("graft build")} to see why`;
1437
+ graphLine = `⚠ the graph build failed: run ${cmd("graft build")} to see why`;
1483
1438
  }
1484
1439
  else if (existing) {
1485
1440
  graphLine = `✓ graph ready · ${fmt(existing.meta.nodeCount)} nodes, ${fmt(existing.meta.edgeCount)} edges`;
@@ -1488,14 +1443,36 @@ async function runInitCommand(dir, opts) {
1488
1443
  graphLine = "✓ graph ready";
1489
1444
  }
1490
1445
  else {
1491
- graphLine = `· skipped the graph build — run ${cmd("graft build")}`;
1446
+ graphLine = `· skipped the graph build: run ${cmd("graft build")}`;
1492
1447
  }
1493
1448
  }
1449
+ // After the graph build too: it adds the map's ignore lines (`.trail/.gitignore` under trail, `.gitignore` and `.ignore`
1450
+ // under graft), and those are init's writes.
1451
+ recordInit(installSnaps);
1452
+ // --pair hears the same stages --connect does; only the ones InitProgress
1453
+ // has no slot for (a read starting, the PR read, nothing to read) stay inline.
1454
+ const report = pair ? pairProgress(pair) : progress;
1455
+ if (report?.mapped) {
1456
+ const g = loadGraphCached(contextDirFor(repo, children.length ? undefined : program.opts().dir));
1457
+ report.mapped({
1458
+ nodes: g?.meta.nodeCount ?? 0,
1459
+ edges: g?.meta.edgeCount ?? 0,
1460
+ files: g ? g.nodes.filter((n) => n.kind === "file").length : 0,
1461
+ });
1462
+ }
1463
+ // With --pair, one lookup measured against reading the files whole: the
1464
+ // page's token step. It needs the map, so it follows the build.
1465
+ let tokens = null;
1466
+ if (pair) {
1467
+ pair.phase("tokens", "running", "", "Measuring one lookup against reading the files");
1468
+ tokens = tokensOnOneLookup(repo, { contextDir: program.opts().dir });
1469
+ pair.tokensDone(tokens);
1470
+ }
1494
1471
  if (!verbose) {
1495
1472
  // Warnings and removals in full, whatever else is folded.
1496
1473
  const removed = [...new Set(reports.flatMap((r) => r.retracted))];
1497
1474
  for (const id of removed)
1498
- console.error(`- removed ${id} — agent not selected`);
1475
+ console.error(`- removed ${id} (agent not selected)`);
1499
1476
  for (const w of reports.flatMap((r) => r.warnings))
1500
1477
  console.error(w);
1501
1478
  const writesFor = (id) => (plan.find((p) => p.id === id)?.writes ?? []).filter((w) => opts.global !== false || w.scope !== "global");
@@ -1507,107 +1484,58 @@ async function runInitCommand(dir, opts) {
1507
1484
  console.error(`✓ ${id.padEnd(width)} ${summary}`.trimEnd());
1508
1485
  }
1509
1486
  }
1510
- // Under trail: what init learned, where this repo's learnings are, and how
1511
- // many there already are. The team's come from what is tied to the code:
1512
- // what reviewers kept flagging on merged PRs, and Claude Code's notes about
1513
- // the repo on this machine. What the person kept telling Claude is theirs.
1487
+ // Under trail: what the look-back found, and #662's team rows (what
1488
+ // reviewers kept flagging on merged PRs, and Claude Code's notes about the
1489
+ // repo on this machine), what the person kept, where this repo's learnings
1490
+ // are, and how many there already are.
1514
1491
  let learnedNow = false;
1515
1492
  let teamSaved = 0;
1516
- const memory = TRAIL && wantClaude ? memoryCandidates(repo, { offered: offeredBefore(repo) }) : [];
1517
- if (TRAIL && (pastRead || reviewRead || memory.length)) {
1518
- if (pastRead && !pastRead.done)
1519
- console.error("· still reading your past sessions…");
1520
- const fromSessions = pastRead ? (await pastRead.result).findings : [];
1521
- if (reviewRead && !reviewRead.done)
1522
- console.error("· still reading PR reviews…");
1523
- const fromReviews = reviewRead ? anchoredReviews(repo, (await reviewRead.result).findings) : [];
1524
- // On a terminal, the person ticks which to keep in one list, the team's
1525
- // first and ticked, their own unticked. A scripted run, or one with
1526
- // --yes, keeps the team's and what they told Claude. Cancelling saves
1527
- // none, and the next init asks again.
1528
- const picked = process.stdin.isTTY && process.stderr.isTTY && !opts.yes && fromReviews.length + memory.length + fromSessions.length
1529
- ? await pickLearnings([
1530
- ...fromReviews.map((f) => ({ title: f.title, forTeam: true, prs: f.prs })),
1531
- ...memory.map((c) => ({ title: c.title, forTeam: c.forTeam, memory: true })),
1532
- ...fromSessions.map((f) => ({ title: f.title, forTeam: false, sessions: f.sessions.length })),
1533
- ])
1534
- : undefined;
1535
- // The picked rows that are `found`'s, which start at `from` in the list.
1536
- const keepFrom = (from) => picked === undefined
1537
- ? undefined
1538
- : async (found) => picked && picked.filter((i) => i >= from && i < from + found.length).map((i) => found[i - from]);
1539
- const learned = [];
1540
- // Reviews first: which of them are offered depends on the learnings already saved.
1541
- if (reviewRead) {
1542
- const { result, saved, kept, cancelled } = await finishReviewRead(repo, reviewRead, keepFrom(0));
1543
- const offered = fromReviews.length;
1544
- if (saved.length) {
1545
- const n = saved.length;
1546
- const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1547
- console.error(`✓ ${what} reviewers made authors change, from the last ${result.prs} merged PRs here`);
1548
- // saveReviewFindings keeps the findings' order.
1549
- saved.forEach((note, i) => console.error(` for your team · from review on ${(kept[i]?.prs ?? []).map((n) => `#${n}`).join(", ")}: ${note.title}`));
1550
- }
1551
- else if (cancelled) {
1552
- console.error(`· saved none of the ${offered} things reviewers made authors change; ${cmd("graft init")} again asks again`);
1553
- }
1554
- else if (offered) {
1555
- console.error(`· kept none of the ${offered} things reviewers made authors change`);
1556
- }
1557
- else if (result.reason === "failed") {
1558
- console.error(`· couldn't read PR reviews this time; ${cmd("graft init")} again tries again`);
1559
- }
1560
- learned.push(...saved);
1493
+ // What the look-back read, for the --pair and --connect pages, and the skills it found (--pair names them).
1494
+ let sessionsRead = 0;
1495
+ let skillsFound = [];
1496
+ if (lookBack) {
1497
+ // With --pair the person reviews what was found on Trail's page, so no screen here, as with no terminal.
1498
+ const screen = tty && !pair;
1499
+ const learned = await finishLookBack(repo, lookBack.reads, { yes: opts.yes === true, verbose: opts.verbose === true, say: (l) => console.error(l), ...(screen ? { io: { stdin: process.stdin, stdout: process.stderr } } : {}) });
1500
+ // #662's lines for the team rows it kept with no screen, in its words.
1501
+ const { fromReviews, fromMemory } = learned.teamKept ?? { fromReviews: null, fromMemory: null };
1502
+ if (fromReviews?.saved.length) {
1503
+ const n = fromReviews.saved.length;
1504
+ const what = n < fromReviews.offered ? `kept ${n} of the ${fromReviews.offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1505
+ console.error(`✓ ${what} reviewers made authors change, from the last ${fromReviews.result.prs} merged PRs here`);
1506
+ // saveReviewFindings keeps the findings' order.
1507
+ fromReviews.saved.forEach((note, i) => console.error(` for your team · from review on ${(fromReviews.kept[i]?.prs ?? []).map((n) => `#${n}`).join(", ")}: ${note.title}`));
1561
1508
  }
1562
- if (memory.length) {
1563
- const { saved, cancelled } = await finishMemoryRead(repo, memory, keepFrom(fromReviews.length));
1564
- if (saved.length) {
1565
- console.error(`✓ kept ${saved.length} of the ${memory.length} notes your Claude keeps about this repo`);
1566
- for (const note of saved)
1567
- console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${note.title}`);
1568
- }
1569
- else if (cancelled) {
1570
- console.error(`· saved none of the ${memory.length} notes your Claude keeps about this repo; ${cmd("graft init")} again asks again`);
1571
- }
1572
- learned.push(...saved);
1509
+ if (fromMemory?.saved.length) {
1510
+ console.error(`✓ kept ${fromMemory.saved.length} of the ${fromMemory.offered} notes your Claude keeps about this repo`);
1511
+ for (const note of fromMemory.saved)
1512
+ console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${note.title}`);
1573
1513
  }
1574
- if (pastRead) {
1575
- const { result, saved, kept, cancelled } = await finishPastRead(repo, pastRead, keepFrom(fromReviews.length + memory.length));
1576
- const offered = result.findings.length;
1577
- if (saved.length) {
1578
- const n = saved.length;
1579
- const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1580
- console.error(`✓ ${what} you told Claude more than once, from your last ${result.read} sessions here, just for you`);
1581
- // Each with how many sessions it was said in: saveFindings keeps the findings' order.
1582
- saved.forEach((note, i) => {
1583
- const said = kept[i]?.sessions.length ?? 0;
1584
- const count = said > 1 ? `told Claude in ${said} sessions: ` : "";
1585
- console.error(` just for you · ${count}${note.title}`);
1586
- });
1587
- }
1588
- else if (cancelled) {
1589
- console.error(`· saved none of the ${offered} things you told Claude more than once; ${cmd("graft init")} again asks again`);
1590
- }
1591
- else if (offered) {
1592
- console.error(`· kept none of the ${offered} things you told Claude more than once`);
1593
- }
1594
- else if (result.reason === "failed") {
1595
- console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
1596
- }
1597
- learned.push(...saved);
1514
+ sessionsRead = learned.read?.sessions ?? 0;
1515
+ skillsFound = learned.skills ?? [];
1516
+ const prs = learned.read?.prs;
1517
+ if (pair && prs !== undefined && prs !== null) {
1518
+ const n = fromReviews?.saved.length ?? 0;
1519
+ pair.phase("prs", "done", `${prs} PRs`, `Read ${prs} merged PRs: ${n} thing${n === 1 ? "" : "s"} reviewers made authors change`);
1598
1520
  }
1599
- learnedNow = learned.length > 0;
1600
- teamSaved = learned.filter((s) => s.scope === "repo").length;
1521
+ // The team learnings kept with no screen, said in no past session; after a screen the page lists the repo's own.
1522
+ const notes = [...(fromReviews?.saved ?? []), ...(fromMemory?.saved ?? [])].map((note) => ({ note, sessions: 0 }));
1523
+ report?.learned?.({ notes, sessions: sessionsRead });
1524
+ learnedNow = learned.kept > 0;
1525
+ teamSaved = learned.team;
1601
1526
  if (learnedNow) {
1602
1527
  console.error("· Claude follows them from your next session");
1603
- if (teamSaved)
1528
+ if (learned.inRepo)
1604
1529
  console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
1605
1530
  }
1606
1531
  }
1532
+ // Not looked back (not trail, or not Claude Code): the page still gets its stage.
1533
+ if (!lookBack)
1534
+ report?.learned?.({ notes: [], sessions: 0 });
1607
1535
  if (TRAIL && !learnedNow) {
1608
1536
  const home = notesHome.get(repo);
1609
1537
  if (home) {
1610
- const learnings = listNotes(repo, { branches: false }).filter((n) => n.scope === "repo");
1538
+ const learnings = liveLearnings(listNotes(repo, { branches: false })).filter((n) => n.scope === "repo");
1611
1539
  if (learnings.length === 0)
1612
1540
  console.error(`✓ learnings ${LEARNINGS_DIR}/ · a session that fixes a bug, takes a correction or opens a PR leaves one there, for your whole team`);
1613
1541
  else
@@ -1644,8 +1572,8 @@ async function runInitCommand(dir, opts) {
1644
1572
  console.error(NOT_GIT_LINE);
1645
1573
  else if (tops.length)
1646
1574
  console.error(TRAIL
1647
- ? `· commit ${tops.slice(0, 5).join(" and ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} once — after that, teammates' Claude reads your learnings even before they install trail`
1648
- : `· commit ${tops.slice(0, 5).join(" ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} to share it — graft/ stays local and git-ignored`);
1575
+ ? `· commit ${tops.slice(0, 5).join(" and ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} once: after that, teammates' Claude reads your learnings even before they install trail`
1576
+ : `· commit ${tops.slice(0, 5).join(" ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} to share it: graft/ stays local and git-ignored`);
1649
1577
  }
1650
1578
  // One epilogue for the whole run. A workspace parent holds no nodes of its
1651
1579
  // own, so the totals come from the children — the graph the user actually got.
@@ -1660,14 +1588,130 @@ async function runInitCommand(dir, opts) {
1660
1588
  nodes: graphs.reduce((n, g) => n + g.meta.nodeCount, 0),
1661
1589
  edges: graphs.reduce((n, g) => n + g.meta.edgeCount, 0),
1662
1590
  }));
1591
+ // Hand what was found to the review page, and leave a child waiting for
1592
+ // the person to connect and review there.
1593
+ if (pair) {
1594
+ // Teammates' branches on this person's files, checked against the
1595
+ // learnings just saved and the repo's own: synchronous, so the finding
1596
+ // exists before the page is told the read is done.
1597
+ pair.phase("team", "running", "", "Checking your team's branches");
1598
+ const teamNotes = listNotes(repo, { branches: false }).filter((n) => n.scope === "repo" && !n.ref);
1599
+ // No remote (or no default branch) means nothing was checked: `teammates`
1600
+ // is left out, since an empty list would say "checked, none".
1601
+ const teammates = teammatesChecked(repo, { learnings: teamNotes });
1602
+ pair.teamDone(teammates);
1603
+ pair.findings({ tokens, sessions: lookBack?.reads.lb ? sessionsRead : undefined, teammates });
1604
+ await pair.finish(repo, skillsFound);
1605
+ pair.handOff(repo);
1606
+ console.error(`✓ Trail has what it found · review it in the browser: ${pair.url}`);
1607
+ if (!process.stdout.isTTY)
1608
+ console.error(`[trail] Tell the user, in one line: their Trail page has moved on; review what Trail found there. If it isn't open, the link is ${pair.url}`);
1609
+ }
1663
1610
  // Run by an agent, not a person: the last line tells it to ask about the
1664
1611
  // commit, since the person never saw the picker and Claude Code folds the rest.
1665
- if (teamSaved && !process.stderr.isTTY)
1612
+ // With --pair the person reviews them on the Trail page first, so no ask here.
1613
+ if (teamSaved && !pair && !process.stderr.isTTY)
1666
1614
  console.error(`→ ${teamSaved} team learning${teamSaved === 1 ? "" : "s"} in ${LEARNINGS_DIR}/ ${teamSaved === 1 ? "isn't" : "aren't"} committed yet. Ask the user: "Commit ${teamSaved === 1 ? "it" : `the ${teamSaved}`} so your teammates' Claude starts from ${teamSaved === 1 ? "it" : "them"}? (yes / no)"`);
1667
1615
  // Sorted so `claude,cursor` and `cursor,claude` aggregate as one value.
1668
1616
  track("init_completed", { agents: [...ids].sort().join(","), consent: consent === undefined ? "unasked" : String(consent) }, { repo });
1669
1617
  return { ids };
1670
1618
  }
1619
+ /**
1620
+ * `trail init --connect <CODE>`: the onboarding on team.trailhq.com. Claims the
1621
+ * one-time code (a wrong or spent one stops here, before anything is written),
1622
+ * saves the workspace token where `trail login` keeps it, then runs init as
1623
+ * usual while the page ticks off connected, mapped and learned. At the end,
1624
+ * one summary of the repo goes up (a lookup's tokens saved, the learnings'
1625
+ * titles, teammates' branches from git: never code, diffs or session text),
1626
+ * then `done`. Offline, init still sets the repo up and says how to connect.
1627
+ */
1628
+ async function runConnectInit(dir, opts) {
1629
+ if (opts.listAgents || opts.dryRun) {
1630
+ if (opts.dryRun)
1631
+ console.error("· --dry-run: not connecting");
1632
+ await runInitCommand(dir, opts);
1633
+ return;
1634
+ }
1635
+ const repo = resolve(dir);
1636
+ if (!useServerOption(opts.server))
1637
+ return;
1638
+ const base = trailUrl();
1639
+ const code = normalizeCode(opts.connect ?? "");
1640
+ if (!code) {
1641
+ console.error(`✗ "${opts.connect ?? ""}" isn't a Trail code · it looks like TRL-7F2A-K9QD, from ${base}/welcome/code`);
1642
+ process.exitCode = 1;
1643
+ return;
1644
+ }
1645
+ const ref = repoRef(repo);
1646
+ const machine = machineName();
1647
+ const got = await claimConnectCode(base, code, { machine, repo: ref });
1648
+ const cloud = "cloud" in got ? got.cloud : null;
1649
+ if ("cloud" in got) {
1650
+ const cloud = got.cloud;
1651
+ writeCloud(cloud);
1652
+ // A sign-in link out from before is moot now.
1653
+ clearPending();
1654
+ console.error(`✓ connected to workspace ${cloud.workspace.name}`);
1655
+ track("brain_signup_settled", { outcome: "linked", mode: "connect", duration_bucket: durationBucket(0) }, { repo });
1656
+ }
1657
+ else if (got.reason === "invalid") {
1658
+ console.error(`✗ ${got.error} · get a new code at ${base}/welcome/code`);
1659
+ process.exitCode = 1;
1660
+ return;
1661
+ }
1662
+ else {
1663
+ console.error(offlineLine(got.error, base, code, cmd("graft init")));
1664
+ }
1665
+ const reporter = cloud ? new OnboardingReporter(cloud, ref, { machine }) : null;
1666
+ void reporter?.stage("connected");
1667
+ // What init learned this run, kept for the summary.
1668
+ const heard = { notes: [], sessions: 0 };
1669
+ const progress = reporter
1670
+ ? {
1671
+ mapped: (m) => void reporter.stage("mapped", m),
1672
+ learned: (l) => {
1673
+ Object.assign(heard, l);
1674
+ const team = learningsSummary(repo, l.notes);
1675
+ void reporter.stage("learned", { learnings: team.length, sessions: l.sessions || Math.max(0, ...team.map((t) => t.sessions)) });
1676
+ },
1677
+ }
1678
+ : undefined;
1679
+ // The page's command is pasted into a terminal, but an agent may run it for
1680
+ // them: with no terminal to pick in, wire the agents this machine has.
1681
+ const interactive = process.stdin.isTTY && process.stderr.isTTY;
1682
+ const res = await runInitCommand(dir, interactive ? opts : { ...opts, yes: true }, progress);
1683
+ if (!reporter || !cloud)
1684
+ return;
1685
+ if (!res) {
1686
+ await reporter.flush();
1687
+ return;
1688
+ }
1689
+ // What Trail found, for the page: route B, summaries only.
1690
+ const learnings = learningsSummary(repo, heard.notes);
1691
+ const sessions = heard.sessions || Math.max(0, ...learnings.map((l) => l.sessions));
1692
+ const tokens = tokensOnOneLookup(repo, { contextDir: program.opts().dir });
1693
+ const teamNotes = listNotes(repo, { branches: false }).filter((n) => n.scope === "repo" && !n.ref);
1694
+ const teammates = teammatesFromGit(repo, { learnings: teamNotes });
1695
+ const sent = await reporter.summary({ tokens, ...(sessions ? { sessions } : {}), learnings, teammates });
1696
+ await reporter.stage("done");
1697
+ await reporter.flush();
1698
+ // Claude Code gets the workspace's brains through one user-level `trail` entry.
1699
+ const brainsEntry = res.ids.includes("claude") && opts.global !== false ? installBrainsServer(homedir()) : null;
1700
+ if (sent) {
1701
+ const parts = [
1702
+ tokens ? `one lookup saved ${tokens.pct}% of the tokens` : null,
1703
+ `${learnings.length} learning${learnings.length === 1 ? "" : "s"}`,
1704
+ `${teammates.length} teammate branch${teammates.length === 1 ? "" : "es"} on your files`,
1705
+ ].filter(Boolean);
1706
+ console.error(`✓ sent ${cloud.workspace.name} a summary: ${parts.join(", ")} · titles, counts and names only, no code`);
1707
+ console.error(`· see what Trail found: ${base}/welcome/code/found`);
1708
+ }
1709
+ else {
1710
+ console.error(`· couldn't send the summary to Trail this time · with a new code from ${base}/welcome/code, ${connectAgain(cmd("graft init"), "<code>", base)} sends it again`);
1711
+ }
1712
+ if (brainsEntry?.action === "created" || brainsEntry?.action === "updated")
1713
+ console.error(`✓ Claude Code gets your workspace's brains in every project · restart it to pick them up`);
1714
+ }
1671
1715
  /** One repo's worth of `init` writes — the parent, then each workspace child. */
1672
1716
  function wireTarget(repo, ids, ctx) {
1673
1717
  const { home, cliPath, plan, wantClaude } = ctx;
@@ -1690,7 +1734,7 @@ function wireTarget(repo, ids, ctx) {
1690
1734
  // be rewritten, and the graph cache is kept (init is one step from using it).
1691
1735
  const retracted = changed(runRetract(repo, { home, apply: true, global: opts.global, cache: false, exclude: ids })).filter((r) => r.action !== "skipped-unparseable");
1692
1736
  for (const r of retracted)
1693
- say(`- removed ${r.path} (${r.what}) — agent not selected`);
1737
+ say(`- removed ${r.path} (${r.what}): agent not selected`);
1694
1738
  report.retracted = [...new Set(retracted.map((r) => r.hostId))];
1695
1739
  if (wantClaude) {
1696
1740
  // `global`/`home` are threaded through alongside `statusline`: the claude layer
@@ -1706,11 +1750,11 @@ function wireTarget(repo, ids, ctx) {
1706
1750
  // trail registers no server for Claude Code: the hooks and the `trail` command cover it.
1707
1751
  }
1708
1752
  else if (res.mcp.action === "skipped-unparseable")
1709
- say(`⚠ .mcp.json: ${res.mcp.path} left unchanged (not valid JSON) — add the graft server manually`);
1753
+ say(`⚠ .mcp.json: ${res.mcp.path} left unchanged (not valid JSON); add the graft server manually`);
1710
1754
  else if (res.mcp.action === "unchanged")
1711
1755
  say(`· mcp claude: ${res.mcp.path} (already registered)`);
1712
1756
  else
1713
- say(`✓ mcp claude: ${res.mcp.path} (${res.mcp.action}) — restart Claude Code to load the graft MCP server`);
1757
+ say(`✓ mcp claude: ${res.mcp.path} (${res.mcp.action}); restart Claude Code to load the graft MCP server`);
1714
1758
  say(res.built ? `✓ built the graph (${cmd("graft build")})` : "· skipped graph build");
1715
1759
  if (!wantStatusline)
1716
1760
  say("· skipped Claude Code statusLine (--no-statusline)");
@@ -1735,6 +1779,13 @@ function wireTarget(repo, ids, ctx) {
1735
1779
  say(`✓ mcp ${m.id}: ${m.path} (${m.action})`);
1736
1780
  for (const h of r.hooks)
1737
1781
  say(`✓ hook ${h.id}: ${h.path} (${h.action})`);
1782
+ // Phase 2 (4.17, Q3): Codex runs Trail's hooks only once the person trusts them in /hooks.
1783
+ if (r.hooks.some((h) => h.id === "codex-hooks")) {
1784
+ const trust = codexTrustLine({ ...process.env, HOME: home, CODEX_HOME: process.env.CODEX_HOME || join(home, ".codex") });
1785
+ // A ⚠ line, so the short output keeps it too: until then Trail does nothing in Codex.
1786
+ if (trust)
1787
+ say(`⚠ ${trust}`);
1788
+ }
1738
1789
  // Only worth saying when there was actually something out-of-repo to skip.
1739
1790
  if (opts.global === false && selectedWrites(plan, ids).some((w) => w.scope === "global"))
1740
1791
  say("· skipped out-of-repo writes (--no-global)");
@@ -1764,7 +1815,7 @@ function wireTarget(repo, ids, ctx) {
1764
1815
  function formatRetractions(rs, apply) {
1765
1816
  const hit = changed(rs);
1766
1817
  if (hit.length === 0)
1767
- return "· nothing to remove — no graft wiring found here";
1818
+ return "· nothing to remove: no graft wiring found here";
1768
1819
  const verb = apply ? "removed" : "would remove";
1769
1820
  const lines = [];
1770
1821
  const byHost = new Map();
@@ -1782,9 +1833,9 @@ function formatRetractions(rs, apply) {
1782
1833
  ? "-"
1783
1834
  : "~";
1784
1835
  const note = r.action === "skipped-unparseable"
1785
- ? " — not valid JSON, left untouched (remove the graft entry by hand)"
1836
+ ? ": not valid JSON, left untouched (remove the graft entry by hand)"
1786
1837
  : r.action === "deleted"
1787
- ? ` (${r.what} — deleted)`
1838
+ ? ` (${r.what}, deleted)`
1788
1839
  : ` (${r.what})`;
1789
1840
  const scope = r.scope === "global" ? " [machine-wide]" : "";
1790
1841
  lines.push(` ${mark} ${verb}: ${r.path}${scope}${note}`);
@@ -1796,7 +1847,7 @@ grouped(program.command("uninstall"), GROUP.setup)
1796
1847
  .description(`Remove every file and config entry ${TRAIL ? "trail or graft" : "graft"} has written to this repo (the inverse of init)`)
1797
1848
  .argument("[dir]", "target repo directory", ".")
1798
1849
  .option("-y, --yes", "actually remove (without this, prints what it would remove and exits)")
1799
- .option("--keep-cache", "keep graft/ and the .gitignore entries — wiring only")
1850
+ .option("--keep-cache", "keep graft/ and the .gitignore entries: wiring only")
1800
1851
  .option("--no-global", "leave out-of-repo files alone (~/.codex, ~/.gemini)")
1801
1852
  .action((dir, opts) => {
1802
1853
  const repo = resolve(dir);
@@ -1804,7 +1855,7 @@ grouped(program.command("uninstall"), GROUP.setup)
1804
1855
  const common = { home, global: opts.global, cache: opts.keepCache ? false : true };
1805
1856
  if (!opts.yes) {
1806
1857
  console.error(formatRetractions(planRetract(repo, common), false));
1807
- console.error("\nDry run — nothing was touched. Re-run with -y to remove.");
1858
+ console.error("\nDry run: nothing was touched. Re-run with -y to remove.");
1808
1859
  if (opts.global !== false)
1809
1860
  console.error("Entries marked [machine-wide] affect every project; --no-global skips them.");
1810
1861
  return;
@@ -1813,7 +1864,7 @@ grouped(program.command("uninstall"), GROUP.setup)
1813
1864
  console.error(formatRetractions(done, true));
1814
1865
  const bad = changed(done).filter((r) => r.action === "skipped-unparseable");
1815
1866
  console.error(bad.length
1816
- ? `\n⚠ ${bad.length} file(s) could not be parsed and were left as-is — see above.`
1867
+ ? `\n⚠ ${bad.length} file(s) could not be parsed and were left as-is: see above.`
1817
1868
  : `\n✓ ${brand()} fully removed. \`${cmd("graft init")}\` re-wires from scratch.`);
1818
1869
  });
1819
1870
  /* -------------------------------------------------------------------------- */
@@ -1823,23 +1874,22 @@ grouped(program.command("uninstall"), GROUP.setup)
1823
1874
  * `trail login`: connect this machine to a Trail workspace by hand. Normally
1824
1875
  * nobody runs it: Claude offers the same link once there's a learning to
1825
1876
  * share. Prints the link, opens it, and waits for the person to sign in and
1826
- * pick repos, then uploads those repos' learnings.
1877
+ * add the repo they're in, then uploads its committed learnings. Other repos
1878
+ * are added one at a time afterwards (`trail workspace add`).
1827
1879
  */
1828
1880
  function loginCommand(name = "login") {
1829
1881
  return new Command(name)
1830
- .description("Connect this machine to a Trail workspace: sign in, pick the repos it gets, invite your team")
1831
- .argument("[dir]", "the repo you're in, ticked first in the picker", ".")
1832
- .option("--server <url>", "a self-hosted Trail, e.g. https://trail.acme.internal (default: Trail cloud)")
1882
+ .description("Connect this machine to a Trail workspace: sign in, add this repo, invite your team")
1883
+ .argument("[dir]", "the repo to add", ".")
1884
+ .option("--server <url>", "a self-hosted Trail, e.g. https://trail.acme.internal (default: Trail cloud, https://team.trailhq.com)")
1833
1885
  .action(async (dir, opts) => {
1834
1886
  const repo = resolve(dir);
1835
- const server = opts.server?.trim().replace(/\/+$/, "");
1836
- if (server) {
1837
- if (!/^https?:\/\//.test(server)) {
1838
- console.error(`✗ --server takes a URL, like https://trail.acme.internal`);
1839
- process.exitCode = 1;
1840
- return;
1841
- }
1842
- process.env.TRAIL_URL = server;
1887
+ // Saved on the link, so its sign-in and every upload after go there; nothing in the environment.
1888
+ const server = opts.server === undefined ? undefined : serverUrl(opts.server);
1889
+ if (server === null) {
1890
+ console.error(`✗ --server takes a URL, like https://trail.acme.internal`);
1891
+ process.exitCode = 1;
1892
+ return;
1843
1893
  }
1844
1894
  const cloud = readCloud();
1845
1895
  if (cloud) {
@@ -1847,8 +1897,8 @@ function loginCommand(name = "login") {
1847
1897
  return;
1848
1898
  }
1849
1899
  rememberRepo(repo);
1850
- const url = loginLink(repo);
1851
- console.error(`· sign in and pick the repos your workspace gets:\n ${url}`);
1900
+ const url = loginLink(repo, process.env, Date.now(), server);
1901
+ console.error(`· sign in and add this repo to your workspace:\n ${url}`);
1852
1902
  if (process.stdout.isTTY)
1853
1903
  openBrowser(url);
1854
1904
  console.error("· waiting for you to finish in the browser (ctrl-c stops; Claude picks it up later too)");
@@ -1869,6 +1919,7 @@ function logoutCommand(name = "logout") {
1869
1919
  .action(() => {
1870
1920
  const cloud = readCloud();
1871
1921
  clearPending();
1922
+ removeBrainsServer(homedir());
1872
1923
  if (!clearCloud()) {
1873
1924
  console.error("· not connected to a workspace");
1874
1925
  return;
@@ -1918,6 +1969,8 @@ function workspaceCommand(name = "workspace") {
1918
1969
  if (here.git && !me.repos.some((r) => r.key === here.key)) {
1919
1970
  console.log(` · ${here.name.padEnd(width)}not in the workspace · ${cmd("graft workspace add")} shares its learnings`);
1920
1971
  }
1972
+ else if (here.git)
1973
+ printLines(leftOutLines(here.checkout));
1921
1974
  });
1922
1975
  ws.command("add")
1923
1976
  .description("Add this repo to your workspace and share its learnings")
@@ -1951,18 +2004,33 @@ function workspaceCommand(name = "workspace") {
1951
2004
  return;
1952
2005
  }
1953
2006
  console.error(`✓ added ${here.name} to ${cloud.workspace.name} · shared ${res.learnings} learning${res.learnings === 1 ? "" : "s"}`);
2007
+ for (const l of leftOutLines(here.checkout))
2008
+ console.error(l);
1954
2009
  console.error(`· ${cloud.workspace.url}`);
1955
2010
  });
1956
2011
  return ws;
1957
2012
  }
2013
+ /**
2014
+ * One line per learning of this repo's that the workspace doesn't get because
2015
+ * it looks like it holds a secret: named, with what was seen, never the
2016
+ * secret. Nothing when there are none or git can't read the repo.
2017
+ */
2018
+ function leftOutLines(checkout) {
2019
+ const out = workspaceLearnings(checkout)?.leftOut ?? [];
2020
+ return out.map((l) => ` · left out: ${flatLine(l.name, 200)}${l.ref ? ` (on ${flatLine(l.ref, 120)})` : ""}: looks like it holds a secret (${l.why}). Take the secret out and commit, and it goes up with the next sync`);
2021
+ }
1958
2022
  /* -------------------------------------------------------------------------- */
1959
2023
  /* trail team · trail check */
1960
2024
  /* -------------------------------------------------------------------------- */
1961
- /** Fold a long line at `width`, indenting what wraps. */
2025
+ /**
2026
+ * Fold a long line at `width`, indenting what wraps. The text holds a
2027
+ * learning's or a teammate's words, and any word can start a line, so it's
2028
+ * one line first and no word starts with a `[trail]`-style marker (flatten.ts).
2029
+ */
1962
2030
  function wrap(text, width, indent) {
1963
2031
  const out = [];
1964
2032
  let line = "";
1965
- for (const word of text.split(/\s+/)) {
2033
+ for (const word of unmarkedWords(text)) {
1966
2034
  if (line && line.length + 1 + word.length > width) {
1967
2035
  out.push(line);
1968
2036
  line = indent + word;
@@ -2085,7 +2153,7 @@ function localTeam(repo, days, json) {
2085
2153
  }
2086
2154
  function teamCheckCommand(name = "check") {
2087
2155
  return new Command(name)
2088
- .description("Check your branch's diff against everything the team has learned: the repo's learnings, yours and your skills")
2156
+ .description("Check your branch's diff against the learnings that bear on it: this repo's, yours, and the skills")
2089
2157
  .argument("[dir]", "repository root", ".")
2090
2158
  .option("--base <ref>", "compare against where the branch left this ref (default: origin's default branch)")
2091
2159
  .option("--json", "output as JSON")
@@ -2105,7 +2173,10 @@ function withoutTrailFiles(diff) {
2105
2173
  * the repo's `.trail/` (teammates' pushed branches included), this person's
2106
2174
  * own, and the repo's skills. No sign-in, no server. Exits 1 on a conflict.
2107
2175
  */
2108
- async function runTeamCheck(repo, opts) {
2176
+ async function runTeamCheck(dir, opts) {
2177
+ // From the top of the checkout: git names changed files from there, and a learning's touches have to
2178
+ // line up with them exactly to fail the check. From a folder inside, none ever would.
2179
+ const repo = checkoutRoot(dir) ?? dir;
2109
2180
  const d = branchDiff(repo, opts.base);
2110
2181
  if (!d) {
2111
2182
  console.error("✗ not a git repository");
@@ -2125,17 +2196,20 @@ async function runTeamCheck(repo, opts) {
2125
2196
  maybeRefreshBranches(repo);
2126
2197
  if (d.truncated)
2127
2198
  console.error("⚠ the diff is over 400 KB; only the first 400 KB is checked");
2128
- const r = checkLocally({ diff: withoutTrailFiles(d.diff), files, notes: withBaseLearnings(listNotes(repo), atBase), skills: listSkills(repo), asker: me });
2199
+ // Replaced and retired learnings are left out after the swap, so a branch can't retire a learning by editing it.
2200
+ const notes = withBaseLearnings(listNotes(repo), atBase);
2201
+ const r = checkLocally({ diff: withoutTrailFiles(d.diff), files, notes, skills: listSkills(repo), asker: me, committed: committedAtBase(repo, d.from) });
2129
2202
  r.findings.unshift(...edits);
2130
- const sources = r.findings.map((f) => f.source).filter((s) => s.path);
2203
+ // Only learnings the workspace gets: a personal one's file name carries its title.
2204
+ const personal = new Set(notes.filter((n) => n.scope === "personal").map((n) => n.path));
2205
+ const sources = r.findings.map((f) => f.source).filter((s) => s.path && !personal.has(s.path));
2131
2206
  recordActivity({
2132
2207
  repo: repoPlace(repo).key,
2133
2208
  kind: "check",
2134
2209
  asker: me,
2135
2210
  session: sessionKey(repo) ?? `${me}-${new Date().toISOString().slice(0, 10)}`,
2136
- query: "",
2137
- files,
2138
- shown_paths: [...new Set(sources.map((s) => s.path.slice(s.path.lastIndexOf("/") + 1)))],
2211
+ file_count: files.length,
2212
+ shown_paths: [...new Set(sources.map((s) => learningFileName(s.path)))],
2139
2213
  shown_authors: [...new Set(sources.map((s) => s.author ?? ""))],
2140
2214
  at: new Date().toISOString(),
2141
2215
  });
@@ -2156,23 +2230,28 @@ async function runTeamCheck(repo, opts) {
2156
2230
  function printCheck(r) {
2157
2231
  console.log(`checking ${r.files} changed file${r.files === 1 ? "" : "s"} against ${r.notes} learning${r.notes === 1 ? "" : "s"} and ${r.skills} skill${r.skills === 1 ? "" : "s"}\n`);
2158
2232
  const conflicts = r.findings.filter((f) => f.verdict === "conflict");
2159
- for (const f of [...conflicts, ...r.findings.filter((x) => x.verdict !== "conflict")]) {
2160
- const mark = f.verdict === "conflict" ? "✗" : "✓";
2233
+ const leads = r.findings.filter((f) => f.verdict === "lead");
2234
+ for (const f of [...conflicts, ...leads, ...r.findings.filter((x) => x.verdict === "follows")]) {
2235
+ const mark = f.verdict === "conflict" ? "✗" : f.verdict === "lead" ? "●" : "✓";
2161
2236
  console.log(`${mark} ${f.file}${f.line ? `:${f.line}` : ""}`);
2162
2237
  console.log(` ${f.summary}`);
2238
+ if (f.why)
2239
+ console.log(` worth a look, can't fail the check: ${f.why}`);
2240
+ if (f.replacedBy)
2241
+ console.log(` this branch replaces it with ${f.replacedBy.id} ("${f.replacedBy.title}"), which counts once it's on the base branch: until then, say why in the PR`);
2163
2242
  const s = f.source;
2164
- if (f.verdict === "conflict" && s.quote) {
2243
+ if (f.verdict !== "follows" && s.quote) {
2165
2244
  const who = [s.author, s.date ? shortDate(s.date) : ""].filter(Boolean).join(" · ");
2166
2245
  for (const l of wrap(`${who ? `${who}: ` : ""}${s.quote}`, 70, " "))
2167
2246
  console.log(` ${l.trimStart()}`);
2168
2247
  }
2169
2248
  }
2170
2249
  if (r.findings.length === 0)
2171
- console.log("✓ nothing the team has learned bears on this change");
2250
+ console.log("✓ no learning bears on this change");
2172
2251
  console.log("");
2173
2252
  console.log(conflicts.length
2174
2253
  ? `· ${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"} · fix ${conflicts.length === 1 ? "it" : "them"}, or say why in the commit message or PR description`
2175
- : "· no conflicts");
2254
+ : `· no conflicts${leads.length ? ` · ${leads.length} worth a look` : ""}`);
2176
2255
  }
2177
2256
  /** Tokens saved by this repo's agent sessions touched in the last 7 days. */
2178
2257
  function savedThisWeek(repo, now = Date.now()) {
@@ -2214,7 +2293,8 @@ function statusCommand(name = "status") {
2214
2293
  const g = graph ? await engineFrom().checkGraph(repo) : null;
2215
2294
  const fresh = g && !g.missing ? g.ok : null;
2216
2295
  const kept = keepsNotes(repo);
2217
- const all = kept ? listNotes(repo) : [];
2296
+ // Only what still holds: a retired, held or replaced learning reaches no agent.
2297
+ const all = kept ? liveLearnings(listNotes(repo)) : [];
2218
2298
  const inRepo = all.filter((n) => n.scope === "repo" && !n.ref).length;
2219
2299
  const onBranches = all.filter((n) => n.ref).length;
2220
2300
  const personal = all.filter((n) => n.scope === "personal").length;
@@ -2239,7 +2319,7 @@ function statusCommand(name = "status") {
2239
2319
  else
2240
2320
  row("code map", `✓ ${fmt(graph.meta.nodeCount)} nodes · in sync with the code`);
2241
2321
  if (kept) {
2242
- const parts = [`${inRepo} for the team in ${LEARNINGS_DIR}/`];
2322
+ const parts = [`${inRepo} added to this repo, in ${LEARNINGS_DIR}/`];
2243
2323
  if (onBranches)
2244
2324
  parts.push(`${onBranches} on teammates' branches`);
2245
2325
  if (personal)
@@ -2250,9 +2330,15 @@ function statusCommand(name = "status") {
2250
2330
  if (cloudEnabled())
2251
2331
  row("workspace", "not connected · Claude offers a link once there are learnings to share");
2252
2332
  }
2333
+ else if (cloud.via === "connect") {
2334
+ row("workspace", `${cloud.workspace.name} · gets a summary of the repos you run ${cmd("graft init")} --connect in · ${cloud.workspace.url}`);
2335
+ }
2253
2336
  else
2254
2337
  row("workspace", `${cloud.workspace.name} · ${cloud.repos.length} repo${cloud.repos.length === 1 ? "" : "s"}${shared ? ", this one included" : `, not this one (${cmd("graft workspace add")})`} · ${cloud.workspace.url}`);
2255
2338
  row("this week", saved > 0 ? `~${shortCount(saved)} tokens saved` : "nothing saved yet");
2339
+ // Phase 2 (4.17, D137): Codex's trust of Trail's hooks, and why it keeps nothing new there yet.
2340
+ for (const l of codexStatusLines())
2341
+ row("codex", l);
2256
2342
  });
2257
2343
  }
2258
2344
  if (TRAIL) {
@@ -2263,7 +2349,7 @@ if (TRAIL) {
2263
2349
  program.addCommand(c.helpGroup(GROUP.cloud), { hidden: !cloudEnabled() });
2264
2350
  program.addCommand(statusCommand().helpGroup(GROUP.setup));
2265
2351
  // Help lists the groups in this order, and the commands within each.
2266
- const order = ["ask", "grep", "skeleton", "callers", "map", "note", "check", "team", "learn", "skills", "init", "build", "status", "upgrade", "uninstall", "telemetry", "workspace", "login", "logout", "blast"];
2352
+ const order = ["ask", "grep", "skeleton", "callers", "map", "note", "check", "team", "skills", "init", "build", "status", "upgrade", "uninstall", "telemetry", "workspace", "login", "logout", "blast"];
2267
2353
  const rank = (c) => {
2268
2354
  const i = order.indexOf(c.name());
2269
2355
  return i === -1 ? order.length : i;
@@ -2277,15 +2363,14 @@ if (TRAIL) {
2277
2363
  callers: "who calls it, or what it calls with --direction out",
2278
2364
  map: "directory clusters, hubs and hotspots",
2279
2365
  note: "save what this session worked out, as a learning in .trail/",
2280
- learn: "turn a correction into a takeaway on a skill",
2281
- skills: "list skills, show one, fold takeaways in, publish to the repo",
2366
+ skills: "the steps that worked, kept as skills when you say so · on, off, show, forget",
2282
2367
  init: "wire trail into your agents",
2283
2368
  build: "rebuild the code map · --check fails if it's stale",
2284
2369
  status: "code map, learnings and tokens saved",
2285
2370
  upgrade: "install the latest trail",
2286
2371
  uninstall: "remove everything trail or graft wrote to this repo",
2287
2372
  telemetry: "show or turn off the anonymous usage stats",
2288
- check: "check your diff against everything the team has learned",
2373
+ check: "check your diff against this repo's learnings and yours",
2289
2374
  team: "what the team learned this week, and where work overlaps",
2290
2375
  workspace: "your workspace's repos, team page and invite link",
2291
2376
  login: "connect a Trail workspace (Claude offers you the link)",