@nanonets/graft 0.22.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +7 -6
  2. package/dist/app/checkout.d.ts +0 -32
  3. package/dist/app/checkout.d.ts.map +1 -1
  4. package/dist/app/checkout.js +0 -45
  5. package/dist/app/checkout.js.map +1 -1
  6. package/dist/app/identity.d.ts +0 -56
  7. package/dist/app/identity.d.ts.map +1 -1
  8. package/dist/app/identity.js +0 -119
  9. package/dist/app/identity.js.map +1 -1
  10. package/dist/app/main.js +0 -8
  11. package/dist/app/main.js.map +1 -1
  12. package/dist/app/server.d.ts +0 -20
  13. package/dist/app/server.d.ts.map +1 -1
  14. package/dist/app/server.js +0 -121
  15. package/dist/app/server.js.map +1 -1
  16. package/dist/check/learning-edits.d.ts +9 -0
  17. package/dist/check/learning-edits.d.ts.map +1 -0
  18. package/dist/check/learning-edits.js +62 -0
  19. package/dist/check/learning-edits.js.map +1 -0
  20. package/dist/claude/format.d.ts.map +1 -1
  21. package/dist/claude/format.js +4 -4
  22. package/dist/claude/format.js.map +1 -1
  23. package/dist/claude/shim-template.d.ts.map +1 -1
  24. package/dist/claude/shim-template.js +39 -3
  25. package/dist/claude/shim-template.js.map +1 -1
  26. package/dist/claude/skill-template.d.ts.map +1 -1
  27. package/dist/claude/skill-template.js +18 -7
  28. package/dist/claude/skill-template.js.map +1 -1
  29. package/dist/cli-picker.d.ts +15 -0
  30. package/dist/cli-picker.d.ts.map +1 -1
  31. package/dist/cli-picker.js +33 -3
  32. package/dist/cli-picker.js.map +1 -1
  33. package/dist/cli.js +100 -27
  34. package/dist/cli.js.map +1 -1
  35. package/dist/cloud/diff.d.ts +2 -0
  36. package/dist/cloud/diff.d.ts.map +1 -1
  37. package/dist/cloud/diff.js +1 -0
  38. package/dist/cloud/diff.js.map +1 -1
  39. package/dist/notes/cold-start.d.ts +54 -4
  40. package/dist/notes/cold-start.d.ts.map +1 -1
  41. package/dist/notes/cold-start.js +97 -36
  42. package/dist/notes/cold-start.js.map +1 -1
  43. package/dist/notes/notes.d.ts +19 -4
  44. package/dist/notes/notes.d.ts.map +1 -1
  45. package/dist/notes/notes.js +53 -15
  46. package/dist/notes/notes.js.map +1 -1
  47. package/dist/notes/pr-reviews.d.ts +108 -0
  48. package/dist/notes/pr-reviews.d.ts.map +1 -0
  49. package/dist/notes/pr-reviews.js +325 -0
  50. package/dist/notes/pr-reviews.js.map +1 -0
  51. package/dist/telemetry/contract.d.ts.map +1 -1
  52. package/dist/telemetry/contract.js +6 -5
  53. package/dist/telemetry/contract.js.map +1 -1
  54. package/package.json +1 -1
  55. package/dist/app/brain-build-process.d.ts +0 -22
  56. package/dist/app/brain-build-process.d.ts.map +0 -1
  57. package/dist/app/brain-build-process.js +0 -129
  58. package/dist/app/brain-build-process.js.map +0 -1
  59. package/dist/app/brain-build-worker.d.ts +0 -35
  60. package/dist/app/brain-build-worker.d.ts.map +0 -1
  61. package/dist/app/brain-build-worker.js +0 -56
  62. package/dist/app/brain-build-worker.js.map +0 -1
  63. package/dist/app/brain-build.d.ts +0 -160
  64. package/dist/app/brain-build.d.ts.map +0 -1
  65. package/dist/app/brain-build.js +0 -278
  66. package/dist/app/brain-build.js.map +0 -1
  67. package/dist/app/history.d.ts +0 -163
  68. package/dist/app/history.d.ts.map +0 -1
  69. package/dist/app/history.js +0 -319
  70. package/dist/app/history.js.map +0 -1
  71. package/dist/app/sources.d.ts +0 -81
  72. package/dist/app/sources.d.ts.map +0 -1
  73. package/dist/app/sources.js +0 -470
  74. 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 { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, savedByNote, commitLines, PERSONAL_SAVED_LINE, PR_SPEEDUP, SECTIONS, tokensWorthSaying, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
50
51
  import { 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";
@@ -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 })
@@ -1220,14 +1225,17 @@ async function runNoteCommand(repo, opts) {
1220
1225
  for (const n of saved)
1221
1226
  console.log(`✓ ${n.scope === "personal" ? "saved just for you" : "saved for your team"} · ${notePlace(n)}`);
1222
1227
  const note = saved[0];
1228
+ // Name a code file if the note touches one; docs are rarely what it's about.
1229
+ const about = touches.find((t) => !/\.(md|mdx|txt)$/i.test(t.split("#")[0])) ?? touches[0];
1230
+ const first = about ? ` that touches ${basename(about.split("#")[0])}` : "";
1223
1231
  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])}` : "";
1232
+ if (took && tokensWorthSaying(note.cost?.tokens)) {
1228
1233
  const reading = tokensLabel(readingTokens(note)).replace(" tokens", "");
1229
1234
  console.log(`· this ${took.replace(" to work out", " to figure out")}. the next session${first} gets it for ${reading}`);
1230
1235
  }
1236
+ else if (note.scope !== "personal") {
1237
+ console.log(`· the next session${first} starts from this, and gets to a PR ${PR_SPEEDUP}`);
1238
+ }
1231
1239
  // Learnings about the same files, from someone else: this one builds on theirs.
1232
1240
  const files = new Set(touches.map((t) => t.split("#")[0]));
1233
1241
  const prior = earlier.find((n) => n.author !== note.author && n.touches.some((t) => files.has(t.split("#")[0])));
@@ -1347,11 +1355,15 @@ async function runInitCommand(dir, opts) {
1347
1355
  const wantClaude = ids.includes("claude");
1348
1356
  const cliPath = fileURLToPath(import.meta.url);
1349
1357
  // 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.
1358
+ // in this repo's past sessions, and what reviewers kept flagging on its
1359
+ // merged PRs, read while the rest of init runs and saved as learnings at
1360
+ // the end. Once per repo, and never asked about first.
1352
1361
  const pastRead = TRAIL && wantClaude ? beginPastRead(repo) : null;
1353
1362
  if (pastRead)
1354
1363
  console.error(`· reading your ${pastRead.sessions} past Claude sessions here for what you keep telling Claude`);
1364
+ const reviewRead = TRAIL && wantClaude ? beginReviewRead(repo) : null;
1365
+ if (reviewRead)
1366
+ console.error("· reading review comments on this repo's merged PRs for what reviewers keep flagging");
1355
1367
  if (children.length)
1356
1368
  console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s) — ${children.join(", ")}`);
