@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.
- package/README.md +26 -21
- package/dist/blast/render.js +1 -1
- package/dist/blast/render.js.map +1 -1
- package/dist/brand.js +1 -1
- package/dist/brand.js.map +1 -1
- package/dist/check/learning-edits.d.ts +18 -3
- package/dist/check/learning-edits.d.ts.map +1 -1
- package/dist/check/learning-edits.js +54 -10
- package/dist/check/learning-edits.js.map +1 -1
- package/dist/check/local-check.d.ts +7 -3
- package/dist/check/local-check.d.ts.map +1 -1
- package/dist/check/local-check.js +55 -11
- package/dist/check/local-check.js.map +1 -1
- package/dist/check/types.d.ts +11 -2
- package/dist/check/types.d.ts.map +1 -1
- package/dist/claude/format.d.ts +11 -7
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +50 -28
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hook-io.js +1 -1
- package/dist/claude/hook-io.js.map +1 -1
- package/dist/claude/hook-layers.d.ts +4 -2
- package/dist/claude/hook-layers.d.ts.map +1 -1
- package/dist/claude/hook-layers.js +21 -4
- package/dist/claude/hook-layers.js.map +1 -1
- package/dist/claude/hooks.d.ts +9 -32
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +154 -350
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +56 -18
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/note-signals.d.ts +6 -0
- package/dist/claude/note-signals.d.ts.map +1 -1
- package/dist/claude/note-signals.js +14 -3
- package/dist/claude/note-signals.js.map +1 -1
- package/dist/claude/post-bash.d.ts +26 -0
- package/dist/claude/post-bash.d.ts.map +1 -0
- package/dist/claude/post-bash.js +191 -0
- package/dist/claude/post-bash.js.map +1 -0
- package/dist/claude/pre-bash-skills.d.ts +21 -0
- package/dist/claude/pre-bash-skills.d.ts.map +1 -0
- package/dist/claude/pre-bash-skills.js +253 -0
- package/dist/claude/pre-bash-skills.js.map +1 -0
- package/dist/claude/pre-bash.d.ts +50 -0
- package/dist/claude/pre-bash.d.ts.map +1 -0
- package/dist/claude/pre-bash.js +245 -0
- package/dist/claude/pre-bash.js.map +1 -0
- package/dist/claude/prompt-consent.d.ts +19 -0
- package/dist/claude/prompt-consent.d.ts.map +1 -0
- package/dist/claude/prompt-consent.js +14 -0
- package/dist/claude/prompt-consent.js.map +1 -0
- package/dist/claude/prompt-hook.d.ts +76 -0
- package/dist/claude/prompt-hook.d.ts.map +1 -0
- package/dist/claude/prompt-hook.js +303 -0
- package/dist/claude/prompt-hook.js.map +1 -0
- package/dist/claude/prompt-skills.d.ts +19 -0
- package/dist/claude/prompt-skills.d.ts.map +1 -0
- package/dist/claude/prompt-skills.js +305 -0
- package/dist/claude/prompt-skills.js.map +1 -0
- package/dist/claude/session-metrics.js +4 -4
- package/dist/claude/session-metrics.js.map +1 -1
- package/dist/claude/settings-merge.d.ts +10 -0
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +46 -17
- package/dist/claude/settings-merge.js.map +1 -1
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +86 -28
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/skill-template.d.ts +2 -0
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +53 -34
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/skill-use.d.ts +16 -0
- package/dist/claude/skill-use.d.ts.map +1 -0
- package/dist/claude/skill-use.js +78 -0
- package/dist/claude/skill-use.js.map +1 -0
- package/dist/claude/skills-wiring.d.ts +26 -0
- package/dist/claude/skills-wiring.d.ts.map +1 -0
- package/dist/claude/skills-wiring.js +110 -0
- package/dist/claude/skills-wiring.js.map +1 -0
- package/dist/claude/start-init.d.ts +13 -0
- package/dist/claude/start-init.d.ts.map +1 -0
- package/dist/claude/start-init.js +75 -0
- package/dist/claude/start-init.js.map +1 -0
- package/dist/claude/start-skills.d.ts +31 -0
- package/dist/claude/start-skills.d.ts.map +1 -0
- package/dist/claude/start-skills.js +274 -0
- package/dist/claude/start-skills.js.map +1 -0
- package/dist/claude/state.d.ts +19 -7
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js +59 -4
- package/dist/claude/state.js.map +1 -1
- package/dist/claude/stop-skills.d.ts +23 -0
- package/dist/claude/stop-skills.d.ts.map +1 -0
- package/dist/claude/stop-skills.js +233 -0
- package/dist/claude/stop-skills.js.map +1 -0
- package/dist/claude/stop.d.ts +23 -0
- package/dist/claude/stop.d.ts.map +1 -0
- package/dist/claude/stop.js +219 -0
- package/dist/claude/stop.js.map +1 -0
- package/dist/claude/tally.d.ts +29 -41
- package/dist/claude/tally.d.ts.map +1 -1
- package/dist/claude/tally.js +111 -165
- package/dist/claude/tally.js.map +1 -1
- package/dist/claude/walls-hook.d.ts +52 -0
- package/dist/claude/walls-hook.d.ts.map +1 -0
- package/dist/claude/walls-hook.js +279 -0
- package/dist/claude/walls-hook.js.map +1 -0
- package/dist/cli-epilogue.d.ts.map +1 -1
- package/dist/cli-epilogue.js +24 -14
- package/dist/cli-epilogue.js.map +1 -1
- package/dist/cli-meta.js +3 -3
- package/dist/cli-meta.js.map +1 -1
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +8 -6
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +238 -402
- package/dist/cli.js.map +1 -1
- package/dist/cloud/head-learnings.d.ts +13 -0
- package/dist/cloud/head-learnings.d.ts.map +1 -0
- package/dist/cloud/head-learnings.js +84 -0
- package/dist/cloud/head-learnings.js.map +1 -0
- package/dist/cloud/lookup-activity.d.ts +5 -3
- package/dist/cloud/lookup-activity.d.ts.map +1 -1
- package/dist/cloud/lookup-activity.js +8 -8
- package/dist/cloud/lookup-activity.js.map +1 -1
- package/dist/cloud/nudge.d.ts +15 -7
- package/dist/cloud/nudge.d.ts.map +1 -1
- package/dist/cloud/nudge.js +42 -33
- package/dist/cloud/nudge.js.map +1 -1
- package/dist/cloud/onboarding.d.ts +37 -1
- package/dist/cloud/onboarding.d.ts.map +1 -1
- package/dist/cloud/onboarding.js +77 -1
- package/dist/cloud/onboarding.js.map +1 -1
- package/dist/cloud/other-repos.d.ts +39 -0
- package/dist/cloud/other-repos.d.ts.map +1 -0
- package/dist/cloud/other-repos.js +137 -0
- package/dist/cloud/other-repos.js.map +1 -0
- package/dist/cloud/pair.d.ts +17 -7
- package/dist/cloud/pair.d.ts.map +1 -1
- package/dist/cloud/pair.js +81 -60
- package/dist/cloud/pair.js.map +1 -1
- package/dist/cloud/workspace-sync.d.ts +67 -11
- package/dist/cloud/workspace-sync.d.ts.map +1 -1
- package/dist/cloud/workspace-sync.js +299 -55
- package/dist/cloud/workspace-sync.js.map +1 -1
- package/dist/cloud/workspace.d.ts +74 -10
- package/dist/cloud/workspace.d.ts.map +1 -1
- package/dist/cloud/workspace.js +96 -8
- package/dist/cloud/workspace.js.map +1 -1
- package/dist/hosts/claude-global.js +1 -1
- package/dist/hosts/claude-global.js.map +1 -1
- package/dist/hosts/codex-hooks.d.ts +6 -0
- package/dist/hosts/codex-hooks.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.js +89 -16
- package/dist/hosts/codex-hooks.js.map +1 -1
- package/dist/hosts/codex-trust.d.ts +28 -0
- package/dist/hosts/codex-trust.d.ts.map +1 -0
- package/dist/hosts/codex-trust.js +280 -0
- package/dist/hosts/codex-trust.js.map +1 -0
- package/dist/hosts/cursor-hooks.d.ts.map +1 -1
- package/dist/hosts/cursor-hooks.js +3 -1
- package/dist/hosts/cursor-hooks.js.map +1 -1
- package/dist/init/candidates.d.ts +60 -0
- package/dist/init/candidates.d.ts.map +1 -0
- package/dist/init/candidates.js +202 -0
- package/dist/init/candidates.js.map +1 -0
- package/dist/init/claude-call.d.ts +51 -0
- package/dist/init/claude-call.d.ts.map +1 -0
- package/dist/init/claude-call.js +199 -0
- package/dist/init/claude-call.js.map +1 -0
- package/dist/init/discover-worker.d.ts +18 -0
- package/dist/init/discover-worker.d.ts.map +1 -0
- package/dist/init/discover-worker.js +111 -0
- package/dist/init/discover-worker.js.map +1 -0
- package/dist/init/handover.d.ts +16 -0
- package/dist/init/handover.d.ts.map +1 -0
- package/dist/init/handover.js +92 -0
- package/dist/init/handover.js.map +1 -0
- package/dist/init/howtos.d.ts +20 -0
- package/dist/init/howtos.d.ts.map +1 -0
- package/dist/init/howtos.js +90 -0
- package/dist/init/howtos.js.map +1 -0
- package/dist/init/init-record.d.ts +134 -0
- package/dist/init/init-record.d.ts.map +1 -0
- package/dist/init/init-record.js +339 -0
- package/dist/init/init-record.js.map +1 -0
- package/dist/init/install-paths.d.ts +6 -0
- package/dist/init/install-paths.d.ts.map +1 -0
- package/dist/init/install-paths.js +87 -0
- package/dist/init/install-paths.js.map +1 -0
- package/dist/init/keep-answer.d.ts +43 -0
- package/dist/init/keep-answer.d.ts.map +1 -0
- package/dist/init/keep-answer.js +118 -0
- package/dist/init/keep-answer.js.map +1 -0
- package/dist/init/keep-gate.d.ts +30 -0
- package/dist/init/keep-gate.d.ts.map +1 -0
- package/dist/init/keep-gate.js +178 -0
- package/dist/init/keep-gate.js.map +1 -0
- package/dist/init/keep-list.d.ts +67 -0
- package/dist/init/keep-list.d.ts.map +1 -0
- package/dist/init/keep-list.js +232 -0
- package/dist/init/keep-list.js.map +1 -0
- package/dist/init/keep.d.ts +52 -0
- package/dist/init/keep.d.ts.map +1 -0
- package/dist/init/keep.js +393 -0
- package/dist/init/keep.js.map +1 -0
- package/dist/init/look-back-mine.d.ts +37 -0
- package/dist/init/look-back-mine.d.ts.map +1 -0
- package/dist/init/look-back-mine.js +215 -0
- package/dist/init/look-back-mine.js.map +1 -0
- package/dist/init/look-back.d.ts +81 -0
- package/dist/init/look-back.d.ts.map +1 -0
- package/dist/init/look-back.js +170 -0
- package/dist/init/look-back.js.map +1 -0
- package/dist/init/miners.d.ts +46 -0
- package/dist/init/miners.d.ts.map +1 -0
- package/dist/init/miners.js +141 -0
- package/dist/init/miners.js.map +1 -0
- package/dist/init/name-call.d.ts +37 -0
- package/dist/init/name-call.d.ts.map +1 -0
- package/dist/init/name-call.js +151 -0
- package/dist/init/name-call.js.map +1 -0
- package/dist/init/plain-file.d.ts.map +1 -1
- package/dist/init/plain-file.js +3 -0
- package/dist/init/plain-file.js.map +1 -1
- package/dist/init/prefs.d.ts +70 -0
- package/dist/init/prefs.d.ts.map +1 -0
- package/dist/init/prefs.js +282 -0
- package/dist/init/prefs.js.map +1 -0
- package/dist/init/relay.d.ts +15 -0
- package/dist/init/relay.d.ts.map +1 -0
- package/dist/init/relay.js +15 -0
- package/dist/init/relay.js.map +1 -0
- package/dist/init/replay-runs.d.ts +121 -0
- package/dist/init/replay-runs.d.ts.map +1 -0
- package/dist/init/replay-runs.js +457 -0
- package/dist/init/replay-runs.js.map +1 -0
- package/dist/init/replay-tamper.d.ts +18 -0
- package/dist/init/replay-tamper.d.ts.map +1 -0
- package/dist/init/replay-tamper.js +87 -0
- package/dist/init/replay-tamper.js.map +1 -0
- package/dist/init/review-screen.d.ts +74 -0
- package/dist/init/review-screen.d.ts.map +1 -0
- package/dist/init/review-screen.js +258 -0
- package/dist/init/review-screen.js.map +1 -0
- package/dist/init/review-tty.d.ts +29 -0
- package/dist/init/review-tty.d.ts.map +1 -0
- package/dist/init/review-tty.js +218 -0
- package/dist/init/review-tty.js.map +1 -0
- package/dist/init/review.d.ts +12 -0
- package/dist/init/review.d.ts.map +1 -0
- package/dist/init/review.js +12 -0
- package/dist/init/review.js.map +1 -0
- package/dist/init/run.d.ts +87 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +347 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/sessions.d.ts +63 -0
- package/dist/init/sessions.d.ts.map +1 -0
- package/dist/init/sessions.js +377 -0
- package/dist/init/sessions.js.map +1 -0
- package/dist/init/transcript.d.ts +81 -0
- package/dist/init/transcript.d.ts.map +1 -0
- package/dist/init/transcript.js +216 -0
- package/dist/init/transcript.js.map +1 -0
- package/dist/init/types.d.ts +139 -0
- package/dist/init/types.d.ts.map +1 -0
- package/dist/init/types.js +2 -0
- package/dist/init/types.js.map +1 -0
- package/dist/notes/cold-start.d.ts +5 -213
- package/dist/notes/cold-start.d.ts.map +1 -1
- package/dist/notes/cold-start.js +5 -654
- package/dist/notes/cold-start.js.map +1 -1
- package/dist/notes/edit-overlap.d.ts.map +1 -1
- package/dist/notes/edit-overlap.js +4 -1
- package/dist/notes/edit-overlap.js.map +1 -1
- package/dist/notes/notes.d.ts +8 -4
- package/dist/notes/notes.d.ts.map +1 -1
- package/dist/notes/notes.js +9 -7
- package/dist/notes/notes.js.map +1 -1
- package/dist/notes/pr-reviews.d.ts +4 -0
- package/dist/notes/pr-reviews.d.ts.map +1 -1
- package/dist/notes/pr-reviews.js +6 -5
- package/dist/notes/pr-reviews.js.map +1 -1
- package/dist/notes/repo-trail.d.ts +1 -1
- package/dist/notes/repo-trail.d.ts.map +1 -1
- package/dist/notes/repo-trail.js +24 -4
- package/dist/notes/repo-trail.js.map +1 -1
- package/dist/search/grep.d.ts.map +1 -1
- package/dist/search/grep.js +4 -2
- package/dist/search/grep.js.map +1 -1
- package/dist/search/user-regexp.d.ts +26 -0
- package/dist/search/user-regexp.d.ts.map +1 -0
- package/dist/search/user-regexp.js +150 -0
- package/dist/search/user-regexp.js.map +1 -0
- package/dist/skills/anchors-cli.d.ts +5 -0
- package/dist/skills/anchors-cli.d.ts.map +1 -1
- package/dist/skills/anchors-cli.js +7 -2
- package/dist/skills/anchors-cli.js.map +1 -1
- package/dist/skills/anchors-wide.js +2 -2
- package/dist/skills/anchors-wide.js.map +1 -1
- package/dist/skills/cli-skills-1b.d.ts +17 -0
- package/dist/skills/cli-skills-1b.d.ts.map +1 -0
- package/dist/skills/cli-skills-1b.js +166 -0
- package/dist/skills/cli-skills-1b.js.map +1 -0
- package/dist/skills/cli-skills-2.d.ts +35 -0
- package/dist/skills/cli-skills-2.d.ts.map +1 -0
- package/dist/skills/cli-skills-2.js +129 -0
- package/dist/skills/cli-skills-2.js.map +1 -0
- package/dist/skills/cli-skills-report-1b.d.ts +49 -0
- package/dist/skills/cli-skills-report-1b.d.ts.map +1 -0
- package/dist/skills/cli-skills-report-1b.js +191 -0
- package/dist/skills/cli-skills-report-1b.js.map +1 -0
- package/dist/skills/cli-skills-report.d.ts +86 -0
- package/dist/skills/cli-skills-report.d.ts.map +1 -0
- package/dist/skills/cli-skills-report.js +121 -0
- package/dist/skills/cli-skills-report.js.map +1 -0
- package/dist/skills/cli-skills.d.ts +30 -0
- package/dist/skills/cli-skills.d.ts.map +1 -0
- package/dist/skills/cli-skills.js +379 -0
- package/dist/skills/cli-skills.js.map +1 -0
- package/dist/skills/compile-split.d.ts.map +1 -1
- package/dist/skills/compile-split.js +18 -0
- package/dist/skills/compile-split.js.map +1 -1
- package/dist/skills/compile-words.d.ts +4 -1
- package/dist/skills/compile-words.d.ts.map +1 -1
- package/dist/skills/compile-words.js +68 -8
- package/dist/skills/compile-words.js.map +1 -1
- package/dist/skills/compile.d.ts +7 -0
- package/dist/skills/compile.d.ts.map +1 -1
- package/dist/skills/compile.js +17 -0
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/credit.d.ts +86 -0
- package/dist/skills/credit.d.ts.map +1 -0
- package/dist/skills/credit.js +259 -0
- package/dist/skills/credit.js.map +1 -0
- package/dist/skills/fold-gated.d.ts +3 -0
- package/dist/skills/fold-gated.d.ts.map +1 -0
- package/dist/skills/fold-gated.js +78 -0
- package/dist/skills/fold-gated.js.map +1 -0
- package/dist/skills/fold.d.ts +87 -0
- package/dist/skills/fold.d.ts.map +1 -0
- package/dist/skills/fold.js +348 -0
- package/dist/skills/fold.js.map +1 -0
- package/dist/skills/follow-learnings.d.ts +128 -0
- package/dist/skills/follow-learnings.d.ts.map +1 -0
- package/dist/skills/follow-learnings.js +278 -0
- package/dist/skills/follow-learnings.js.map +1 -0
- package/dist/skills/learning-steps.d.ts +43 -0
- package/dist/skills/learning-steps.d.ts.map +1 -0
- package/dist/skills/learning-steps.js +294 -0
- package/dist/skills/learning-steps.js.map +1 -0
- package/dist/skills/lesson-gate.d.ts +25 -0
- package/dist/skills/lesson-gate.d.ts.map +1 -0
- package/dist/skills/lesson-gate.js +316 -0
- package/dist/skills/lesson-gate.js.map +1 -0
- package/dist/skills/offer-drift.d.ts +35 -0
- package/dist/skills/offer-drift.d.ts.map +1 -0
- package/dist/skills/offer-drift.js +98 -0
- package/dist/skills/offer-drift.js.map +1 -0
- package/dist/skills/offer-lesson.d.ts +31 -0
- package/dist/skills/offer-lesson.d.ts.map +1 -0
- package/dist/skills/offer-lesson.js +123 -0
- package/dist/skills/offer-lesson.js.map +1 -0
- package/dist/skills/offer-lines.d.ts +40 -0
- package/dist/skills/offer-lines.d.ts.map +1 -0
- package/dist/skills/offer-lines.js +80 -0
- package/dist/skills/offer-lines.js.map +1 -0
- package/dist/skills/offer-moment.d.ts +102 -0
- package/dist/skills/offer-moment.d.ts.map +1 -0
- package/dist/skills/offer-moment.js +260 -0
- package/dist/skills/offer-moment.js.map +1 -0
- package/dist/skills/offer-repair.d.ts +36 -0
- package/dist/skills/offer-repair.d.ts.map +1 -0
- package/dist/skills/offer-repair.js +212 -0
- package/dist/skills/offer-repair.js.map +1 -0
- package/dist/skills/offer-retire.d.ts +53 -0
- package/dist/skills/offer-retire.d.ts.map +1 -0
- package/dist/skills/offer-retire.js +149 -0
- package/dist/skills/offer-retire.js.map +1 -0
- package/dist/skills/offer-update.d.ts +54 -0
- package/dist/skills/offer-update.d.ts.map +1 -0
- package/dist/skills/offer-update.js +115 -0
- package/dist/skills/offer-update.js.map +1 -0
- package/dist/skills/offer-words-1b.d.ts +32 -0
- package/dist/skills/offer-words-1b.d.ts.map +1 -0
- package/dist/skills/offer-words-1b.js +80 -0
- package/dist/skills/offer-words-1b.js.map +1 -0
- package/dist/skills/offer-words-2.d.ts +123 -0
- package/dist/skills/offer-words-2.d.ts.map +1 -0
- package/dist/skills/offer-words-2.js +194 -0
- package/dist/skills/offer-words-2.js.map +1 -0
- package/dist/skills/offer-words.d.ts +65 -0
- package/dist/skills/offer-words.d.ts.map +1 -0
- package/dist/skills/offer-words.js +205 -0
- package/dist/skills/offer-words.js.map +1 -0
- package/dist/skills/offers.d.ts +66 -0
- package/dist/skills/offers.d.ts.map +1 -0
- package/dist/skills/offers.js +284 -0
- package/dist/skills/offers.js.map +1 -0
- package/dist/skills/runblock.d.ts +2 -0
- package/dist/skills/runblock.d.ts.map +1 -1
- package/dist/skills/runblock.js +11 -6
- package/dist/skills/runblock.js.map +1 -1
- package/dist/skills/save-gate.d.ts +64 -0
- package/dist/skills/save-gate.d.ts.map +1 -0
- package/dist/skills/save-gate.js +354 -0
- package/dist/skills/save-gate.js.map +1 -0
- package/dist/skills/save-verify.d.ts +59 -0
- package/dist/skills/save-verify.d.ts.map +1 -0
- package/dist/skills/save-verify.js +390 -0
- package/dist/skills/save-verify.js.map +1 -0
- package/dist/skills/settings.d.ts +12 -2
- package/dist/skills/settings.d.ts.map +1 -1
- package/dist/skills/settings.js +24 -2
- package/dist/skills/settings.js.map +1 -1
- package/dist/skills/skills.d.ts +26 -8
- package/dist/skills/skills.d.ts.map +1 -1
- package/dist/skills/skills.js +105 -52
- package/dist/skills/skills.js.map +1 -1
- package/dist/skills/types.d.ts +8 -0
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/skills/update-gate.d.ts +36 -0
- package/dist/skills/update-gate.d.ts.map +1 -0
- package/dist/skills/update-gate.js +357 -0
- package/dist/skills/update-gate.js.map +1 -0
- package/dist/skills/update-plan.d.ts +40 -0
- package/dist/skills/update-plan.d.ts.map +1 -0
- package/dist/skills/update-plan.js +196 -0
- package/dist/skills/update-plan.js.map +1 -0
- package/dist/skills/value-line.d.ts +94 -0
- package/dist/skills/value-line.d.ts.map +1 -0
- package/dist/skills/value-line.js +355 -0
- package/dist/skills/value-line.js.map +1 -0
- package/dist/telemetry/contract.d.ts +15 -1
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +80 -11
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/telemetry/gate.js +4 -4
- package/dist/telemetry/gate.js.map +1 -1
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/notice.js +5 -5
- package/dist/telemetry/notice.js.map +1 -1
- package/dist/telemetry/queue.d.ts.map +1 -1
- package/dist/telemetry/queue.js +7 -7
- package/dist/telemetry/queue.js.map +1 -1
- package/dist/telemetry/track.d.ts.map +1 -1
- package/dist/telemetry/track.js +4 -3
- package/dist/telemetry/track.js.map +1 -1
- package/dist/trace/checks.d.ts.map +1 -1
- package/dist/trace/checks.js +10 -2
- package/dist/trace/checks.js.map +1 -1
- package/dist/trace/gate-file.d.ts +1 -1
- package/dist/trace/gate-file.d.ts.map +1 -1
- package/dist/trace/gate-file.js +3 -3
- package/dist/trace/gate-file.js.map +1 -1
- package/dist/upkeep-run.d.ts.map +1 -1
- package/dist/upkeep-run.js +2 -23
- package/dist/upkeep-run.js.map +1 -1
- package/dist/upkeep.d.ts.map +1 -1
- package/dist/upkeep.js +2 -3
- package/dist/upkeep.js.map +1 -1
- package/dist/util/private-fs.d.ts +6 -0
- package/dist/util/private-fs.d.ts.map +1 -1
- package/dist/util/private-fs.js +9 -1
- package/dist/util/private-fs.js.map +1 -1
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +17 -3
- package/dist/util/state.js.map +1 -1
- package/package.json +1 -1
- package/dist/skills/found-skill.d.ts +0 -35
- package/dist/skills/found-skill.d.ts.map +0 -1
- package/dist/skills/found-skill.js +0 -66
- 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,
|
|
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
|
|
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,
|
|
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,
|
|
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 {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
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,
|
|
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: "
|
|
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
|
-
? "
|
|
127
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
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", "
|
|
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
|
-
.
|
|
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
|
|
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}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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}
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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" : "
|
|
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,
|
|
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
|
-
|
|
1361
|
-
if (!server)
|
|
1231
|
+
if (raw === undefined || !raw.trim())
|
|
1362
1232
|
return true;
|
|
1363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(", ")}
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1510
|
-
//
|
|
1511
|
-
//
|
|
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:
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
console.error(
|
|
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 (
|
|
1533
|
-
pair.phase("decisions", "running", "",
|
|
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 (
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
1617
|
-
//
|
|
1618
|
-
//
|
|
1619
|
-
//
|
|
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
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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 (
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
console.error(
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
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
|
-
|
|
1725
|
-
|
|
1726
|
-
report?.learned?.({ notes
|
|
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 (
|
|
1528
|
+
if (learned.inRepo)
|
|
1730
1529
|
console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
|
|
1731
1530
|
}
|
|
1732
1531
|
}
|
|
1733
|
-
//
|
|
1734
|
-
if (!
|
|
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
|
|
1777
|
-
: `· commit ${tops.slice(0, 5).join(" ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} to share it
|
|
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:
|
|
1805
|
-
await pair.finish(repo,
|
|
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})
|
|
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)
|
|
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})
|
|
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
|
|
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
|
-
? "
|
|
1836
|
+
? ": not valid JSON, left untouched (remove the graft entry by hand)"
|
|
2031
1837
|
: r.action === "deleted"
|
|
2032
|
-
? ` (${r.what}
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
2076
|
-
.argument("[dir]", "the repo
|
|
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
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2383
|
-
|
|
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
|
-
|
|
2406
|
-
|
|
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
|
|
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("✓
|
|
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
|
-
:
|
|
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
|
-
|
|
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}
|
|
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", "
|
|
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
|
-
|
|
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
|
|
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)",
|