@nanonets/graft 0.24.1 → 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 (480) hide show
  1. package/README.md +26 -21
  2. package/dist/blast/render.js +1 -1
  3. package/dist/blast/render.js.map +1 -1
  4. package/dist/brand.js +1 -1
  5. package/dist/brand.js.map +1 -1
  6. package/dist/check/learning-edits.d.ts +18 -3
  7. package/dist/check/learning-edits.d.ts.map +1 -1
  8. package/dist/check/learning-edits.js +54 -10
  9. package/dist/check/learning-edits.js.map +1 -1
  10. package/dist/check/local-check.d.ts +7 -3
  11. package/dist/check/local-check.d.ts.map +1 -1
  12. package/dist/check/local-check.js +55 -11
  13. package/dist/check/local-check.js.map +1 -1
  14. package/dist/check/types.d.ts +11 -2
  15. package/dist/check/types.d.ts.map +1 -1
  16. package/dist/claude/format.d.ts +11 -7
  17. package/dist/claude/format.d.ts.map +1 -1
  18. package/dist/claude/format.js +50 -28
  19. package/dist/claude/format.js.map +1 -1
  20. package/dist/claude/hook-io.js +1 -1
  21. package/dist/claude/hook-io.js.map +1 -1
  22. package/dist/claude/hook-layers.d.ts +4 -2
  23. package/dist/claude/hook-layers.d.ts.map +1 -1
  24. package/dist/claude/hook-layers.js +21 -4
  25. package/dist/claude/hook-layers.js.map +1 -1
  26. package/dist/claude/hooks.d.ts +9 -32
  27. package/dist/claude/hooks.d.ts.map +1 -1
  28. package/dist/claude/hooks.js +154 -350
  29. package/dist/claude/hooks.js.map +1 -1
  30. package/dist/claude/init.d.ts.map +1 -1
  31. package/dist/claude/init.js +56 -18
  32. package/dist/claude/init.js.map +1 -1
  33. package/dist/claude/note-signals.d.ts +6 -0
  34. package/dist/claude/note-signals.d.ts.map +1 -1
  35. package/dist/claude/note-signals.js +14 -3
  36. package/dist/claude/note-signals.js.map +1 -1
  37. package/dist/claude/post-bash.d.ts +26 -0
  38. package/dist/claude/post-bash.d.ts.map +1 -0
  39. package/dist/claude/post-bash.js +191 -0
  40. package/dist/claude/post-bash.js.map +1 -0
  41. package/dist/claude/pre-bash-skills.d.ts +21 -0
  42. package/dist/claude/pre-bash-skills.d.ts.map +1 -0
  43. package/dist/claude/pre-bash-skills.js +253 -0
  44. package/dist/claude/pre-bash-skills.js.map +1 -0
  45. package/dist/claude/pre-bash.d.ts +50 -0
  46. package/dist/claude/pre-bash.d.ts.map +1 -0
  47. package/dist/claude/pre-bash.js +245 -0
  48. package/dist/claude/pre-bash.js.map +1 -0
  49. package/dist/claude/prompt-consent.d.ts +19 -0
  50. package/dist/claude/prompt-consent.d.ts.map +1 -0
  51. package/dist/claude/prompt-consent.js +14 -0
  52. package/dist/claude/prompt-consent.js.map +1 -0
  53. package/dist/claude/prompt-hook.d.ts +76 -0
  54. package/dist/claude/prompt-hook.d.ts.map +1 -0
  55. package/dist/claude/prompt-hook.js +303 -0
  56. package/dist/claude/prompt-hook.js.map +1 -0
  57. package/dist/claude/prompt-skills.d.ts +19 -0
  58. package/dist/claude/prompt-skills.d.ts.map +1 -0
  59. package/dist/claude/prompt-skills.js +305 -0
  60. package/dist/claude/prompt-skills.js.map +1 -0
  61. package/dist/claude/session-metrics.js +4 -4
  62. package/dist/claude/session-metrics.js.map +1 -1
  63. package/dist/claude/settings-merge.d.ts +10 -0
  64. package/dist/claude/settings-merge.d.ts.map +1 -1
  65. package/dist/claude/settings-merge.js +46 -17
  66. package/dist/claude/settings-merge.js.map +1 -1
  67. package/dist/claude/shim-template.d.ts.map +1 -1
  68. package/dist/claude/shim-template.js +86 -28
  69. package/dist/claude/shim-template.js.map +1 -1
  70. package/dist/claude/skill-template.d.ts +2 -0
  71. package/dist/claude/skill-template.d.ts.map +1 -1
  72. package/dist/claude/skill-template.js +53 -34
  73. package/dist/claude/skill-template.js.map +1 -1
  74. package/dist/claude/skill-use.d.ts +16 -0
  75. package/dist/claude/skill-use.d.ts.map +1 -0
  76. package/dist/claude/skill-use.js +78 -0
  77. package/dist/claude/skill-use.js.map +1 -0
  78. package/dist/claude/skills-wiring.d.ts +26 -0
  79. package/dist/claude/skills-wiring.d.ts.map +1 -0
  80. package/dist/claude/skills-wiring.js +110 -0
  81. package/dist/claude/skills-wiring.js.map +1 -0
  82. package/dist/claude/start-init.d.ts +13 -0
  83. package/dist/claude/start-init.d.ts.map +1 -0
  84. package/dist/claude/start-init.js +75 -0
  85. package/dist/claude/start-init.js.map +1 -0
  86. package/dist/claude/start-skills.d.ts +31 -0
  87. package/dist/claude/start-skills.d.ts.map +1 -0
  88. package/dist/claude/start-skills.js +274 -0
  89. package/dist/claude/start-skills.js.map +1 -0
  90. package/dist/claude/state.d.ts +19 -7
  91. package/dist/claude/state.d.ts.map +1 -1
  92. package/dist/claude/state.js +59 -4
  93. package/dist/claude/state.js.map +1 -1
  94. package/dist/claude/stop-skills.d.ts +23 -0
  95. package/dist/claude/stop-skills.d.ts.map +1 -0
  96. package/dist/claude/stop-skills.js +233 -0
  97. package/dist/claude/stop-skills.js.map +1 -0
  98. package/dist/claude/stop.d.ts +23 -0
  99. package/dist/claude/stop.d.ts.map +1 -0
  100. package/dist/claude/stop.js +219 -0
  101. package/dist/claude/stop.js.map +1 -0
  102. package/dist/claude/tally.d.ts +29 -41
  103. package/dist/claude/tally.d.ts.map +1 -1
  104. package/dist/claude/tally.js +111 -165
  105. package/dist/claude/tally.js.map +1 -1
  106. package/dist/claude/walls-hook.d.ts +52 -0
  107. package/dist/claude/walls-hook.d.ts.map +1 -0
  108. package/dist/claude/walls-hook.js +279 -0
  109. package/dist/claude/walls-hook.js.map +1 -0
  110. package/dist/cli-epilogue.d.ts.map +1 -1
  111. package/dist/cli-epilogue.js +24 -14
  112. package/dist/cli-epilogue.js.map +1 -1
  113. package/dist/cli-meta.js +3 -3
  114. package/dist/cli-meta.js.map +1 -1
  115. package/dist/cli-picker.d.ts.map +1 -1
  116. package/dist/cli-picker.js +8 -6
  117. package/dist/cli-picker.js.map +1 -1
  118. package/dist/cli.d.ts.map +1 -1
  119. package/dist/cli.js +238 -402
  120. package/dist/cli.js.map +1 -1
  121. package/dist/cloud/head-learnings.d.ts +13 -0
  122. package/dist/cloud/head-learnings.d.ts.map +1 -0
  123. package/dist/cloud/head-learnings.js +84 -0
  124. package/dist/cloud/head-learnings.js.map +1 -0
  125. package/dist/cloud/lookup-activity.d.ts +5 -3
  126. package/dist/cloud/lookup-activity.d.ts.map +1 -1
  127. package/dist/cloud/lookup-activity.js +8 -8
  128. package/dist/cloud/lookup-activity.js.map +1 -1
  129. package/dist/cloud/nudge.d.ts +15 -7
  130. package/dist/cloud/nudge.d.ts.map +1 -1
  131. package/dist/cloud/nudge.js +42 -33
  132. package/dist/cloud/nudge.js.map +1 -1
  133. package/dist/cloud/onboarding.d.ts +37 -1
  134. package/dist/cloud/onboarding.d.ts.map +1 -1
  135. package/dist/cloud/onboarding.js +77 -1
  136. package/dist/cloud/onboarding.js.map +1 -1
  137. package/dist/cloud/other-repos.d.ts +39 -0
  138. package/dist/cloud/other-repos.d.ts.map +1 -0
  139. package/dist/cloud/other-repos.js +137 -0
  140. package/dist/cloud/other-repos.js.map +1 -0
  141. package/dist/cloud/pair.d.ts +17 -7
  142. package/dist/cloud/pair.d.ts.map +1 -1
  143. package/dist/cloud/pair.js +81 -60
  144. package/dist/cloud/pair.js.map +1 -1
  145. package/dist/cloud/workspace-sync.d.ts +67 -11
  146. package/dist/cloud/workspace-sync.d.ts.map +1 -1
  147. package/dist/cloud/workspace-sync.js +299 -55
  148. package/dist/cloud/workspace-sync.js.map +1 -1
  149. package/dist/cloud/workspace.d.ts +74 -10
  150. package/dist/cloud/workspace.d.ts.map +1 -1
  151. package/dist/cloud/workspace.js +96 -8
  152. package/dist/cloud/workspace.js.map +1 -1
  153. package/dist/hosts/claude-global.js +1 -1
  154. package/dist/hosts/claude-global.js.map +1 -1
  155. package/dist/hosts/codex-hooks.d.ts +6 -0
  156. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  157. package/dist/hosts/codex-hooks.js +89 -16
  158. package/dist/hosts/codex-hooks.js.map +1 -1
  159. package/dist/hosts/codex-trust.d.ts +28 -0
  160. package/dist/hosts/codex-trust.d.ts.map +1 -0
  161. package/dist/hosts/codex-trust.js +280 -0
  162. package/dist/hosts/codex-trust.js.map +1 -0
  163. package/dist/hosts/cursor-hooks.d.ts.map +1 -1
  164. package/dist/hosts/cursor-hooks.js +3 -1
  165. package/dist/hosts/cursor-hooks.js.map +1 -1
  166. package/dist/init/candidates.d.ts +60 -0
  167. package/dist/init/candidates.d.ts.map +1 -0
  168. package/dist/init/candidates.js +202 -0
  169. package/dist/init/candidates.js.map +1 -0
  170. package/dist/init/claude-call.d.ts +51 -0
  171. package/dist/init/claude-call.d.ts.map +1 -0
  172. package/dist/init/claude-call.js +199 -0
  173. package/dist/init/claude-call.js.map +1 -0
  174. package/dist/init/discover-worker.d.ts +18 -0
  175. package/dist/init/discover-worker.d.ts.map +1 -0
  176. package/dist/init/discover-worker.js +111 -0
  177. package/dist/init/discover-worker.js.map +1 -0
  178. package/dist/init/handover.d.ts +16 -0
  179. package/dist/init/handover.d.ts.map +1 -0
  180. package/dist/init/handover.js +92 -0
  181. package/dist/init/handover.js.map +1 -0
  182. package/dist/init/howtos.d.ts +20 -0
  183. package/dist/init/howtos.d.ts.map +1 -0
  184. package/dist/init/howtos.js +90 -0
  185. package/dist/init/howtos.js.map +1 -0
  186. package/dist/init/init-record.d.ts +134 -0
  187. package/dist/init/init-record.d.ts.map +1 -0
  188. package/dist/init/init-record.js +339 -0
  189. package/dist/init/init-record.js.map +1 -0
  190. package/dist/init/install-paths.d.ts +6 -0
  191. package/dist/init/install-paths.d.ts.map +1 -0
  192. package/dist/init/install-paths.js +87 -0
  193. package/dist/init/install-paths.js.map +1 -0
  194. package/dist/init/keep-answer.d.ts +43 -0
  195. package/dist/init/keep-answer.d.ts.map +1 -0
  196. package/dist/init/keep-answer.js +118 -0
  197. package/dist/init/keep-answer.js.map +1 -0
  198. package/dist/init/keep-gate.d.ts +30 -0
  199. package/dist/init/keep-gate.d.ts.map +1 -0
  200. package/dist/init/keep-gate.js +178 -0
  201. package/dist/init/keep-gate.js.map +1 -0
  202. package/dist/init/keep-list.d.ts +67 -0
  203. package/dist/init/keep-list.d.ts.map +1 -0
  204. package/dist/init/keep-list.js +232 -0
  205. package/dist/init/keep-list.js.map +1 -0
  206. package/dist/init/keep.d.ts +52 -0
  207. package/dist/init/keep.d.ts.map +1 -0
  208. package/dist/init/keep.js +393 -0
  209. package/dist/init/keep.js.map +1 -0
  210. package/dist/init/look-back-mine.d.ts +37 -0
  211. package/dist/init/look-back-mine.d.ts.map +1 -0
  212. package/dist/init/look-back-mine.js +215 -0
  213. package/dist/init/look-back-mine.js.map +1 -0
  214. package/dist/init/look-back.d.ts +81 -0
  215. package/dist/init/look-back.d.ts.map +1 -0
  216. package/dist/init/look-back.js +170 -0
  217. package/dist/init/look-back.js.map +1 -0
  218. package/dist/init/miners.d.ts +46 -0
  219. package/dist/init/miners.d.ts.map +1 -0
  220. package/dist/init/miners.js +141 -0
  221. package/dist/init/miners.js.map +1 -0
  222. package/dist/init/name-call.d.ts +37 -0
  223. package/dist/init/name-call.d.ts.map +1 -0
  224. package/dist/init/name-call.js +151 -0
  225. package/dist/init/name-call.js.map +1 -0
  226. package/dist/init/plain-file.d.ts.map +1 -1
  227. package/dist/init/plain-file.js +3 -0
  228. package/dist/init/plain-file.js.map +1 -1
  229. package/dist/init/prefs.d.ts +70 -0
  230. package/dist/init/prefs.d.ts.map +1 -0
  231. package/dist/init/prefs.js +282 -0
  232. package/dist/init/prefs.js.map +1 -0
  233. package/dist/init/relay.d.ts +15 -0
  234. package/dist/init/relay.d.ts.map +1 -0
  235. package/dist/init/relay.js +15 -0
  236. package/dist/init/relay.js.map +1 -0
  237. package/dist/init/replay-runs.d.ts +121 -0
  238. package/dist/init/replay-runs.d.ts.map +1 -0
  239. package/dist/init/replay-runs.js +457 -0
  240. package/dist/init/replay-runs.js.map +1 -0
  241. package/dist/init/replay-tamper.d.ts +18 -0
  242. package/dist/init/replay-tamper.d.ts.map +1 -0
  243. package/dist/init/replay-tamper.js +87 -0
  244. package/dist/init/replay-tamper.js.map +1 -0
  245. package/dist/init/review-screen.d.ts +74 -0
  246. package/dist/init/review-screen.d.ts.map +1 -0
  247. package/dist/init/review-screen.js +258 -0
  248. package/dist/init/review-screen.js.map +1 -0
  249. package/dist/init/review-tty.d.ts +29 -0
  250. package/dist/init/review-tty.d.ts.map +1 -0
  251. package/dist/init/review-tty.js +218 -0
  252. package/dist/init/review-tty.js.map +1 -0
  253. package/dist/init/review.d.ts +12 -0
  254. package/dist/init/review.d.ts.map +1 -0
  255. package/dist/init/review.js +12 -0
  256. package/dist/init/review.js.map +1 -0
  257. package/dist/init/run.d.ts +87 -0
  258. package/dist/init/run.d.ts.map +1 -0
  259. package/dist/init/run.js +347 -0
  260. package/dist/init/run.js.map +1 -0
  261. package/dist/init/sessions.d.ts +63 -0
  262. package/dist/init/sessions.d.ts.map +1 -0
  263. package/dist/init/sessions.js +377 -0
  264. package/dist/init/sessions.js.map +1 -0
  265. package/dist/init/transcript.d.ts +81 -0
  266. package/dist/init/transcript.d.ts.map +1 -0
  267. package/dist/init/transcript.js +216 -0
  268. package/dist/init/transcript.js.map +1 -0
  269. package/dist/init/types.d.ts +139 -0
  270. package/dist/init/types.d.ts.map +1 -0
  271. package/dist/init/types.js +2 -0
  272. package/dist/init/types.js.map +1 -0
  273. package/dist/notes/cold-start.d.ts +5 -213
  274. package/dist/notes/cold-start.d.ts.map +1 -1
  275. package/dist/notes/cold-start.js +5 -654
  276. package/dist/notes/cold-start.js.map +1 -1
  277. package/dist/notes/edit-overlap.d.ts.map +1 -1
  278. package/dist/notes/edit-overlap.js +4 -1
  279. package/dist/notes/edit-overlap.js.map +1 -1
  280. package/dist/notes/notes.d.ts +8 -4
  281. package/dist/notes/notes.d.ts.map +1 -1
  282. package/dist/notes/notes.js +9 -7
  283. package/dist/notes/notes.js.map +1 -1
  284. package/dist/notes/pr-reviews.d.ts +4 -0
  285. package/dist/notes/pr-reviews.d.ts.map +1 -1
  286. package/dist/notes/pr-reviews.js +6 -5
  287. package/dist/notes/pr-reviews.js.map +1 -1
  288. package/dist/notes/repo-trail.d.ts +1 -1
  289. package/dist/notes/repo-trail.d.ts.map +1 -1
  290. package/dist/notes/repo-trail.js +24 -4
  291. package/dist/notes/repo-trail.js.map +1 -1
  292. package/dist/search/grep.d.ts.map +1 -1
  293. package/dist/search/grep.js +4 -2
  294. package/dist/search/grep.js.map +1 -1
  295. package/dist/search/user-regexp.d.ts +26 -0
  296. package/dist/search/user-regexp.d.ts.map +1 -0
  297. package/dist/search/user-regexp.js +150 -0
  298. package/dist/search/user-regexp.js.map +1 -0
  299. package/dist/skills/anchors-cli.d.ts +5 -0
  300. package/dist/skills/anchors-cli.d.ts.map +1 -1
  301. package/dist/skills/anchors-cli.js +7 -2
  302. package/dist/skills/anchors-cli.js.map +1 -1
  303. package/dist/skills/anchors-wide.js +2 -2
  304. package/dist/skills/anchors-wide.js.map +1 -1
  305. package/dist/skills/cli-skills-1b.d.ts +17 -0
  306. package/dist/skills/cli-skills-1b.d.ts.map +1 -0
  307. package/dist/skills/cli-skills-1b.js +166 -0
  308. package/dist/skills/cli-skills-1b.js.map +1 -0
  309. package/dist/skills/cli-skills-2.d.ts +35 -0
  310. package/dist/skills/cli-skills-2.d.ts.map +1 -0
  311. package/dist/skills/cli-skills-2.js +129 -0
  312. package/dist/skills/cli-skills-2.js.map +1 -0
  313. package/dist/skills/cli-skills-report-1b.d.ts +49 -0
  314. package/dist/skills/cli-skills-report-1b.d.ts.map +1 -0
  315. package/dist/skills/cli-skills-report-1b.js +191 -0
  316. package/dist/skills/cli-skills-report-1b.js.map +1 -0
  317. package/dist/skills/cli-skills-report.d.ts +86 -0
  318. package/dist/skills/cli-skills-report.d.ts.map +1 -0
  319. package/dist/skills/cli-skills-report.js +121 -0
  320. package/dist/skills/cli-skills-report.js.map +1 -0
  321. package/dist/skills/cli-skills.d.ts +30 -0
  322. package/dist/skills/cli-skills.d.ts.map +1 -0
  323. package/dist/skills/cli-skills.js +379 -0
  324. package/dist/skills/cli-skills.js.map +1 -0
  325. package/dist/skills/compile-split.d.ts.map +1 -1
  326. package/dist/skills/compile-split.js +18 -0
  327. package/dist/skills/compile-split.js.map +1 -1
  328. package/dist/skills/compile-words.d.ts +4 -1
  329. package/dist/skills/compile-words.d.ts.map +1 -1
  330. package/dist/skills/compile-words.js +68 -8
  331. package/dist/skills/compile-words.js.map +1 -1
  332. package/dist/skills/compile.d.ts +7 -0
  333. package/dist/skills/compile.d.ts.map +1 -1
  334. package/dist/skills/compile.js +17 -0
  335. package/dist/skills/compile.js.map +1 -1
  336. package/dist/skills/credit.d.ts +86 -0
  337. package/dist/skills/credit.d.ts.map +1 -0
  338. package/dist/skills/credit.js +259 -0
  339. package/dist/skills/credit.js.map +1 -0
  340. package/dist/skills/fold-gated.d.ts +3 -0
  341. package/dist/skills/fold-gated.d.ts.map +1 -0
  342. package/dist/skills/fold-gated.js +78 -0
  343. package/dist/skills/fold-gated.js.map +1 -0
  344. package/dist/skills/fold.d.ts +87 -0
  345. package/dist/skills/fold.d.ts.map +1 -0
  346. package/dist/skills/fold.js +348 -0
  347. package/dist/skills/fold.js.map +1 -0
  348. package/dist/skills/follow-learnings.d.ts +128 -0
  349. package/dist/skills/follow-learnings.d.ts.map +1 -0
  350. package/dist/skills/follow-learnings.js +278 -0
  351. package/dist/skills/follow-learnings.js.map +1 -0
  352. package/dist/skills/learning-steps.d.ts +43 -0
  353. package/dist/skills/learning-steps.d.ts.map +1 -0
  354. package/dist/skills/learning-steps.js +294 -0
  355. package/dist/skills/learning-steps.js.map +1 -0
  356. package/dist/skills/lesson-gate.d.ts +25 -0
  357. package/dist/skills/lesson-gate.d.ts.map +1 -0
  358. package/dist/skills/lesson-gate.js +316 -0
  359. package/dist/skills/lesson-gate.js.map +1 -0
  360. package/dist/skills/offer-drift.d.ts +35 -0
  361. package/dist/skills/offer-drift.d.ts.map +1 -0
  362. package/dist/skills/offer-drift.js +98 -0
  363. package/dist/skills/offer-drift.js.map +1 -0
  364. package/dist/skills/offer-lesson.d.ts +31 -0
  365. package/dist/skills/offer-lesson.d.ts.map +1 -0
  366. package/dist/skills/offer-lesson.js +123 -0
  367. package/dist/skills/offer-lesson.js.map +1 -0
  368. package/dist/skills/offer-lines.d.ts +40 -0
  369. package/dist/skills/offer-lines.d.ts.map +1 -0
  370. package/dist/skills/offer-lines.js +80 -0
  371. package/dist/skills/offer-lines.js.map +1 -0
  372. package/dist/skills/offer-moment.d.ts +102 -0
  373. package/dist/skills/offer-moment.d.ts.map +1 -0
  374. package/dist/skills/offer-moment.js +260 -0
  375. package/dist/skills/offer-moment.js.map +1 -0
  376. package/dist/skills/offer-repair.d.ts +36 -0
  377. package/dist/skills/offer-repair.d.ts.map +1 -0
  378. package/dist/skills/offer-repair.js +212 -0
  379. package/dist/skills/offer-repair.js.map +1 -0
  380. package/dist/skills/offer-retire.d.ts +53 -0
  381. package/dist/skills/offer-retire.d.ts.map +1 -0
  382. package/dist/skills/offer-retire.js +149 -0
  383. package/dist/skills/offer-retire.js.map +1 -0
  384. package/dist/skills/offer-update.d.ts +54 -0
  385. package/dist/skills/offer-update.d.ts.map +1 -0
  386. package/dist/skills/offer-update.js +115 -0
  387. package/dist/skills/offer-update.js.map +1 -0
  388. package/dist/skills/offer-words-1b.d.ts +32 -0
  389. package/dist/skills/offer-words-1b.d.ts.map +1 -0
  390. package/dist/skills/offer-words-1b.js +80 -0
  391. package/dist/skills/offer-words-1b.js.map +1 -0
  392. package/dist/skills/offer-words-2.d.ts +123 -0
  393. package/dist/skills/offer-words-2.d.ts.map +1 -0
  394. package/dist/skills/offer-words-2.js +194 -0
  395. package/dist/skills/offer-words-2.js.map +1 -0
  396. package/dist/skills/offer-words.d.ts +65 -0
  397. package/dist/skills/offer-words.d.ts.map +1 -0
  398. package/dist/skills/offer-words.js +205 -0
  399. package/dist/skills/offer-words.js.map +1 -0
  400. package/dist/skills/offers.d.ts +66 -0
  401. package/dist/skills/offers.d.ts.map +1 -0
  402. package/dist/skills/offers.js +284 -0
  403. package/dist/skills/offers.js.map +1 -0
  404. package/dist/skills/runblock.d.ts +2 -0
  405. package/dist/skills/runblock.d.ts.map +1 -1
  406. package/dist/skills/runblock.js +11 -6
  407. package/dist/skills/runblock.js.map +1 -1
  408. package/dist/skills/save-gate.d.ts +64 -0
  409. package/dist/skills/save-gate.d.ts.map +1 -0
  410. package/dist/skills/save-gate.js +354 -0
  411. package/dist/skills/save-gate.js.map +1 -0
  412. package/dist/skills/save-verify.d.ts +59 -0
  413. package/dist/skills/save-verify.d.ts.map +1 -0
  414. package/dist/skills/save-verify.js +390 -0
  415. package/dist/skills/save-verify.js.map +1 -0
  416. package/dist/skills/settings.d.ts +12 -2
  417. package/dist/skills/settings.d.ts.map +1 -1
  418. package/dist/skills/settings.js +24 -2
  419. package/dist/skills/settings.js.map +1 -1
  420. package/dist/skills/skills.d.ts +26 -8
  421. package/dist/skills/skills.d.ts.map +1 -1
  422. package/dist/skills/skills.js +105 -52
  423. package/dist/skills/skills.js.map +1 -1
  424. package/dist/skills/types.d.ts +8 -0
  425. package/dist/skills/types.d.ts.map +1 -1
  426. package/dist/skills/update-gate.d.ts +36 -0
  427. package/dist/skills/update-gate.d.ts.map +1 -0
  428. package/dist/skills/update-gate.js +357 -0
  429. package/dist/skills/update-gate.js.map +1 -0
  430. package/dist/skills/update-plan.d.ts +40 -0
  431. package/dist/skills/update-plan.d.ts.map +1 -0
  432. package/dist/skills/update-plan.js +196 -0
  433. package/dist/skills/update-plan.js.map +1 -0
  434. package/dist/skills/value-line.d.ts +94 -0
  435. package/dist/skills/value-line.d.ts.map +1 -0
  436. package/dist/skills/value-line.js +355 -0
  437. package/dist/skills/value-line.js.map +1 -0
  438. package/dist/telemetry/contract.d.ts +15 -1
  439. package/dist/telemetry/contract.d.ts.map +1 -1
  440. package/dist/telemetry/contract.js +80 -11
  441. package/dist/telemetry/contract.js.map +1 -1
  442. package/dist/telemetry/gate.js +4 -4
  443. package/dist/telemetry/gate.js.map +1 -1
  444. package/dist/telemetry/index.d.ts +1 -1
  445. package/dist/telemetry/index.d.ts.map +1 -1
  446. package/dist/telemetry/index.js +1 -1
  447. package/dist/telemetry/index.js.map +1 -1
  448. package/dist/telemetry/notice.js +5 -5
  449. package/dist/telemetry/notice.js.map +1 -1
  450. package/dist/telemetry/queue.d.ts.map +1 -1
  451. package/dist/telemetry/queue.js +7 -7
  452. package/dist/telemetry/queue.js.map +1 -1
  453. package/dist/telemetry/track.d.ts.map +1 -1
  454. package/dist/telemetry/track.js +4 -3
  455. package/dist/telemetry/track.js.map +1 -1
  456. package/dist/trace/checks.d.ts.map +1 -1
  457. package/dist/trace/checks.js +10 -2
  458. package/dist/trace/checks.js.map +1 -1
  459. package/dist/trace/gate-file.d.ts +1 -1
  460. package/dist/trace/gate-file.d.ts.map +1 -1
  461. package/dist/trace/gate-file.js +3 -3
  462. package/dist/trace/gate-file.js.map +1 -1
  463. package/dist/upkeep-run.d.ts.map +1 -1
  464. package/dist/upkeep-run.js +2 -23
  465. package/dist/upkeep-run.js.map +1 -1
  466. package/dist/upkeep.d.ts.map +1 -1
  467. package/dist/upkeep.js +2 -3
  468. package/dist/upkeep.js.map +1 -1
  469. package/dist/util/private-fs.d.ts +6 -0
  470. package/dist/util/private-fs.d.ts.map +1 -1
  471. package/dist/util/private-fs.js +9 -1
  472. package/dist/util/private-fs.js.map +1 -1
  473. package/dist/util/state.d.ts.map +1 -1
  474. package/dist/util/state.js +17 -3
  475. package/dist/util/state.js.map +1 -1
  476. package/package.json +1 -1
  477. package/dist/skills/found-skill.d.ts +0 -35
  478. package/dist/skills/found-skill.d.ts.map +0 -1
  479. package/dist/skills/found-skill.js +0 -66
  480. package/dist/skills/found-skill.js.map +0 -1
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,7 +40,7 @@ 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";
44
45
  import { installBrainsServer, removeBrainsServer } from "./hosts/claude-global.js";
