@nanonets/graft 0.22.0 → 0.23.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 +11 -10
- package/dist/app/checkout.d.ts +0 -32
- package/dist/app/checkout.d.ts.map +1 -1
- package/dist/app/checkout.js +0 -45
- package/dist/app/checkout.js.map +1 -1
- package/dist/app/identity.d.ts +0 -56
- package/dist/app/identity.d.ts.map +1 -1
- package/dist/app/identity.js +0 -119
- package/dist/app/identity.js.map +1 -1
- package/dist/app/main.js +0 -8
- package/dist/app/main.js.map +1 -1
- package/dist/app/server.d.ts +0 -20
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +0 -121
- package/dist/app/server.js.map +1 -1
- package/dist/ask/ask.d.ts +0 -3
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +1 -3
- package/dist/ask/ask.js.map +1 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +4 -1
- package/dist/brand.js.map +1 -1
- package/dist/check/learning-edits.d.ts +9 -0
- package/dist/check/learning-edits.d.ts.map +1 -0
- package/dist/check/learning-edits.js +62 -0
- package/dist/check/learning-edits.js.map +1 -0
- package/dist/claude/format.d.ts +1 -9
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +6 -21
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hook-layers.d.ts.map +1 -1
- package/dist/claude/hook-layers.js +5 -3
- package/dist/claude/hook-layers.js.map +1 -1
- package/dist/claude/hooks.d.ts +6 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +101 -14
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts +38 -7
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +172 -33
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/settings-merge.d.ts +23 -0
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +103 -7
- 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 +39 -3
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/skill-template.d.ts +4 -3
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +25 -10
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/state.d.ts +1 -1
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/cli-picker.d.ts +15 -0
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +33 -3
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.js +141 -60
- package/dist/cli.js.map +1 -1
- package/dist/cloud/diff.d.ts +2 -0
- package/dist/cloud/diff.d.ts.map +1 -1
- package/dist/cloud/diff.js +1 -0
- package/dist/cloud/diff.js.map +1 -1
- package/dist/context/map-dir.d.ts +13 -0
- package/dist/context/map-dir.d.ts.map +1 -0
- package/dist/context/map-dir.js +67 -0
- package/dist/context/map-dir.js.map +1 -0
- package/dist/context/node-file.d.ts +4 -3
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +18 -4
- package/dist/context/node-file.js.map +1 -1
- package/dist/hosts/claude-global.d.ts.map +1 -1
- package/dist/hosts/claude-global.js +31 -11
- package/dist/hosts/claude-global.js.map +1 -1
- package/dist/hosts/config-write.js +1 -1
- package/dist/hosts/config-write.js.map +1 -1
- package/dist/hosts/retract.d.ts.map +1 -1
- package/dist/hosts/retract.js +15 -6
- package/dist/hosts/retract.js.map +1 -1
- package/dist/notes/branches.d.ts +18 -1
- package/dist/notes/branches.d.ts.map +1 -1
- package/dist/notes/branches.js +73 -7
- package/dist/notes/branches.js.map +1 -1
- package/dist/notes/cold-start.d.ts +54 -4
- package/dist/notes/cold-start.d.ts.map +1 -1
- package/dist/notes/cold-start.js +97 -36
- package/dist/notes/cold-start.js.map +1 -1
- package/dist/notes/edit-overlap.d.ts +62 -0
- package/dist/notes/edit-overlap.d.ts.map +1 -0
- package/dist/notes/edit-overlap.js +202 -0
- package/dist/notes/edit-overlap.js.map +1 -0
- package/dist/notes/notes.d.ts +25 -4
- package/dist/notes/notes.d.ts.map +1 -1
- package/dist/notes/notes.js +65 -15
- package/dist/notes/notes.js.map +1 -1
- package/dist/notes/pr-reviews.d.ts +108 -0
- package/dist/notes/pr-reviews.d.ts.map +1 -0
- package/dist/notes/pr-reviews.js +325 -0
- package/dist/notes/pr-reviews.js.map +1 -0
- package/dist/notes/repo-trail.d.ts +3 -1
- package/dist/notes/repo-trail.d.ts.map +1 -1
- package/dist/notes/repo-trail.js +40 -3
- package/dist/notes/repo-trail.js.map +1 -1
- package/dist/notes/session-cost.d.ts +16 -4
- package/dist/notes/session-cost.d.ts.map +1 -1
- package/dist/notes/session-cost.js +120 -11
- package/dist/notes/session-cost.js.map +1 -1
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +6 -5
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/upkeep-run.d.ts.map +1 -1
- package/dist/upkeep-run.js +4 -2
- package/dist/upkeep-run.js.map +1 -1
- package/dist/upkeep.js +2 -2
- package/dist/upkeep.js.map +1 -1
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +2 -1
- package/dist/util/state.js.map +1 -1
- package/package.json +1 -1
- package/dist/app/brain-build-process.d.ts +0 -22
- package/dist/app/brain-build-process.d.ts.map +0 -1
- package/dist/app/brain-build-process.js +0 -129
- package/dist/app/brain-build-process.js.map +0 -1
- package/dist/app/brain-build-worker.d.ts +0 -35
- package/dist/app/brain-build-worker.d.ts.map +0 -1
- package/dist/app/brain-build-worker.js +0 -56
- package/dist/app/brain-build-worker.js.map +0 -1
- package/dist/app/brain-build.d.ts +0 -160
- package/dist/app/brain-build.d.ts.map +0 -1
- package/dist/app/brain-build.js +0 -278
- package/dist/app/brain-build.js.map +0 -1
- package/dist/app/history.d.ts +0 -163
- package/dist/app/history.d.ts.map +0 -1
- package/dist/app/history.js +0 -319
- package/dist/app/history.js.map +0 -1
- package/dist/app/sources.d.ts +0 -81
- package/dist/app/sources.d.ts.map +0 -1
- package/dist/app/sources.js +0 -470
- package/dist/app/sources.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { checkoutRoot, ensureRepoHome, keepsNotes, rememberRepo, repoName, repoP
|
|
|
26
26
|
import { brand, cmd, graftNotice, strandedRepoNotice, tag } from "./brand.js";
|
|
27
27
|
import { branchDiff } from "./cloud/diff.js";
|
|
28
28
|
import { openBrowser } from "./util/browser.js";
|
|
29
|
+
import { learningsAtBase, teammateEdits, withBaseLearnings } from "./check/learning-edits.js";
|
|
29
30
|
import { contextDirFor } from "./context/node-file.js";
|
|
30
31
|
import { loadGraphCached } from "./graph/load.js";
|
|
31
32
|
import { ensureFreshChildren, ensureFreshGraph, refreshNote } from "./graph/refresh.js";
|
|
@@ -37,7 +38,7 @@ import { runWorkspaceAsk, runWorkspaceBuild, runWorkspaceCallers, runWorkspaceCh
|
|
|
37
38
|
import { formatInitEpilogue } from "./cli-epilogue.js";
|
|
38
39
|
import { planInit, selectedWrites } from "./hosts/plan.js";
|
|
39
40
|
import { planRetract, runRetract, changed } from "./hosts/retract.js";
|
|
40
|
-
import { compactWrites, formatNonInteractiveHelp, formatPlan, runPicker } from "./cli-picker.js";
|
|
41
|
+
import { compactWrites, formatNonInteractiveHelp, formatPlan, pickLearnings, runPicker } from "./cli-picker.js";
|
|
41
42
|
import { buildGraphWithProgress } from "./claude/build-progress.js";
|
|
42
43
|
import { homedir } from "node:os";
|
|
43
44
|
import { formatUpgradeReport, formatVersionReport, getNpmViewVersion, readCurrentVersion, readPackageName, runUpgrade } from "./cli-meta.js";
|
|
@@ -46,9 +47,10 @@ import { patchBuildConfig } from "./util/state.js";
|
|
|
46
47
|
import { normalizePathPrefix } from "./util/paths.js";
|
|
47
48
|
import { latestSession, formatSessionStats, sessionInputRate } from "./claude/session-metrics.js";
|
|
48
49
|
import { listSessionIds, readSession, sessionDir } from "./claude/state.js";
|
|
49
|
-
import { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, savedByNote, commitLines, PERSONAL_SAVED_LINE, SECTIONS, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
|
|
50
|
-
import { currentSessionCost } from "./notes/session-cost.js";
|
|
50
|
+
import { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, savedByNote, commitLines, PERSONAL_SAVED_LINE, PR_SPEEDUP, SECTIONS, tokensWorthSaying, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
|
|
51
|
+
import { aboutTerms, currentSessionCost } from "./notes/session-cost.js";
|
|
51
52
|
import { beginPastRead, finishPastRead } from "./notes/cold-start.js";
|
|
53
|
+
import { beginReviewRead, finishReviewRead } from "./notes/pr-reviews.js";
|
|
52
54
|
import { currentBranch, noteAuthor, teammatesOn } from "./notes/git-facts.js";
|
|
53
55
|
import { confirm, fold, getSkill, learn, listSkills, publish, skillForAgent, skillName, skillStatus } from "./skills/skills.js";
|
|
54
56
|
import { clearPending, cloudLines, loginLink, sessionKey, waitForConnect } from "./cloud/nudge.js";
|
|
@@ -163,7 +165,7 @@ function queryRoot(dir) {
|
|
|
163
165
|
return resolve(dir);
|
|
164
166
|
const { root, levels } = nearestGraftRoot(process.cwd(), program.opts().dir);
|
|
165
167
|
if (levels > 0)
|
|
166
|
-
console.error(`${tag()} no
|
|
168
|
+
console.error(`${tag()} no code map here — answering from ${contextDirFor(root, program.opts().dir)}`);
|
|
167
169
|
return root;
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
@@ -267,15 +269,18 @@ function spelling(action) {
|
|
|
267
269
|
// Hidden: spawned detached by maybeSyncInBackground when a workspace repo's
|
|
268
270
|
// learnings changed, or uses are waiting to go. Running it by hand syncs now.
|
|
269
271
|
// A repo that moved from graft to trail at session start reads its past
|
|
270
|
-
// sessions here, detached, so the session doesn't wait on it.
|
|
272
|
+
// sessions and PR reviews here, detached, so the session doesn't wait on it.
|
|
271
273
|
program
|
|
272
274
|
.command("_trail-past-read", { hidden: true })
|
|
273
275
|
.argument("<dir>")
|
|
274
276
|
.action(async (dir) => {
|
|
275
277
|
const repo = resolve(dir);
|
|
276
278
|
const read = beginPastRead(repo);
|
|
279
|
+
const reviews = beginReviewRead(repo);
|
|
277
280
|
if (read)
|
|
278
281
|
await finishPastRead(repo, read);
|
|
282
|
+
if (reviews)
|
|
283
|
+
await finishReviewRead(repo, reviews);
|
|
279
284
|
});
|
|
280
285
|
program
|
|
281
286
|
.command("_trail-sync", { hidden: true })
|
|
@@ -287,10 +292,10 @@ program
|
|
|
287
292
|
// Hidden: spawned detached by maybeRefreshBranches.
|
|
288
293
|
program
|
|
289
294
|
.command("_trail-branches", { hidden: true })
|
|
290
|
-
.description("internal: fetch, then note which files teammates'
|
|
295
|
+
.description("internal: fetch, then note which files teammates' open branches change and the learnings they carry")
|
|
291
296
|
.argument("[dir]", "target repo directory", ".")
|
|
292
297
|
.action((dir) => {
|
|
293
|
-
refreshBranches(resolve(dir));
|
|
298
|
+
refreshBranches(resolve(dir), { prs: true });
|
|
294
299
|
});
|
|
295
300
|
program
|
|
296
301
|
.command("_trail-claim", { hidden: true })
|
|
@@ -375,7 +380,7 @@ function moveHereToTrail(dir) {
|
|
|
375
380
|
}
|
|
376
381
|
grouped(program.command("build"), GROUP.setup)
|
|
377
382
|
.description(TRAIL
|
|
378
|
-
? "Rebuild the code map in
|
|
383
|
+
? "Rebuild the code map in .trail/cache/ ($0, no key). --check fails if it's stale; --deep adds LLM summaries."
|
|
379
384
|
: "Build graft/ from your code — wiring graph + per-file cards ($0, no key). " +
|
|
380
385
|
"Add --deep for the LLM concept map + per-symbol summaries/crux.")
|
|
381
386
|
.argument("[dir]", "repository root", ".")
|
|
@@ -605,11 +610,11 @@ function pointerFile(pointer) {
|
|
|
605
610
|
return pointer.replace(/:L\d+(-L?\d+)?$/, "");
|
|
606
611
|
}
|
|
607
612
|
/**
|
|
608
|
-
* After `ask`, under trail: which shown learnings are a teammate's,
|
|
609
|
-
* teammates' pushed branches change the files the answer points into, and the
|
|
613
|
+
* After `ask`, under trail: which shown learnings are a teammate's, and the
|
|
610
614
|
* shown learnings recorded as a use, which is what the team page counts as
|
|
611
615
|
* reuse. Then the background upkeep that keeps branches and the workspace
|
|
612
|
-
* current. Spawns no git itself.
|
|
616
|
+
* current. Spawns no git itself. Teammates' branches come up after an edit
|
|
617
|
+
* instead, once there's a function to compare (notes/edit-overlap.ts).
|
|
613
618
|
*/
|
|
614
619
|
function afterAsk(r, repo) {
|
|
615
620
|
if (!r.notesChecked)
|
|
@@ -618,12 +623,6 @@ function afterAsk(r, repo) {
|
|
|
618
623
|
maybeRefreshBranches(repo);
|
|
619
624
|
const me = noteAuthor(repo);
|
|
620
625
|
const files = [...new Set(r.hits.map((h) => pointerFile(h.pointer)))];
|
|
621
|
-
const overlaps = overlapsWith(repo, files, me).slice(0, 2);
|
|
622
|
-
if (overlaps.length) {
|
|
623
|
-
r.overlaps = overlaps.map((o) => formatOverlap(o));
|
|
624
|
-
if (!process.stdout.isTTY)
|
|
625
|
-
r.overlaps.push(`${tag()} Start your reply with the overlap line${overlaps.length === 1 ? "" : "s"} above, word for word, so the user knows.`);
|
|
626
|
-
}
|
|
627
626
|
const shown = r.notes ?? [];
|
|
628
627
|
if (shown.length) {
|
|
629
628
|
for (const h of shown)
|
|
@@ -745,7 +744,7 @@ async function runCheckCommand(dirArg, opts, label) {
|
|
|
745
744
|
console.log(JSON.stringify({ context: r, graph: g.missing ? null : g }, null, 2));
|
|
746
745
|
}
|
|
747
746
|
else if (bothMissing) {
|
|
748
|
-
console.log(`${label}: NO GRAPH\n\nNo
|
|
747
|
+
console.log(`${label}: NO GRAPH\n\nNo code map found. Run \`${cmd("graft build")}\` first.`);
|
|
749
748
|
}
|
|
750
749
|
else {
|
|
751
750
|
if (r.missing) {
|
|
@@ -1187,9 +1186,11 @@ async function runNoteCommand(repo, opts) {
|
|
|
1187
1186
|
};
|
|
1188
1187
|
const minutes = wholeNumber(opts.minutes, "--minutes");
|
|
1189
1188
|
const tokens = wholeNumber(opts.tokens, "--tokens");
|
|
1190
|
-
// What the session spent, from the agent's own transcript when there is one
|
|
1191
|
-
//
|
|
1192
|
-
|
|
1189
|
+
// What the session spent, from the agent's own transcript when there is one,
|
|
1190
|
+
// counted from where the work the note names first came up; the flags win,
|
|
1191
|
+
// since only the agent knows for sure where its task began.
|
|
1192
|
+
const named = [...(opts.touches?.split(",").map((t) => t.trim()).filter(Boolean) ?? []), ...mentionedFiles(repo, body)];
|
|
1193
|
+
const spent = currentSessionCost(repo, undefined, Date.now(), aboutTerms(body, named));
|
|
1193
1194
|
const cost = minutes !== undefined || tokens !== undefined || spent
|
|
1194
1195
|
? { minutes: minutes ?? spent?.minutes, tokens: tokens ?? spent?.tokens }
|
|
1195
1196
|
: undefined;
|
|
@@ -1220,14 +1221,17 @@ async function runNoteCommand(repo, opts) {
|
|
|
1220
1221
|
for (const n of saved)
|
|
1221
1222
|
console.log(`✓ ${n.scope === "personal" ? "saved just for you" : "saved for your team"} · ${notePlace(n)}`);
|
|
1222
1223
|
const note = saved[0];
|
|
1224
|
+
// Name a code file if the note touches one; docs are rarely what it's about.
|
|
1225
|
+
const about = touches.find((t) => !/\.(md|mdx|txt)$/i.test(t.split("#")[0])) ?? touches[0];
|
|
1226
|
+
const first = about ? ` that touches ${basename(about.split("#")[0])}` : "";
|
|
1223
1227
|
const took = costLabel(note.cost);
|
|
1224
|
-
if (took) {
|
|
1225
|
-
// Name a code file if the note touches one; docs are rarely what it's about.
|
|
1226
|
-
const about = touches.find((t) => !/\.(md|mdx|txt)$/i.test(t.split("#")[0])) ?? touches[0];
|
|
1227
|
-
const first = about ? ` that touches ${basename(about.split("#")[0])}` : "";
|
|
1228
|
+
if (took && tokensWorthSaying(note.cost?.tokens)) {
|
|
1228
1229
|
const reading = tokensLabel(readingTokens(note)).replace(" tokens", "");
|
|
1229
1230
|
console.log(`· this ${took.replace(" to work out", " to figure out")}. the next session${first} gets it for ${reading}`);
|
|
1230
1231
|
}
|
|
1232
|
+
else if (note.scope !== "personal") {
|
|
1233
|
+
console.log(`· the next session${first} starts from this, and gets to a PR ${PR_SPEEDUP}`);
|
|
1234
|
+
}
|
|
1231
1235
|
// Learnings about the same files, from someone else: this one builds on theirs.
|
|
1232
1236
|
const files = new Set(touches.map((t) => t.split("#")[0]));
|
|
1233
1237
|
const prior = earlier.find((n) => n.author !== note.author && n.touches.some((t) => files.has(t.split("#")[0])));
|
|
@@ -1346,19 +1350,11 @@ async function runInitCommand(dir, opts) {
|
|
|
1346
1350
|
}
|
|
1347
1351
|
const wantClaude = ids.includes("claude");
|
|
1348
1352
|
const cliPath = fileURLToPath(import.meta.url);
|
|
1349
|
-
// Under trail, with Claude Code picked: what the person kept telling Claude
|
|
1350
|
-
// in this repo's past sessions, read while the rest of init runs and saved
|
|
1351
|
-
// as learnings at the end. Once per repo, and never asked about first.
|
|
1352
|
-
const pastRead = TRAIL && wantClaude ? beginPastRead(repo) : null;
|
|
1353
|
-
if (pastRead)
|
|
1354
|
-
console.error(`· reading your ${pastRead.sessions} past Claude sessions here for what you keep telling Claude`);
|
|
1355
|
-
if (children.length)
|
|
1356
|
-
console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s) — ${children.join(", ")}`);
|
|
1357
|
-
// Today's output, file by file, behind --verbose. Everything else gets one
|
|
1358
|
-
// line per step: the graph, then one per agent.
|
|
1359
|
-
const verbose = opts.verbose === true;
|
|
1360
1353
|
// Under trail, each repo gets its `.trail/learnings/`, committed with the
|
|
1361
1354
|
// code, and its folder in ~/.trail for personal learnings and corrections.
|
|
1355
|
+
// First, before the reads below send their first event: that makes the
|
|
1356
|
+
// repo one on trail, so the event's repo id lands in `.trail/cache/` and
|
|
1357
|
+
// not in a `graft/` that nothing ignores.
|
|
1362
1358
|
const notesHome = new Map();
|
|
1363
1359
|
if (TRAIL)
|
|
1364
1360
|
for (const target of targets) {
|
|
@@ -1368,6 +1364,21 @@ async function runInitCommand(dir, opts) {
|
|
|
1368
1364
|
if (place.git)
|
|
1369
1365
|
maybeRefreshBranches(place.checkout);
|
|
1370
1366
|
}
|
|
1367
|
+
// Under trail, with Claude Code picked: what the person kept telling Claude
|
|
1368
|
+
// in this repo's past sessions, and what reviewers kept flagging on its
|
|
1369
|
+
// merged PRs, read while the rest of init runs and saved as learnings at
|
|
1370
|
+
// the end. Once per repo, and never asked about first.
|
|
1371
|
+
const pastRead = TRAIL && wantClaude ? beginPastRead(repo) : null;
|
|
1372
|
+
if (pastRead)
|
|
1373
|
+
console.error(`· reading your ${pastRead.sessions} past Claude sessions here for what you keep telling Claude`);
|
|
1374
|
+
const reviewRead = TRAIL && wantClaude ? beginReviewRead(repo) : null;
|
|
1375
|
+
if (reviewRead)
|
|
1376
|
+
console.error("· reading review comments on this repo's merged PRs for what reviewers keep flagging");
|
|
1377
|
+
if (children.length)
|
|
1378
|
+
console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s) — ${children.join(", ")}`);
|
|
1379
|
+
// Today's output, file by file, behind --verbose. Everything else gets one
|
|
1380
|
+
// line per step: the graph, then one per agent.
|
|
1381
|
+
const verbose = opts.verbose === true;
|
|
1371
1382
|
const reports = [];
|
|
1372
1383
|
for (const target of targets) {
|
|
1373
1384
|
if (verbose && target !== repo)
|
|
@@ -1418,26 +1429,82 @@ async function runInitCommand(dir, opts) {
|
|
|
1418
1429
|
console.error(`✓ ${id.padEnd(width)} ${summary}`.trimEnd());
|
|
1419
1430
|
}
|
|
1420
1431
|
}
|
|
1421
|
-
// Under trail: what init learned from past sessions, where
|
|
1422
|
-
// learnings are, and how many there already are.
|
|
1432
|
+
// Under trail: what init learned from past sessions and PR reviews, where
|
|
1433
|
+
// this repo's learnings are, and how many there already are.
|
|
1423
1434
|
let learnedNow = false;
|
|
1424
|
-
if (TRAIL && pastRead) {
|
|
1425
|
-
if (!pastRead.done)
|
|
1435
|
+
if (TRAIL && (pastRead || reviewRead)) {
|
|
1436
|
+
if (pastRead && !pastRead.done)
|
|
1426
1437
|
console.error("· still reading your past sessions…");
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1438
|
+
const fromSessions = pastRead ? (await pastRead.result).findings : [];
|
|
1439
|
+
if (reviewRead && !reviewRead.done)
|
|
1440
|
+
console.error("· still reading PR reviews…");
|
|
1441
|
+
const fromReviews = reviewRead ? (await reviewRead.result).findings : [];
|
|
1442
|
+
// On a terminal, the person ticks which to keep in one list for both
|
|
1443
|
+
// sources, all ticked to start; a scripted run, or one with --yes, keeps
|
|
1444
|
+
// them all. Cancelling saves none, and the next init asks again.
|
|
1445
|
+
const picked = process.stdin.isTTY && process.stderr.isTTY && !opts.yes && fromSessions.length + fromReviews.length
|
|
1446
|
+
? await pickLearnings([
|
|
1447
|
+
...fromSessions.map((f) => ({ title: f.title, forTeam: f.for === "team", sessions: f.sessions.length })),
|
|
1448
|
+
...fromReviews.map((f) => ({ title: f.title, forTeam: true, prs: f.prs.length })),
|
|
1449
|
+
])
|
|
1450
|
+
: undefined;
|
|
1451
|
+
// The picked rows that are `found`'s, which start at `from` in the list.
|
|
1452
|
+
const keepFrom = (from) => picked === undefined
|
|
1453
|
+
? undefined
|
|
1454
|
+
: async (found) => picked && picked.filter((i) => i >= from && i < from + found.length).map((i) => found[i - from]);
|
|
1455
|
+
const learned = [];
|
|
1456
|
+
if (pastRead) {
|
|
1457
|
+
const { result, saved, kept, cancelled } = await finishPastRead(repo, pastRead, keepFrom(0));
|
|
1458
|
+
const offered = result.findings.length;
|
|
1459
|
+
if (saved.length) {
|
|
1460
|
+
const n = saved.length;
|
|
1461
|
+
const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
|
|
1462
|
+
console.error(`✓ ${what} you told Claude more than once, from your last ${result.read} sessions here`);
|
|
1463
|
+
// Each with how many sessions it was said in: saveFindings keeps the findings' order.
|
|
1464
|
+
saved.forEach((note, i) => {
|
|
1465
|
+
const said = kept[i]?.sessions.length ?? 0;
|
|
1466
|
+
const count = said > 1 ? `told Claude in ${said} sessions: ` : "";
|
|
1467
|
+
console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${count}${note.title}`);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
else if (cancelled) {
|
|
1471
|
+
console.error(`· saved none of the ${offered} things you told Claude more than once; ${cmd("graft init")} again asks again`);
|
|
1472
|
+
}
|
|
1473
|
+
else if (offered) {
|
|
1474
|
+
console.error(`· kept none of the ${offered} things you told Claude more than once`);
|
|
1475
|
+
}
|
|
1476
|
+
else if (result.reason === "failed") {
|
|
1477
|
+
console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
|
|
1478
|
+
}
|
|
1479
|
+
learned.push(...saved);
|
|
1480
|
+
}
|
|
1481
|
+
if (reviewRead) {
|
|
1482
|
+
const { result, saved, kept, cancelled } = await finishReviewRead(repo, reviewRead, keepFrom(fromSessions.length));
|
|
1483
|
+
const offered = result.findings.length;
|
|
1484
|
+
if (saved.length) {
|
|
1485
|
+
const n = saved.length;
|
|
1486
|
+
const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
|
|
1487
|
+
console.error(`✓ ${what} reviewers flagged on more than one PR, from the last ${result.prs} merged PRs here`);
|
|
1488
|
+
// saveReviewFindings keeps the findings' order.
|
|
1489
|
+
saved.forEach((note, i) => console.error(` for your team · flagged in review on ${kept[i]?.prs.length ?? 2} PRs: ${note.title}`));
|
|
1490
|
+
}
|
|
1491
|
+
else if (cancelled) {
|
|
1492
|
+
console.error(`· saved none of the ${offered} things reviewers flagged; ${cmd("graft init")} again asks again`);
|
|
1493
|
+
}
|
|
1494
|
+
else if (offered) {
|
|
1495
|
+
console.error(`· kept none of the ${offered} things reviewers flagged`);
|
|
1496
|
+
}
|
|
1497
|
+
else if (result.reason === "failed") {
|
|
1498
|
+
console.error(`· couldn't read PR reviews this time; ${cmd("graft init")} again tries again`);
|
|
1499
|
+
}
|
|
1500
|
+
learned.push(...saved);
|
|
1501
|
+
}
|
|
1502
|
+
learnedNow = learned.length > 0;
|
|
1503
|
+
if (learnedNow) {
|
|
1434
1504
|
console.error("· Claude follows them from your next session");
|
|
1435
|
-
if (
|
|
1505
|
+
if (learned.some((s) => s.scope === "repo"))
|
|
1436
1506
|
console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
|
|
1437
1507
|
}
|
|
1438
|
-
else if (result.reason === "failed") {
|
|
1439
|
-
console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
|
|
1440
|
-
}
|
|
1441
1508
|
}
|
|
1442
1509
|
if (TRAIL && !learnedNow) {
|
|
1443
1510
|
const home = notesHome.get(repo);
|
|
@@ -1455,8 +1522,12 @@ async function runInitCommand(dir, opts) {
|
|
|
1455
1522
|
for (const w of selectedWrites(plan, ids)) {
|
|
1456
1523
|
if (w.scope !== "repo")
|
|
1457
1524
|
continue;
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1525
|
+
const rel = shown(repo, w.path);
|
|
1526
|
+
// trail's Claude Code setup shares one file in .claude/; the rest there is personal.
|
|
1527
|
+
if (TRAIL && rel.startsWith(".claude/") && rel !== ".claude/settings.json")
|
|
1528
|
+
continue;
|
|
1529
|
+
const parts = rel.split("/");
|
|
1530
|
+
const top = TRAIL && rel === ".claude/settings.json" ? rel : parts.length > 1 ? `${parts[0]}/` : parts[0];
|
|
1460
1531
|
if (!tops.includes(top))
|
|
1461
1532
|
tops.push(top);
|
|
1462
1533
|
}
|
|
@@ -1470,8 +1541,9 @@ async function runInitCommand(dir, opts) {
|
|
|
1470
1541
|
});
|
|
1471
1542
|
tops.splice(0, tops.length, ...changedTops);
|
|
1472
1543
|
if (tops.length)
|
|
1473
|
-
console.error(
|
|
1474
|
-
(
|
|
1544
|
+
console.error(TRAIL
|
|
1545
|
+
? `· 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`
|
|
1546
|
+
: `· commit ${tops.slice(0, 5).join(" ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} to share it — graft/ stays local and git-ignored`);
|
|
1475
1547
|
}
|
|
1476
1548
|
// One epilogue for the whole run. A workspace parent holds no nodes of its
|
|
1477
1549
|
// own, so the totals come from the children — the graph the user actually got.
|
|
@@ -1522,8 +1594,12 @@ function wireTarget(repo, ids, ctx) {
|
|
|
1522
1594
|
say(`✓ wrote ${res.settingsPath}`);
|
|
1523
1595
|
for (const s of res.shims)
|
|
1524
1596
|
say(`✓ wrote ${s}`);
|
|
1525
|
-
|
|
1526
|
-
|
|
1597
|
+
if (res.skill)
|
|
1598
|
+
say(`✓ wrote ${res.skill}`);
|
|
1599
|
+
if (!res.mcp) {
|
|
1600
|
+
// trail registers no server for Claude Code: the hooks and the `trail` command cover it.
|
|
1601
|
+
}
|
|
1602
|
+
else if (res.mcp.action === "skipped-unparseable")
|
|
1527
1603
|
say(`⚠ .mcp.json: ${res.mcp.path} left unchanged (not valid JSON) — add the graft server manually`);
|
|
1528
1604
|
else if (res.mcp.action === "unchanged")
|
|
1529
1605
|
say(`· mcp claude: ${res.mcp.path} (already registered)`);
|
|
@@ -1931,15 +2007,20 @@ async function runTeamCheck(repo, opts) {
|
|
|
1931
2007
|
return;
|
|
1932
2008
|
}
|
|
1933
2009
|
const files = d.files.filter((f) => !f.startsWith(".trail/"));
|
|
1934
|
-
|
|
2010
|
+
const me = noteAuthor(repo);
|
|
2011
|
+
// Learnings this branch changed, as they were before it: the check reads those,
|
|
2012
|
+
// and changing a teammate's is a finding of its own.
|
|
2013
|
+
const atBase = learningsAtBase(repo, d.from, d.files);
|
|
2014
|
+
const edits = teammateEdits(repo, atBase, me);
|
|
2015
|
+
if (files.length === 0 && edits.length === 0) {
|
|
1935
2016
|
console.log(`· nothing to check: no changes against ${d.base}`);
|
|
1936
2017
|
return;
|
|
1937
2018
|
}
|
|
1938
2019
|
maybeRefreshBranches(repo);
|
|
1939
2020
|
if (d.truncated)
|
|
1940
2021
|
console.error("⚠ the diff is over 400 KB; only the first 400 KB is checked");
|
|
1941
|
-
const
|
|
1942
|
-
|
|
2022
|
+
const r = checkLocally({ diff: withoutTrailFiles(d.diff), files, notes: withBaseLearnings(listNotes(repo), atBase), skills: listSkills(repo), asker: me });
|
|
2023
|
+
r.findings.unshift(...edits);
|
|
1943
2024
|
const sources = r.findings.map((f) => f.source).filter((s) => s.path);
|
|
1944
2025
|
recordActivity({
|
|
1945
2026
|
repo: repoPlace(repo).key,
|