1357
1369
  // Today's output, file by file, behind --verbose. Everything else gets one
@@ -1418,26 +1430,82 @@ async function runInitCommand(dir, opts) {
1418
1430
  console.error(`✓ ${id.padEnd(width)} ${summary}`.trimEnd());
1419
1431
  }
1420
1432
  }
1421
- // Under trail: what init learned from past sessions, where this repo's
1422
- // learnings are, and how many there already are.
1433
+ // Under trail: what init learned from past sessions and PR reviews, where
1434
+ // this repo's learnings are, and how many there already are.
1423
1435
  let learnedNow = false;
1424
- if (TRAIL && pastRead) {
1425
- if (!pastRead.done)
1436
+ if (TRAIL && (pastRead || reviewRead)) {
1437
+ if (pastRead && !pastRead.done)
1426
1438
  console.error("· still reading your past sessions…");
1427
- const { result, saved } = await finishPastRead(repo, pastRead);
1428
- learnedNow = saved.length > 0;
1429
- if (saved.length) {
1430
- const n = saved.length;
1431
- console.error(`✓ learned ${n} thing${n === 1 ? "" : "s"} you told Claude more than once, from your last ${result.read} sessions here`);
1432
- for (const note of saved)
1433
- console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${note.title}`);
1439
+ const fromSessions = pastRead ? (await pastRead.result).findings : [];
1440
+ if (reviewRead && !reviewRead.done)
1441
+ console.error("· still reading PR reviews…");
1442
+ const fromReviews = reviewRead ? (await reviewRead.result).findings : [];
1443
+ // On a terminal, the person ticks which to keep in one list for both
1444
+ // sources, all ticked to start; a scripted run, or one with --yes, keeps
1445
+ // them all. Cancelling saves none, and the next init asks again.
1446
+ const picked = process.stdin.isTTY && process.stderr.isTTY && !opts.yes && fromSessions.length + fromReviews.length
1447
+ ? await pickLearnings([
1448
+ ...fromSessions.map((f) => ({ title: f.title, forTeam: f.for === "team", sessions: f.sessions.length })),
1449
+ ...fromReviews.map((f) => ({ title: f.title, forTeam: true, prs: f.prs.length })),
1450
+ ])
1451
+ : undefined;
1452
+ // The picked rows that are `found`'s, which start at `from` in the list.
1453
+ const keepFrom = (from) => picked === undefined
1454
+ ? undefined
1455
+ : async (found) => picked && picked.filter((i) => i >= from && i < from + found.length).map((i) => found[i - from]);
1456
+ const learned = [];
1457
+ if (pastRead) {
1458
+ const { result, saved, kept, cancelled } = await finishPastRead(repo, pastRead, keepFrom(0));
1459
+ const offered = result.findings.length;
1460
+ if (saved.length) {
1461
+ const n = saved.length;
1462
+ const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1463
+ console.error(`✓ ${what} you told Claude more than once, from your last ${result.read} sessions here`);
1464
+ // Each with how many sessions it was said in: saveFindings keeps the findings' order.
1465
+ saved.forEach((note, i) => {
1466
+ const said = kept[i]?.sessions.length ?? 0;
1467
+ const count = said > 1 ? `told Claude in ${said} sessions: ` : "";
1468
+ console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${count}${note.title}`);
1469
+ });
1470
+ }
1471
+ else if (cancelled) {
1472
+ console.error(`· saved none of the ${offered} things you told Claude more than once; ${cmd("graft init")} again asks again`);
1473
+ }
1474
+ else if (offered) {
1475
+ console.error(`· kept none of the ${offered} things you told Claude more than once`);
1476
+ }
1477
+ else if (result.reason === "failed") {
1478
+ console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
1479
+ }
1480
+ learned.push(...saved);
1481
+ }
1482
+ if (reviewRead) {
1483
+ const { result, saved, kept, cancelled } = await finishReviewRead(repo, reviewRead, keepFrom(fromSessions.length));
1484
+ const offered = result.findings.length;
1485
+ if (saved.length) {
1486
+ const n = saved.length;
1487
+ const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
1488
+ console.error(`✓ ${what} reviewers flagged on more than one PR, from the last ${result.prs} merged PRs here`);
1489
+ // saveReviewFindings keeps the findings' order.
1490
+ saved.forEach((note, i) => console.error(` for your team · flagged in review on ${kept[i]?.prs.length ?? 2} PRs: ${note.title}`));
1491
+ }
1492
+ else if (cancelled) {
1493
+ console.error(`· saved none of the ${offered} things reviewers flagged; ${cmd("graft init")} again asks again`);
1494
+ }
1495
+ else if (offered) {
1496
+ console.error(`· kept none of the ${offered} things reviewers flagged`);
1497
+ }
1498
+ else if (result.reason === "failed") {
1499
+ console.error(`· couldn't read PR reviews this time; ${cmd("graft init")} again tries again`);
1500
+ }
1501
+ learned.push(...saved);
1502
+ }
1503
+ learnedNow = learned.length > 0;
1504
+ if (learnedNow) {
1434
1505
  console.error("· Claude follows them from your next session");
1435
- if (saved.some((s) => s.scope === "repo"))
1506
+ if (learned.some((s) => s.scope === "repo"))
1436
1507
  console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
1437
1508
  }
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
1509
  }