45
46
  import { homedir } from "node:os";
@@ -49,29 +50,31 @@ import { patchBuildConfig } from "./util/state.js";
49
50
  import { normalizePathPrefix } from "./util/paths.js";
50
51
  import { latestSession, formatSessionStats, sessionInputRate } from "./claude/session-metrics.js";
51
52
  import { listSessionIds, readSession, sessionDir } from "./claude/state.js";
52
- 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";
53
- 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";
54
54
  import { aboutTerms, currentSessionCost } from "./notes/session-cost.js";
55
- import { beginPastRead, finishPastRead } from "./notes/cold-start.js";
56
- import { skippedSkillLines } from "./skills/found-skill.js";
57
- import { anchoredReviews, beginReviewRead, finishReviewRead } from "./notes/pr-reviews.js";
58
- 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
59
  import { finishPair, PairRun, validPairCode } from "./cloud/pair.js";
60
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";
61
63
  import { gateNote, namesRepoSymbol, symbolCandidates, trackedFiles } from "./notes/note-gate.js";
62
- import { confirm, fold, getSkill, learn, listSkills, publish, skillForAgent, skillName, skillStatus } from "./skills/skills.js";
63
64
  import { recordLookup } from "./cloud/lookup-activity.js";
64
65
  import { clearPending, cloudLines, loginLink, sessionKey, waitForConnect } from "./cloud/nudge.js";
65
- import { addRepo, clearCloud, cloudEnabled, fetchMe, fetchTeam, isError, readCloud, trailUrl, writeCloud } from "./cloud/workspace.js";
66
+ import { addRepo, clearCloud, cloudEnabled, fetchMe, fetchTeam, isError, readCloud, serverUrl, trailUrl, useServer, writeCloud } from "./cloud/workspace.js";
66
67
  import { claimConnectCode, connectAgain, offlineLine, learningsSummary, machineName, normalizeCode, OnboardingReporter, repoRef, teammatesChecked, teammatesFromGit, tokensOnOneLookup, } from "./cloud/onboarding.js";
67
- import { inWorkspace, maybeSyncInBackground, recordActivity, runSyncChild, syncRepo } from "./cloud/workspace-sync.js";
68
+ import { inWorkspace, maybeSyncInBackground, recordActivity, runSyncChild, syncRepo, workspaceLearnings } from "./cloud/workspace-sync.js";
68
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";
69
72
  import { formatOverlap, maybeRefreshBranches, overlapsWith, pushedWhen, readBranchCache, refreshBranches } from "./notes/branches.js";
70
73
  import { ensureRepoTrail, LEARNINGS_DIR } from "./notes/repo-trail.js";
71
74
  import { setInputRate } from "./context/savings.js";
72
75
  import { formatUpdateNudge, maybeRefreshInBackground, readStamp, readUpdateCache, refreshUpdateCache, wiredHostIds, wiringOpts, writeStamp } from "./upkeep.js";
73
76
  import { shouldMoveToTrail } from "./upkeep-run.js";
74
- 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";
75
78
  const program = new Command();
76
79
  const currentVersion = readCurrentVersion(import.meta.url);
77
80
  /**
@@ -85,7 +88,7 @@ const TRAIL = brand() === "trail";
85
88
  /** Help-screen groups under trail; graft keeps its one flat list. */