1442
1510
  if (TRAIL && !learnedNow) {
1443
1511
  const home = notesHome.get(repo);
@@ -1931,15 +1999,20 @@ async function runTeamCheck(repo, opts) {
1931
1999
  return;
1932
2000
  }
1933
2001
  const files = d.files.filter((f) => !f.startsWith(".trail/"));
1934
- if (files.length === 0) {
2002
+ const me = noteAuthor(repo);
2003
+ // Learnings this branch changed, as they were before it: the check reads those,
2004
+ // and changing a teammate's is a finding of its own.
2005
+ const atBase = learningsAtBase(repo, d.from, d.files);
2006
+ const edits = teammateEdits(repo, atBase, me);
2007
+ if (files.length === 0 && edits.length === 0) {
1935
2008
  console.log(`· nothing to check: no changes against ${d.base}`);
1936
2009
  return;
1937
2010
  }
1938
2011
  maybeRefreshBranches(repo);
1939
2012
  if (d.truncated)
1940
2013
  console.error("⚠ the diff is over 400 KB; only the first 400 KB is checked");
1941
- const me = noteAuthor(repo);
1942
- const r = checkLocally({ diff: withoutTrailFiles(d.diff), files, notes: listNotes(repo), skills: listSkills(repo), asker: me });
2014
+ const r = checkLocally({ diff: withoutTrailFiles(d.diff), files, notes: withBaseLearnings(listNotes(repo), atBase), skills: listSkills(repo), asker: me });
2015
+ r.findings.unshift(...edits);
1943
2016
  const sources = r.findings.map((f) => f.source).filter((s) => s.path);
1944
2017
  recordActivity({
1945
2018
  repo: repoPlace(repo).key,