86
89
  const GROUP = {
87
90
  find: "Find code · your agent runs these:",
88
- memory: "Team memory:",
91
+ memory: "Learnings:",
89
92
  setup: "Setup:",
90
93
  cloud: "Workspace · your team's view, across repos:",
91
94
  ci: "CI:",
@@ -123,8 +126,8 @@ function noteHit(found) {
123
126
  program
124
127
  .name(brand())
125
128
  .description(TRAIL
126
- ? "Memory for your coding agents: a code map, and the learnings each session\n" +
127
- "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."
128
131
  : "Build a repo's context graph as linked markdown, and keep it in sync with the code.")
129
132
  .version(currentVersion, "-v, --version")
130
133
  .option("--dir <path>", "context graph directory (default: <repo>/graft)")
@@ -157,7 +160,7 @@ function warnUnsupportedExtensions(exts) {
157
160
  return;
158
161
  for (const e of bad) {
159
162
  const shown = e.trim().startsWith(".") ? e.trim() : `.${e.trim()}`;
160
- 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.`);
161
164
  }
162
165
  console.error(` supported: ${supportedExtensions().join(" ")}`);
163
166
  }
@@ -174,7 +177,7 @@ function queryRoot(dir) {
174
177
  return resolve(dir);
175
178
  const { root, levels } = nearestGraftRoot(process.cwd(), program.opts().dir);
176
179
  if (levels > 0)
177
- 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)}`);
178
181
  return root;
179
182
  }
180
183
  /**
@@ -197,7 +200,7 @@ async function refreshBefore(dir, opts) {
197
200
  }
198
201
  /** Attached to every query command: `--no-refresh` answers from the graph exactly
199
202
  * as it is on disk, no rebuild. */
200
- 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"];
201
204
  const VIZ_TABS = ["context", "code", "outline"];
202
205
  /**
203
206
  * `--tabs context,code` → the tabs to write into an exported page.
@@ -213,7 +216,7 @@ function parseTabs(raw) {
213
216
  const want = raw.split(",").map((t) => t.trim()).filter(Boolean);
214
217
  const bad = want.filter((t) => !VIZ_TABS.includes(t));
215
218
  if (bad.length > 0 || want.length === 0) {
216
- 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('", "')}"` : ""}`);
217
220
  process.exit(1);
218
221
  }
219
222
  return want;
@@ -223,7 +226,7 @@ function parseTabs(raw) {
223
226
  * not editorialize on stderr at startup (`mcp` runs its own upkeep at boot, and
224
227
  * `_update-check` IS the fetch).
225
228
  */
226
- const UPKEEP_SKIP = new Set(["version", "upgrade", "_update-check", "_trail-past-read", "mcp"]);
229
+ const UPKEEP_SKIP = new Set(["version", "upgrade", "_update-check", "mcp"]);
227
230
  /**
228
231
  * Every other command: top up the cached registry answer in the background and,
229
232
  * if a newer graft is out, say so once on stderr. This is what makes the CLI the
@@ -277,20 +280,6 @@ function spelling(action) {
277
280
  }
278
281
  // Hidden: spawned detached by maybeSyncInBackground when a workspace repo's
279
282
  // learnings changed, or uses are waiting to go. Running it by hand syncs now.
280
- // A repo that moved from graft to trail at session start reads its past
281
- // sessions and PR reviews here, detached, so the session doesn't wait on it.
282
- program
283
- .command("_trail-past-read", { hidden: true })
284
- .argument("<dir>")
285
- .action(async (dir) => {
286
- const repo = resolve(dir);
287
- const read = beginPastRead(repo);
288
- const reviews = beginReviewRead(repo);
289
- if (read)
290
- await finishPastRead(repo, read);
291
- if (reviews)
292
- await finishReviewRead(repo, reviews);
293
- });
294
283
  program
295
284
  .command("_trail-sync", { hidden: true })
296
285
  .description("internal: send this repo's learnings and the waiting uses to the Trail workspace")
@@ -317,7 +306,11 @@ program
317
306
  .description("internal: after `init --pair`, wait for the connect and the review in the browser, then upload the repo's learnings")
318
307
  .argument("<state>")
319
308
  .argument("<dir>")
320
- .action(async (state, 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);
321
314
  await finishPair(state, resolve(dir));
322
315
  });
323
316
  program
@@ -344,7 +337,7 @@ grouped(program.command("telemetry"), GROUP.setup)
344
337
  return;
345
338
  case "enable":
346
339
  patchState({ enabled: true });
347
- 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.`);
348
341
  return;
349
342
  case "disable":
350
343
  // Also stamp the notice as shown: someone who has just opted out should
@@ -356,7 +349,7 @@ grouped(program.command("telemetry"), GROUP.setup)
356
349
  console.log(formatDebug());
357
350
  return;
358
351
  default:
359
- console.error(`✗ unknown action "${action}" — expected status, enable, disable, or debug`);
352
+ console.error(`✗ unknown action "${action}": expected status, enable, disable, or debug`);
360
353
  process.exit(1);
361
354
  }
362
355
  });
@@ -398,7 +391,7 @@ function moveHereToTrail(dir) {
398
391
  grouped(program.command("build"), GROUP.setup)
399
392
  .description(TRAIL
400
393
  ? "Rebuild the code map in .trail/cache/ ($0, no key). --check fails if it's stale; --deep adds LLM summaries."
401
- : "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). " +
402
395
  "Add --deep for the LLM concept map + per-symbol summaries/crux.")
403
396
  .argument("[dir]", "repository root", ".")
404
397
  .option("--check", "build nothing: fail if graft/ is stale relative to the code (for CI)")
@@ -416,9 +409,9 @@ grouped(program.command("build"), GROUP.setup)
416
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) " +
417
410
  "as ONE graph, so imports across them resolve; persisted for later builds and automatic refreshes")
418
411
  .option("--no-follow-nested-repos", "exclude untracked nested Git clones; persisted for later builds and automatic refreshes (default)")
419
- .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); " +
420
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], [])
421
- .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 " +
422
415
  "concept pass both honor it. Recorded in the graph fingerprint so a later build " +
423
416
  "(and the hooks/refresh path) walks the same set; everything outside the list is skipped", (val, prev) => [...prev, val], [])
424
417
  .option("--no-gitignore", "skip writing graft/ into .gitignore (same as GRAFT_NO_GITIGNORE=1)")
@@ -507,12 +500,12 @@ grouped(program.command("build"), GROUP.setup)
507
500
  const resolved = resolveConfig(cliConfig());
508
501
  if (deep && !resolved.apiKey) {
509
502
  deep = false;
510
- 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" +
511
504
  " Set GRAFT_API_KEY (and GRAFT_PROVIDER / GRAFT_BASE_URL / GRAFT_MODEL for your\n" +
512
505
  ` provider) and re-run \`${cmd("graft build --deep")}\` to add concept nodes and summaries.`);
513
506
  }
514
507
  if (deep && resolved.usedLegacyEnv) {
515
- 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.");
516
509
  }
517
510
  // Workspace parent: build each child into its OWN graft/ + a workspace index.
518
511
  const buildRoot = resolve(dir);
@@ -598,10 +591,10 @@ grouped(program.command("build"), GROUP.setup)
598
591
  console.error(fallbackNotice);
599
592
  const rel = relative(process.cwd(), g.contextDir) || "graft";
600
593
  if (process.env.GRAFT_NO_GITIGNORE) {
601
- 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.`);
602
595
  }
603
596
  else {
604
- 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.`);
605
598
  }
606
599
  // #127: a --deep run whose LLM calls failed used to print the same success
607
600
  // footer and exit 0, so a quota-exhausted build looked identical to a clean
@@ -616,7 +609,7 @@ grouped(program.command("build"), GROUP.setup)
616
609
  const total = ready + m.stale + m.pending;
617
610
  const pct = total > 0 ? Math.round((ready / total) * 100) : 0;
618
611
  console.error("");
619
- 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.`);
620
613
  if (conceptFatal)
621
614
  console.error(` concepts: ${conceptFatal}`);
622
615
  if (m.fatal)
@@ -656,23 +649,24 @@ function afterAsk(r, repo) {
656
649
  if (shown.length) {
657
650
  for (const h of shown)
658
651
  h.note.teammate = !!h.note.author && !!me && h.note.author !== me;
659
- const saved = shown.reduce((sum, h) => sum + savedByNote(h.note), 0);
660
- track("learning_reused", { teammate: String(shown.some((h) => h.note.teammate)), saved_tokens_bucket: savedTokensBucket(saved) }, { repo });
661
652
  }
662
653
  maybeSyncInBackground(repo);
663
654
  }
664
655
  grouped(program.command("ask"), GROUP.find)
665
- .description("Query the graft/ graph — returns ranked nodes + exact file:line, routed to prose or wiring ($0, no key)")
666
- .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)")
667
658
  .argument(...DIR_ARG)
668
659
  .option("-n, --limit <n>", "max results", "8")
669
- .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)")
670
661
  .option("--full", "with --source: inline whole definition spans instead of the default ≤8-line crux excerpts")
671
662
  .option("--in <path>", "narrow to nodes under this path prefix, filtered before scoring (segment-aware, like scopeOf)")
672
663
  .option("--json", "output the result as JSON")
673
664
  .option("--no-graph-rank", "rank by lexical relevance only, without the graph-connectivity re-rank (ablation/eval)")
674
665
  .option(...NO_REFRESH_FLAG)
675
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();
676
670
  const dir = noteQuery(queryRoot(dirArg));
677
671
  await refreshBefore(dir, opts);
678
672
  const askGlobalDir = program.opts().dir;
@@ -709,7 +703,7 @@ grouped(program.command("ask"), GROUP.find)
709
703
  }
710
704
  });
711
705
  grouped(program.command("skeleton"), GROUP.find)
712
- .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")
713
707
  .argument("<file>", "repo-relative path (or unique basename) of the file")
714
708
  .argument(...DIR_ARG)
715
709
  .option("--json", "output the result as JSON")
@@ -766,7 +760,7 @@ async function runCheckCommand(dirArg, opts, label) {
766
760
  }
767
761
  else {
768
762
  if (r.missing) {
769
- 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`);
770
764
  }
771
765
  else {
772
766
  console.log(formatCheckReport(r, label));
@@ -820,7 +814,7 @@ program
820
814
  const globalOpts = program.opts();
821
815
  const contextDir = contextDirFor(root, globalOpts.dir);
822
816
  if (!existsSync(contextDir)) {
823
- 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`);
824
818
  process.exit(1);
825
819
  }
826
820
  const viewerDir = fileURLToPath(new URL("./viewer/", import.meta.url)); // prebuilt
@@ -852,7 +846,7 @@ program
852
846
  });
853
847
  program
854
848
  .command("mcp", { hidden: TRAIL })
855
- .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")
856
850
  .argument(...DIR_ARG)
857
851
  .option("--brains", "serve only the connected Trail workspace's brain tools (what Claude Code gets after init --connect)")
858
852
  .action(async (dirArg, opts) => {
@@ -899,13 +893,13 @@ grouped(program.command("callers"), GROUP.find)
899
893
  });
900
894
  grouped(program.command("blast"), GROUP.ci)
901
895
  .description("Blast radius of a diff: what depends on the lines this change touched ($0, no LLM). " +
902
- "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.")
903
897
  .argument(...DIR_ARG)
904
898
  .option("--base <ref>", "diff against this ref's merge base with HEAD (e.g. origin/main); default: the working tree vs HEAD")
905
899
  .option("-d, --depth <n>", 'hops to walk over incoming edges, or "all" for the full closure (default 2)')
906
900
  .option("--format <fmt>", "text (default) | markdown | mermaid | json")
907
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")
908
- .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)")
909
903
  .option("--title <text>", "subtitle beside the repo name on the exported page (e.g. \"PR #171\")")
910
904
  .option("--no-owners", "do not suggest who to tag (by default, git history names the people behind each affected area)")
911
905
  .option("--pr-author <who...>", "GitHub login, git name or email of the PR author, so they are left out of their own suggestions")
@@ -955,7 +949,7 @@ grouped(program.command("grep"), GROUP.find)
955
949
  });
956
950
  });
957
951
  grouped(program.command("map"), GROUP.find)
958
- .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)")
959
953
  .argument(...DIR_ARG)
960
954
  .option("--max-dirs <n>", "max directory entries shown, rest counted into dropped (default 16)")
961
955
  .option("--json", "output as JSON")
@@ -983,7 +977,7 @@ grouped(program.command("map"), GROUP.find)
983
977
  const contextDir = contextDirFor(root, globalOpts.dir);
984
978
  const graph = loadGraphCached(contextDir);
985
979
  if (!graph) {
986
- console.error(`✗ no graph — run ${cmd("graft build")} first`);
980
+ console.error(`✗ no graph: run ${cmd("graft build")} first`);
987
981
  process.exit(1);
988
982
  return;
989
983
  }
@@ -1013,154 +1007,12 @@ grouped(program.command("note", { hidden: !TRAIL }), GROUP.memory)
1013
1007
  .action(async (dir, opts) => {
1014
1008
  await runNoteCommand(resolve(dir), opts);
1015
1009
  });
1016
- // `trail learn` and `trail skills`: corrections become takeaways on a skill,
1017
- // kept on this machine, and takeaways fold into its SKILL.md. Unlisted under
1018
- // graft, like `note`, but they run.
1019
- // Still runs; unlisted while learnings are the one thing trail keeps.
1020
- grouped(program.command("learn", { hidden: true }), GROUP.memory)
1021
- .description("Turn a correction into a takeaway on a skill, kept in ~/.trail (the takeaway on stdin)")
1022
- .argument("<skill>", "the skill it belongs to, e.g. pdf-coords; a new name makes a new skill")
1023
- .argument("[dir]", "repository root", ".")
1024
- .option("-m, --text <text>", "the takeaway itself, instead of stdin")
1025
- .option("--description <text>", "for a new skill: what it covers")
1026
- .option("--section <heading>", "the SKILL.md heading it belongs under when folded")
1027
- .option("--author <name>", "who taught it (default: the first word of git config user.name)")
1028
- .action(async (skill, dir, opts) => {
1029
- const repo = resolve(dir);
1030
- const text = (opts.text ?? (await readStdin())).trim();
1031
- if (!text) {
1032
- console.error(`✗ a takeaway needs its text, on stdin or with -m:`);
1033
- console.error(` ${cmd("graft learn")} ${skill} <<'EOF'\n Read rotation with page.Rotation(), never pdfcpu: it drops /Rotate on linearized files.\n EOF`);
1034
- process.exitCode = 1;
1035
- return;
1036
- }
1037
- const author = opts.author?.trim() || noteAuthor(repo);
1038
- let r;
1039
- try {
1040
- r = learn(repo, { skill, text, author, date: new Date().toISOString().slice(0, 10), description: opts.description, section: opts.section });
1041
- }
1042
- catch (err) {
1043
- console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
1044
- process.exitCode = 1;
1045
- return;
1046
- }
1047
- track("takeaway_saved", { new_skill: String(r.created) }, { repo });
1048
- const kind = r.created ? "new skill" : r.skill.where === "repo" ? "the repo's skill" : "skill";
1049
- console.log(`✓ takeaway saved to ${kind} ${r.skill.name}${author ? ` · taught by ${author}` : ""}`);
1050
- console.log(`· your agent follows it from now on · kept on this machine, in ${shownPath(dirname(dirname(r.takeaway.path)))}`);
1051
- console.log(`· ${cmd("graft skills")} fold ${r.skill.name} writes it into SKILL.md`);
1052
- });
1053
- const skillsCmd = grouped(program.command("skills", { hidden: true }), GROUP.memory)
1054
- .description("List this repo's skills, and the takeaways waiting to fold into each")
1055
- .argument("[dir]", "repository root", ".")
1056
- .option("--json", "output as JSON")
1057
- .action((dir, opts) => {
1058
- const repo = resolve(dir);
1059
- const all = listSkills(repo);
1060
- if (opts.json) {
1061
- console.log(JSON.stringify(all, null, 2));
1062
- return;
1063
- }
1064
- if (all.length === 0) {
1065
- console.log(`no skills yet · ${cmd("graft learn")} <skill> saves the first takeaway and makes the skill`);
1066
- return;
1067
- }
1068
- const width = Math.max(...all.map((s) => s.name.length)) + 2;
1069
- for (const s of all)
1070
- console.log(`${s.name.padEnd(width)}${skillStatus(s)}`);
1071
- });
1072
- /** `✗ no skill called x` and the exit code, for a name that isn't one. */
1073
- function noSuchSkill(name) {
1074
- console.error(`✗ no skill called ${skillName(name)} · ${cmd("graft skills")} lists them`);
1075
- process.exitCode = 1;
1076
- }
1077
- skillsCmd
1078
- .command("show")
1079
- .description("Print a skill with every takeaway not yet folded in: what an agent reads before working in its area")
1080
- .argument("<skill>", "the skill")
1081
- .option("-C, --dir <path>", "repository root", ".")
1082
- .action((skill, opts) => {
1083
- const s = getSkill(resolve(opts.dir), skill);
1084
- if (!s)
1085
- return noSuchSkill(skill);
1086
- process.stdout.write(skillForAgent(s));
1087
- });
1088
- skillsCmd
1089
- .command("fold")
1090
- .description("Write a skill's waiting takeaways into its SKILL.md and bump its version")
1091
- .argument("[skill]", "one skill; leave out to fold every skill with takeaways waiting")
1092
- .option("--confirmed", "only the takeaways someone other than their teacher confirmed")
1093
- .option("-C, --dir <path>", "repository root", ".")
1094
- .action((skill, opts) => {
1095
- const repo = resolve(opts.dir);
1096
- if (skill && !getSkill(repo, skill))
1097
- return noSuchSkill(skill);
1098
- const names = skill ? [skill] : listSkills(repo).map((s) => s.name);
1099
- let inRepo = false;
1100
- let any = false;
1101
- for (const n of names) {
1102
- const r = fold(repo, n, { confirmed: opts.confirmed });
1103
- if (!r)
1104
- continue;
1105
- if (r.folded.length === 0) {
1106
- if (skill)
1107
- console.log(`· ${r.skill} v${r.from} · nothing to fold${r.stillWaiting ? `, ${r.stillWaiting} waiting for a second person` : ""}`);
1108
- continue;
1109
- }
1110
- any = true;
1111
- inRepo ||= r.where === "repo";
1112
- track("skills_folded", { takeaways_bucket: countBucket(r.folded.length) }, { repo });
1113
- 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` : ""}`);
1114
- console.log(` M ${r.where === "repo" ? shown(repo, r.file) : shownPath(r.file)} ${r.sections.map((h) => `+ ${h}`).join(" ")}`);
1115
- }
1116
- if (!any) {
1117
- if (!skill)
1118
- console.log("· nothing to fold");
1119
- return;
1120
- }
1121
- if (inRepo)
1122
- console.log("· review with git diff, then commit");
1123
- else
1124
- console.log(`· ${cmd("graft skills")} publish <skill> moves a skill into the repo's .claude/skills/, to commit for your team`);
1125
- });
1126
- skillsCmd
1127
- .command("publish")
1128
- .description("Move a skill kept on this machine into the repo's .claude/skills/, ready to commit")
1129
- .argument("<skill>", "the skill")
1130
- .option("-C, --dir <path>", "repository root", ".")
1131
- .action((skill, opts) => {
1132
- const repo = resolve(opts.dir);
1133
- const r = publish(repo, skill);
1134
- if (!r.ok) {
1135
- if (r.reason === "unknown")
1136
- return noSuchSkill(skill);
1137
- console.log(`· ${skillName(skill)} is already in the repo, in .claude/skills/${skillName(skill)}/`);
1138
- return;
1139
- }
1140
- console.log(`✓ ${skillName(skill)} → ${shown(repo, r.file)}`);
1141
- console.log("· commit it to share it: Claude Code loads it for everyone who pulls. its takeaways stay on this machine");
1142
- });
1143
- skillsCmd
1144
- .command("confirm")
1145
- .description("Say a teammate's takeaway is right, so `fold --confirmed` takes it")
1146
- .argument("<skill>", "the skill")
1147
- .argument("[takeaway]", "one takeaway file (default: every one waiting that someone else taught)")
1148
- .option("--author <name>", "who is confirming (default: the first word of git config user.name)")
1149
- .option("-C, --dir <path>", "repository root", ".")
1150
- .action((skill, takeaway, opts) => {
1151
- const repo = resolve(opts.dir);
1152
- if (!getSkill(repo, skill))
1153
- return noSuchSkill(skill);
1154
- const who = opts.author?.trim() || noteAuthor(repo);
1155
- const done = confirm(repo, skill, who, takeaway);
1156
- if (done.length === 0) {
1157
- console.log(`· nothing to confirm on ${skillName(skill)} — a takeaway needs someone other than its teacher`);
1158
- return;
1159
- }
1160
- for (const t of done)
1161
- console.log(`✓ confirmed ${t.taughtBy || "a"}'s takeaway from ${shortDate(t.date)} · ${shownPath(t.path)}`);
1162
- console.log(`· ${cmd("graft skills")} fold ${skillName(skill)} writes it into SKILL.md`);
1163
- });
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);
1164
1016
  grouped(program.command("init"), GROUP.setup)
1165
1017
  .description(TRAIL
1166
1018
  ? "Wire trail into your agents: instruction files and MCP for each, plus hooks and a statusline for Claude Code"
@@ -1181,6 +1033,8 @@ grouped(program.command("init"), GROUP.setup)
1181
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)")
1182
1034
  .option("--pair <code>", "the pair code from Trail's onboarding page: that page shows this run live, then you review what it found")
1183
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")
1184
1038
  .action(async (dir, opts) => {
1185
1039
  if (opts.connect !== undefined && opts.pair !== undefined) {
1186
1040
  console.error("✗ --connect and --pair are two ways in: use the one your Trail page gave you");
@@ -1192,6 +1046,21 @@ grouped(program.command("init"), GROUP.setup)
1192
1046
  else
1193
1047
  await runInitCommand(dir, opts);
1194
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;
1063
+ });
1195
1064
  /** Everything on stdin, or "" when nothing is piped in. */
1196
1065
  async function readStdin() {
1197
1066
  if (process.stdin.isTTY)
@@ -1283,11 +1152,12 @@ async function runNoteCommand(repo, opts) {
1283
1152
  }
1284
1153
  const author = opts.author?.trim() || noteAuthor(repo);
1285
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.
1286
1156
  const earlier = [];
1287
1157
  const saved = [];
1288
1158
  for (const t of targets) {
1289
1159
  earlier.push(...listNotes(t.dir));
1290
- 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));
1291
1161
  }
1292
1162
  const touches = targets.flatMap((t) => t.touches);
1293
1163
  track("note_saved", { has_cost: String(cost?.tokens !== undefined || cost?.minutes !== undefined), touches_bucket: countBucket(touches.length), scope: saved[0]?.scope ?? scope }, { repo });
@@ -1296,7 +1166,8 @@ async function runNoteCommand(repo, opts) {
1296
1166
  return;
1297
1167
  }
1298
1168
  for (const n of saved)
1299
- 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));
1300
1171
  const note = saved[0];
1301
1172
  // Name a code file if the note touches one; docs are rarely what it's about.
1302
1173
  const about = touches.find((t) => !/\.(md|mdx|txt)$/i.test(t.split("#")[0])) ?? touches[0];
@@ -1311,7 +1182,7 @@ async function runNoteCommand(repo, opts) {
1311
1182
  }
1312
1183
  // Learnings about the same files, from someone else: this one builds on theirs.
1313
1184
  const files = new Set(touches.map((t) => t.split("#")[0]));
1314
- 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])));
1315
1186
  if (prior)
1316
1187
  console.log(`· builds on ${prior.author ? `${prior.author}'s` : "a"} learning from ${shortDate(prior.date)}`);
1317
1188
  if (!SECTIONS.some((s) => sectionLead(body, s.heading) !== null))
@@ -1353,19 +1224,19 @@ function printLines(lines) {
1353
1224
  }
1354
1225
  /**
1355
1226
  * `--server <url>` on `init --connect` and `--pair`: the Trail that gave the
1356
- * code, kept for the rest of this run as TRAIL_URL. False, after saying why,
1357
- * when it isn't a URL.
1227
+ * code, for the rest of this run (`useServer`, never the environment). False,
1228
+ * after saying why, when it isn't a URL.
1358
1229
  */
1359
1230
  function useServerOption(raw) {
1360
- const server = raw?.trim().replace(/\/+$/, "");
1361
- if (!server)
1231
+ if (raw === undefined || !raw.trim())
1362
1232
  return true;
1363
- if (!/^https?:\/\//.test(server)) {
1233
+ const server = serverUrl(raw);
1234
+ if (!server) {
1364
1235
  console.error("✗ --server takes a URL, like https://dev.trailhq.com");
1365
1236
  process.exitCode = 1;
1366
1237
  return false;
1367
1238
  }
1368
- process.env.TRAIL_URL = server;
1239
+ useServer(server);
1369
1240
  return true;
1370
1241
  }
1371
1242
  /**
@@ -1379,9 +1250,7 @@ function pairProgress(pair) {
1379
1250
  learned: (l) => {
1380
1251
  if (l.sessions <= 0)
1381
1252
  return;
1382
- // The PR reviews' learnings come with `sessions: 0`; the rest were said in past sessions.
1383
- const fromSessions = l.notes.filter((n) => n.sessions !== 0).length;
1384
- pair.phase("decisions", "done", `${l.sessions} sessions`, `Read ${l.sessions} past sessions: ${fromSessions} thing${fromSessions === 1 ? "" : "s"} you told Claude more than once`);
1253
+ pair.phase("decisions", "done", `${l.sessions} sessions`, `Read ${l.sessions} past Claude sessions`);
1385
1254
  },
1386
1255
  };
1387
1256
  }
@@ -1415,7 +1284,7 @@ async function runInitCommand(dir, opts, progress) {
1415
1284
  const validIds = [...hostIds(), "claude"];
1416
1285
  const unknown = explicit.filter((id) => !validIds.includes(id));
1417
1286
  if (unknown.length) {
1418
- console.error(`✗ unknown agent id(s): ${unknown.join(", ")} — valid: ${validIds.join(", ")}`);
1287
+ console.error(`✗ unknown agent id(s): ${unknown.join(", ")}; valid: ${validIds.join(", ")}`);
1419
1288
  process.exit(1);
1420
1289
  }
1421
1290
  }
@@ -1447,7 +1316,7 @@ async function runInitCommand(dir, opts, progress) {
1447
1316
  offerTelemetry: reason === null || reason === "disabled",
1448
1317
  });
1449
1318
  if (picked === null) {
1450
- console.error("· cancelled — nothing written");
1319
+ console.error("· cancelled, nothing written");
1451
1320
  return null;
1452
1321
  }
1453
1322
  ids = picked.hosts;
@@ -1475,11 +1344,11 @@ async function runInitCommand(dir, opts, progress) {
1475
1344
  if (opts.dryRun) {
1476
1345
  console.error(formatPlan(plan, ids, repo, home));
1477
1346
  for (const child of children)
1478
- 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));
1479
1348
  return null;
1480
1349
  }
1481
1350
  if (ids.length === 0) {
1482
- console.error("· no agents selected — nothing written");
1351
+ console.error("· no agents selected, nothing written");
1483
1352
  return null;
1484
1353
  }
1485
1354
  // With --pair, Trail's onboarding page is waiting on this run: tell it
@@ -1504,11 +1373,13 @@ async function runInitCommand(dir, opts, progress) {
1504
1373
  }
1505
1374
  const wantClaude = ids.includes("claude");
1506
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) : [];
1507
1378
  // Under trail, each repo gets its `.trail/learnings/`, committed with the
1508
1379
  // code, and its folder in ~/.trail for personal learnings and corrections.
1509
- // First, before the reads below send their first event: that makes the
1510
- // repo one on trail, so the event's repo id lands in `.trail/cache/` and
1511
- // 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.
1512
1383
  const notesHome = new Map();
1513
1384
  if (TRAIL)
1514
1385
  for (const target of targets) {
@@ -1518,35 +1389,32 @@ async function runInitCommand(dir, opts, progress) {
1518
1389
  if (place.git)
1519
1390
  maybeRefreshBranches(place.checkout);
1520
1391
  }
1521
- // Under trail, with Claude Code picked: what the person kept telling Claude
1522
- // in this repo's past sessions, and what reviewers kept flagging on its
1523
- // merged PRs, read while the rest of init runs and saved as learnings at
1524
- // the end. Once per repo, and never asked about first.
1525
- const pastRead = TRAIL && wantClaude ? beginPastRead(repo) : null;
1526
- if (pastRead)
1527
- console.error(`· reading your ${pastRead.sessions} past Claude sessions here for what you keep telling Claude`);
1528
- const reviewRead = TRAIL && wantClaude ? beginReviewRead(repo) : null;
1529
- if (reviewRead)
1530
- 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);
1531
1399
  if (pair) {
1532
- if (pastRead)
1533
- pair.phase("decisions", "running", "", `Reading ${pastRead.sessions} past Claude sessions`);
1400
+ if (lookBack?.reads.lb)
1401
+ pair.phase("decisions", "running", "", "Reading past Claude sessions");
1534
1402
  else
1535
1403
  pair.phase("decisions", "done", "none here");
1536
- if (reviewRead)
1404
+ if (lookBack?.reads.reviews)
1537
1405
  pair.phase("prs", "running", "", "Reading review comments on merged PRs");
1538
1406
  else
1539
1407
  pair.phase("prs", "done", "none here");
1540
1408
  }
1541
1409
  if (children.length)
1542
- 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(", ")}`);
1543
1411
  // Today's output, file by file, behind --verbose. Everything else gets one
1544
1412
  // line per step: the graph, then one per agent.
1545
1413
  const verbose = opts.verbose === true;
1546
1414
  const reports = [];
1547
1415
  for (const target of targets) {
1548
1416
  if (verbose && target !== repo)
1549
- console.error(`\n— ${relative(repo, target)}/`);
1417
+ console.error(`\n· ${relative(repo, target)}/`);
1550
1418
  reports.push(wireTarget(target, ids, { home, cliPath, plan, opts, wantClaude, quiet: !verbose }));
1551
1419
  }
1552
1420
  // The graph, built once after the wiring: a workspace parent's build is
@@ -1566,7 +1434,7 @@ async function runInitCommand(dir, opts, progress) {
1566
1434
  graphLine = "✓ graph built";
1567
1435
  }
1568
1436
  else if (res.failed) {
1569
- 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`;
1570
1438
  }
1571
1439
  else if (existing) {
1572
1440
  graphLine = `✓ graph ready · ${fmt(existing.meta.nodeCount)} nodes, ${fmt(existing.meta.edgeCount)} edges`;
@@ -1575,9 +1443,12 @@ async function runInitCommand(dir, opts, progress) {
1575
1443
  graphLine = "✓ graph ready";
1576
1444
  }
1577
1445
  else {
1578
- graphLine = `· skipped the graph build — run ${cmd("graft build")}`;
1446
+ graphLine = `· skipped the graph build: run ${cmd("graft build")}`;
1579
1447
  }
1580
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);
1581
1452
  // --pair hears the same stages --connect does; only the ones InitProgress
1582
1453
  // has no slot for (a read starting, the PR read, nothing to read) stay inline.
1583
1454
  const report = pair ? pairProgress(pair) : progress;
@@ -1601,7 +1472,7 @@ async function runInitCommand(dir, opts, progress) {
1601
1472
  // Warnings and removals in full, whatever else is folded.
1602
1473
  const removed = [...new Set(reports.flatMap((r) => r.retracted))];
1603
1474
  for (const id of removed)
1604
- console.error(`- removed ${id} — agent not selected`);
1475
+ console.error(`- removed ${id} (agent not selected)`);
1605
1476
  for (const w of reports.flatMap((r) => r.warnings))
1606
1477
  console.error(w);
1607
1478
  const writesFor = (id) => (plan.find((p) => p.id === id)?.writes ?? []).filter((w) => opts.global !== false || w.scope !== "global");
@@ -1613,130 +1484,58 @@ async function runInitCommand(dir, opts, progress) {
1613
1484
  console.error(`✓ ${id.padEnd(width)} ${summary}`.trimEnd());
1614
1485
  }
1615
1486
  }
1616
- // Under trail: what init learned, where this repo's learnings are, and how
1617
- // many there already are. The team's come from what is tied to the code:
1618
- // what reviewers kept flagging on merged PRs, and Claude Code's notes about
1619
- // 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.
1620
1491
  let learnedNow = false;
1621
- let skillsWritten = [];
1622
1492
  let teamSaved = 0;
1623
- const memory = TRAIL && wantClaude ? memoryCandidates(repo, { offered: offeredBefore(repo) }) : [];
1624
- if (TRAIL && (pastRead || reviewRead || memory.length)) {
1625
- if (pastRead && !pastRead.done)
1626
- console.error("· still reading your past sessions…");
1627
- const fromSessions = pastRead ? (await pastRead.result).findings : [];
1628
- if (reviewRead && !reviewRead.done)
1629
- console.error("· still reading PR reviews…");
1630
- const fromReviews = reviewRead ? anchoredReviews(repo, (await reviewRead.result).findings) : [];
1631
- // On a terminal, the person ticks which to keep in one list, the team's
1632
- // first and ticked, their own unticked. A scripted run, or one with
1633
- // --yes, keeps the team's and what they told Claude. Cancelling saves
1634
- // none, and the next init asks again.
1635
- const picked = process.stdin.isTTY && process.stderr.isTTY && !opts.yes && !pair && fromReviews.length + memory.length + fromSessions.length
1636
- ? await pickLearnings([
1637
- ...fromReviews.map((f) => ({ title: f.title, forTeam: true, prs: f.prs })),
1638
- ...memory.map((c) => ({ title: c.title, forTeam: c.forTeam, memory: true })),
1639
- ...fromSessions.map((f) => ({ title: f.title, forTeam: false, sessions: f.sessions.length })),
1640
- ])
1641
- : undefined;
1642
- // The picked rows that are `found`'s, which start at `from` in the list.
1643
- const keepFrom = (from) => picked === undefined
1644
- ? undefined
1645
- : async (found) => picked && picked.filter((i) => i >= from && i < from + found.length).map((i) => found[i - from]);
1646
- const learned = [];
1647
- const learnedFrom = [];
1648
- let sessionsRead = 0;
1649
- // Reviews first: which of them are offered depends on the learnings already saved.
1650
- if (reviewRead) {
1651
- const { result, saved, kept, cancelled } = await finishReviewRead(repo, reviewRead, keepFrom(0));
1652
- const offered = fromReviews.length;
1653
- pair?.phase("prs", "done", `${result.prs} PRs`, `Read ${result.prs} merged PRs: ${saved.length} thing${saved.length === 1 ? "" : "s"} reviewers made authors change`);
1654
- if (saved.length) {
1655
- const n = saved.length;
1656
- const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1657
- console.error(`✓ ${what} reviewers made authors change, from the last ${result.prs} merged PRs here`);
1658
- // saveReviewFindings keeps the findings' order.
1659
- saved.forEach((note, i) => console.error(` for your team · from review on ${(kept[i]?.prs ?? []).map((n) => `#${n}`).join(", ")}: ${note.title}`));
1660
- }
1661
- else if (cancelled) {
1662
- console.error(`· saved none of the ${offered} things reviewers made authors change; ${cmd("graft init")} again asks again`);
1663
- }
1664
- else if (offered) {
1665
- console.error(`· kept none of the ${offered} things reviewers made authors change`);
1666
- }
1667
- else if (result.reason === "failed") {
1668
- console.error(`· couldn't read PR reviews this time; ${cmd("graft init")} again tries again`);
1669
- }
1670
- learned.push(...saved);
1671
- // Not said in past sessions: `sessions: 0` keeps them out of the page's session count.
1672
- for (const note of saved)
1673
- learnedFrom.push({ note, sessions: 0 });
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}`));
1674
1508
  }
1675
- if (memory.length) {
1676
- const { saved, cancelled } = await finishMemoryRead(repo, memory, keepFrom(fromReviews.length));
1677
- if (saved.length) {
1678
- console.error(`✓ kept ${saved.length} of the ${memory.length} notes your Claude keeps about this repo`);
1679
- for (const note of saved)
1680
- console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${note.title}`);
1681
- }
1682
- else if (cancelled) {
1683
- console.error(`· saved none of the ${memory.length} notes your Claude keeps about this repo; ${cmd("graft init")} again asks again`);
1684
- }
1685
- learned.push(...saved);
1686
- for (const note of saved)
1687
- learnedFrom.push({ note, sessions: 0 });
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}`);
1688
1513
  }
1689
- if (pastRead) {
1690
- const { result, saved, kept, skills, cancelled } = await finishPastRead(repo, pastRead, keepFrom(fromReviews.length + memory.length));
1691
- sessionsRead = result.read ?? 0;
1692
- skillsWritten = skills;
1693
- for (const { skill, path } of skills) {
1694
- console.error(`✓ wrote a skill from a procedure you walked Claude through more than once: ${skill.title}`);
1695
- console.error(` ${skill.for === "team" ? "for your team" : "just for you "} · ${path}`);
1696
- }
1697
- if (!cancelled)
1698
- for (const line of skippedSkillLines(repo, result.skills, skills))
1699
- console.error(line);
1700
- saved.forEach((note, i) => learnedFrom.push({ note, sessions: kept[i]?.sessions.length }));
1701
- const offered = result.findings.length;
1702
- if (saved.length) {
1703
- const n = saved.length;
1704
- const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1705
- console.error(`✓ ${what} you told Claude more than once, from your last ${result.read} sessions here, just for you`);
1706
- // Each with how many sessions it was said in: saveFindings keeps the findings' order.
1707
- saved.forEach((note, i) => {
1708
- const said = kept[i]?.sessions.length ?? 0;
1709
- const count = said > 1 ? `told Claude in ${said} sessions: ` : "";
1710
- console.error(` just for you · ${count}${note.title}`);
1711
- });
1712
- }
1713
- else if (cancelled) {
1714
- console.error(`· saved none of the ${offered} things you told Claude more than once; ${cmd("graft init")} again asks again`);
1715
- }
1716
- else if (offered) {
1717
- console.error(`· kept none of the ${offered} things you told Claude more than once`);
1718
- }
1719
- else if (result.reason === "failed") {
1720
- console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
1721
- }
1722
- 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`);
1723
1520
  }
1724
- learnedNow = learned.length > 0;
1725
- teamSaved = learned.filter((s) => s.scope === "repo").length;
1726
- report?.learned?.({ notes: learnedFrom, sessions: sessionsRead });
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;
1727
1526
  if (learnedNow) {
1728
1527
  console.error("· Claude follows them from your next session");
1729
- if (teamSaved)
1528
+ if (learned.inRepo)
1730
1529
  console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
1731
1530
  }
1732
1531
  }
1733
- // Nothing read this run (already read once, or not Claude Code): the page still gets its stage.
1734
- if (!(TRAIL && (pastRead || reviewRead || memory.length)))
1532
+ // Not looked back (not trail, or not Claude Code): the page still gets its stage.
1533
+ if (!lookBack)
1735
1534
  report?.learned?.({ notes: [], sessions: 0 });
1736
1535
  if (TRAIL && !learnedNow) {
1737
1536
  const home = notesHome.get(repo);
1738
1537
  if (home) {
1739
- const learnings = listNotes(repo, { branches: false }).filter((n) => n.scope === "repo");
1538
+ const learnings = liveLearnings(listNotes(repo, { branches: false })).filter((n) => n.scope === "repo");
1740
1539
  if (learnings.length === 0)
1741
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`);
1742
1541
  else
@@ -1773,8 +1572,8 @@ async function runInitCommand(dir, opts, progress) {
1773
1572
  console.error(NOT_GIT_LINE);
1774
1573
  else if (tops.length)
1775
1574
  console.error(TRAIL
1776
- ? `· 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`
1777
- : `· 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`);
1778
1577
  }
1779
1578
  // One epilogue for the whole run. A workspace parent holds no nodes of its
1780
1579
  // own, so the totals come from the children — the graph the user actually got.
@@ -1801,8 +1600,8 @@ async function runInitCommand(dir, opts, progress) {
1801
1600
  // is left out, since an empty list would say "checked, none".
1802
1601
  const teammates = teammatesChecked(repo, { learnings: teamNotes });
1803
1602
  pair.teamDone(teammates);
1804
- pair.findings({ tokens, sessions: pastRead?.sessions, teammates });
1805
- await pair.finish(repo, skillsWritten);
1603
+ pair.findings({ tokens, sessions: lookBack?.reads.lb ? sessionsRead : undefined, teammates });
1604
+ await pair.finish(repo, skillsFound);
1806
1605
  pair.handOff(repo);
1807
1606
  console.error(`✓ Trail has what it found · review it in the browser: ${pair.url}`);
1808
1607
  if (!process.stdout.isTTY)
@@ -1935,7 +1734,7 @@ function wireTarget(repo, ids, ctx) {
1935
1734
  // be rewritten, and the graph cache is kept (init is one step from using it).
1936
1735
  const retracted = changed(runRetract(repo, { home, apply: true, global: opts.global, cache: false, exclude: ids })).filter((r) => r.action !== "skipped-unparseable");
1937
1736
  for (const r of retracted)
1938
- say(`- removed ${r.path} (${r.what}) — agent not selected`);
1737
+ say(`- removed ${r.path} (${r.what}): agent not selected`);
1939
1738
  report.retracted = [...new Set(retracted.map((r) => r.hostId))];
1940
1739
  if (wantClaude) {
1941
1740
  // `global`/`home` are threaded through alongside `statusline`: the claude layer
@@ -1951,11 +1750,11 @@ function wireTarget(repo, ids, ctx) {
1951
1750
  // trail registers no server for Claude Code: the hooks and the `trail` command cover it.
1952
1751
  }
1953
1752
  else if (res.mcp.action === "skipped-unparseable")
1954
- 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`);
1955
1754
  else if (res.mcp.action === "unchanged")
1956
1755
  say(`· mcp claude: ${res.mcp.path} (already registered)`);
1957
1756
  else
1958
- 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`);
1959
1758
  say(res.built ? `✓ built the graph (${cmd("graft build")})` : "· skipped graph build");
1960
1759
  if (!wantStatusline)
1961
1760
  say("· skipped Claude Code statusLine (--no-statusline)");
@@ -1980,6 +1779,13 @@ function wireTarget(repo, ids, ctx) {
1980
1779
  say(`✓ mcp ${m.id}: ${m.path} (${m.action})`);
1981
1780
  for (const h of r.hooks)
1982
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
+ }
1983
1789
  // Only worth saying when there was actually something out-of-repo to skip.
1984
1790
  if (opts.global === false && selectedWrites(plan, ids).some((w) => w.scope === "global"))
1985
1791
  say("· skipped out-of-repo writes (--no-global)");
@@ -2009,7 +1815,7 @@ function wireTarget(repo, ids, ctx) {
2009
1815
  function formatRetractions(rs, apply) {
2010
1816
  const hit = changed(rs);
2011
1817
  if (hit.length === 0)
2012
- return "· nothing to remove — no graft wiring found here";
1818
+ return "· nothing to remove: no graft wiring found here";
2013
1819
  const verb = apply ? "removed" : "would remove";
2014
1820
  const lines = [];
2015
1821
  const byHost = new Map();
@@ -2027,9 +1833,9 @@ function formatRetractions(rs, apply) {
2027
1833
  ? "-"
2028
1834
  : "~";
2029
1835
  const note = r.action === "skipped-unparseable"
2030
- ? " — not valid JSON, left untouched (remove the graft entry by hand)"
1836
+ ? ": not valid JSON, left untouched (remove the graft entry by hand)"
2031
1837
  : r.action === "deleted"
2032
- ? ` (${r.what} — deleted)`
1838
+ ? ` (${r.what}, deleted)`
2033
1839
  : ` (${r.what})`;
2034
1840
  const scope = r.scope === "global" ? " [machine-wide]" : "";
2035
1841
  lines.push(` ${mark} ${verb}: ${r.path}${scope}${note}`);
@@ -2041,7 +1847,7 @@ grouped(program.command("uninstall"), GROUP.setup)
2041
1847
  .description(`Remove every file and config entry ${TRAIL ? "trail or graft" : "graft"} has written to this repo (the inverse of init)`)
2042
1848
  .argument("[dir]", "target repo directory", ".")
2043
1849
  .option("-y, --yes", "actually remove (without this, prints what it would remove and exits)")
2044
- .option("--keep-cache", "keep graft/ and the .gitignore entries — wiring only")
1850
+ .option("--keep-cache", "keep graft/ and the .gitignore entries: wiring only")
2045
1851
  .option("--no-global", "leave out-of-repo files alone (~/.codex, ~/.gemini)")
2046
1852
  .action((dir, opts) => {
2047
1853
  const repo = resolve(dir);
@@ -2049,7 +1855,7 @@ grouped(program.command("uninstall"), GROUP.setup)
2049
1855
  const common = { home, global: opts.global, cache: opts.keepCache ? false : true };
2050
1856
  if (!opts.yes) {
2051
1857
  console.error(formatRetractions(planRetract(repo, common), false));
2052
- 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.");
2053
1859
  if (opts.global !== false)
2054
1860
  console.error("Entries marked [machine-wide] affect every project; --no-global skips them.");
2055
1861
  return;
@@ -2058,7 +1864,7 @@ grouped(program.command("uninstall"), GROUP.setup)
2058
1864
  console.error(formatRetractions(done, true));
2059
1865
  const bad = changed(done).filter((r) => r.action === "skipped-unparseable");
2060
1866
  console.error(bad.length
2061
- ? `\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.`
2062
1868
  : `\n✓ ${brand()} fully removed. \`${cmd("graft init")}\` re-wires from scratch.`);
2063
1869
  });
2064
1870
  /* -------------------------------------------------------------------------- */
@@ -2068,23 +1874,22 @@ grouped(program.command("uninstall"), GROUP.setup)
2068
1874
  * `trail login`: connect this machine to a Trail workspace by hand. Normally
2069
1875
  * nobody runs it: Claude offers the same link once there's a learning to
2070
1876
  * share. Prints the link, opens it, and waits for the person to sign in and
2071
- * 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`).
2072
1879
  */
2073
1880
  function loginCommand(name = "login") {
2074
1881
  return new Command(name)
2075
- .description("Connect this machine to a Trail workspace: sign in, pick the repos it gets, invite your team")
2076
- .argument("[dir]", "the repo you're in, ticked first in the picker", ".")
1882
+ .description("Connect this machine to a Trail workspace: sign in, add this repo, invite your team")
1883
+ .argument("[dir]", "the repo to add", ".")
2077
1884
  .option("--server <url>", "a self-hosted Trail, e.g. https://trail.acme.internal (default: Trail cloud, https://team.trailhq.com)")
2078
1885
  .action(async (dir, opts) => {
2079
1886
  const repo = resolve(dir);
2080
- const server = opts.server?.trim().replace(/\/+$/, "");
2081
- if (server) {
2082
- if (!/^https?:\/\//.test(server)) {
2083
- console.error(`✗ --server takes a URL, like https://trail.acme.internal`);
2084
- process.exitCode = 1;
2085
- return;
2086
- }
2087
- 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;
2088
1893
  }
2089
1894
  const cloud = readCloud();
2090
1895
  if (cloud) {
@@ -2092,8 +1897,8 @@ function loginCommand(name = "login") {
2092
1897
  return;
2093
1898
  }
2094
1899
  rememberRepo(repo);
2095
- const url = loginLink(repo);
2096
- 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}`);
2097
1902
  if (process.stdout.isTTY)
2098
1903
  openBrowser(url);
2099
1904
  console.error("· waiting for you to finish in the browser (ctrl-c stops; Claude picks it up later too)");
@@ -2164,6 +1969,8 @@ function workspaceCommand(name = "workspace") {
2164
1969
  if (here.git && !me.repos.some((r) => r.key === here.key)) {
2165
1970
  console.log(` · ${here.name.padEnd(width)}not in the workspace · ${cmd("graft workspace add")} shares its learnings`);
2166
1971
  }
1972
+ else if (here.git)
1973
+ printLines(leftOutLines(here.checkout));
2167
1974
  });
2168
1975
  ws.command("add")
2169
1976
  .description("Add this repo to your workspace and share its learnings")
@@ -2197,18 +2004,33 @@ function workspaceCommand(name = "workspace") {
2197
2004
  return;
2198
2005
  }
2199
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);
2200
2009
  console.error(`· ${cloud.workspace.url}`);
2201
2010
  });
2202
2011
  return ws;
2203
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
+ }
2204
2022
  /* -------------------------------------------------------------------------- */
2205
2023
  /* trail team · trail check */
2206
2024
  /* -------------------------------------------------------------------------- */
2207
- /** 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
+ */
2208
2030
  function wrap(text, width, indent) {
2209
2031
  const out = [];
2210
2032
  let line = "";
2211
- for (const word of text.split(/\s+/)) {
2033
+ for (const word of unmarkedWords(text)) {
2212
2034
  if (line && line.length + 1 + word.length > width) {
2213
2035
  out.push(line);
2214
2036
  line = indent + word;
@@ -2331,7 +2153,7 @@ function localTeam(repo, days, json) {
2331
2153
  }
2332
2154
  function teamCheckCommand(name = "check") {
2333
2155
  return new Command(name)
2334
- .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")
2335
2157
  .argument("[dir]", "repository root", ".")
2336
2158
  .option("--base <ref>", "compare against where the branch left this ref (default: origin's default branch)")
2337
2159
  .option("--json", "output as JSON")
@@ -2351,7 +2173,10 @@ function withoutTrailFiles(diff) {
2351
2173
  * the repo's `.trail/` (teammates' pushed branches included), this person's
2352
2174
  * own, and the repo's skills. No sign-in, no server. Exits 1 on a conflict.
2353
2175
  */
2354
- 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;
2355
2180
  const d = branchDiff(repo, opts.base);
2356
2181
  if (!d) {
2357
2182
  console.error("✗ not a git repository");
@@ -2371,17 +2196,20 @@ async function runTeamCheck(repo, opts) {
2371
2196
  maybeRefreshBranches(repo);
2372
2197
  if (d.truncated)
2373
2198
  console.error("⚠ the diff is over 400 KB; only the first 400 KB is checked");
2374
- 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) });
2375
2202
  r.findings.unshift(...edits);
2376
- 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));
2377
2206
  recordActivity({
2378
2207
  repo: repoPlace(repo).key,
2379
2208
  kind: "check",
2380
2209
  asker: me,
2381
2210
  session: sessionKey(repo) ?? `${me}-${new Date().toISOString().slice(0, 10)}`,
2382
- query: "",
2383
- files,
2384
- 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)))],
2385
2213
  shown_authors: [...new Set(sources.map((s) => s.author ?? ""))],
2386
2214
  at: new Date().toISOString(),
2387
2215
  });
@@ -2402,23 +2230,28 @@ async function runTeamCheck(repo, opts) {
2402
2230
  function printCheck(r) {
2403
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`);
2404
2232
  const conflicts = r.findings.filter((f) => f.verdict === "conflict");
2405
- for (const f of [...conflicts, ...r.findings.filter((x) => x.verdict !== "conflict")]) {
2406
- 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" ? "●" : "✓";
2407
2236
  console.log(`${mark} ${f.file}${f.line ? `:${f.line}` : ""}`);
2408
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`);
2409
2242
  const s = f.source;
2410
- if (f.verdict === "conflict" && s.quote) {
2243
+ if (f.verdict !== "follows" && s.quote) {
2411
2244
  const who = [s.author, s.date ? shortDate(s.date) : ""].filter(Boolean).join(" · ");
2412
2245
  for (const l of wrap(`${who ? `${who}: ` : ""}${s.quote}`, 70, " "))
2413
2246
  console.log(` ${l.trimStart()}`);
2414
2247
  }
2415
2248
  }
2416
2249
  if (r.findings.length === 0)
2417
- console.log("✓ nothing the team has learned bears on this change");
2250
+ console.log("✓ no learning bears on this change");
2418
2251
  console.log("");
2419
2252
  console.log(conflicts.length
2420
2253
  ? `· ${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"} · fix ${conflicts.length === 1 ? "it" : "them"}, or say why in the commit message or PR description`
2421
- : "· no conflicts");
2254
+ : `· no conflicts${leads.length ? ` · ${leads.length} worth a look` : ""}`);
2422
2255
  }
2423
2256
  /** Tokens saved by this repo's agent sessions touched in the last 7 days. */
2424
2257
  function savedThisWeek(repo, now = Date.now()) {
@@ -2460,7 +2293,8 @@ function statusCommand(name = "status") {
2460
2293
  const g = graph ? await engineFrom().checkGraph(repo) : null;
2461
2294
  const fresh = g && !g.missing ? g.ok : null;
2462
2295
  const kept = keepsNotes(repo);
2463
- 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)) : [];
2464
2298
  const inRepo = all.filter((n) => n.scope === "repo" && !n.ref).length;
2465
2299
  const onBranches = all.filter((n) => n.ref).length;
2466
2300
  const personal = all.filter((n) => n.scope === "personal").length;
@@ -2485,7 +2319,7 @@ function statusCommand(name = "status") {
2485
2319
  else
2486
2320
  row("code map", `✓ ${fmt(graph.meta.nodeCount)} nodes · in sync with the code`);
2487
2321
  if (kept) {
2488
- const parts = [`${inRepo} for the team in ${LEARNINGS_DIR}/`];
2322
+ const parts = [`${inRepo} added to this repo, in ${LEARNINGS_DIR}/`];
2489
2323
  if (onBranches)
2490
2324
  parts.push(`${onBranches} on teammates' branches`);
2491
2325
  if (personal)
@@ -2502,6 +2336,9 @@ function statusCommand(name = "status") {
2502
2336
  else
2503
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}`);
2504
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);
2505
2342
  });
2506
2343
  }
2507
2344
  if (TRAIL) {
@@ -2512,7 +2349,7 @@ if (TRAIL) {
2512
2349
  program.addCommand(c.helpGroup(GROUP.cloud), { hidden: !cloudEnabled() });
2513
2350
  program.addCommand(statusCommand().helpGroup(GROUP.setup));
2514
2351
  // Help lists the groups in this order, and the commands within each.
2515
- 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"];
2516
2353
  const rank = (c) => {
2517
2354
  const i = order.indexOf(c.name());
2518
2355
  return i === -1 ? order.length : i;
@@ -2526,15 +2363,14 @@ if (TRAIL) {
2526
2363
  callers: "who calls it, or what it calls with --direction out",
2527
2364
  map: "directory clusters, hubs and hotspots",
2528
2365
  note: "save what this session worked out, as a learning in .trail/",
2529
- learn: "turn a correction into a takeaway on a skill",
2530
- 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",
2531
2367
  init: "wire trail into your agents",
2532
2368
  build: "rebuild the code map · --check fails if it's stale",
2533
2369
  status: "code map, learnings and tokens saved",
2534
2370
  upgrade: "install the latest trail",
2535
2371
  uninstall: "remove everything trail or graft wrote to this repo",
2536
2372
  telemetry: "show or turn off the anonymous usage stats",
2537
- check: "check your diff against everything the team has learned",
2373
+ check: "check your diff against this repo's learnings and yours",
2538
2374
  team: "what the team learned this week, and where work overlaps",
2539
2375
  workspace: "your workspace's repos, team page and invite link",
2540
2376
  login: "connect a Trail workspace (Claude offers you the link)",