@nanonets/graft 0.23.0 → 0.24.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 +4 -4
- package/dist/ai/crux.d.ts +31 -3
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +88 -10
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/llm/openai.d.ts +101 -0
- package/dist/ai/llm/openai.d.ts.map +1 -1
- package/dist/ai/llm/openai.js +396 -6
- package/dist/ai/llm/openai.js.map +1 -1
- package/dist/ai/llm/recover-tool.d.ts +2 -0
- package/dist/ai/llm/recover-tool.d.ts.map +1 -1
- package/dist/ai/llm/recover-tool.js +1 -1
- package/dist/ai/llm/recover-tool.js.map +1 -1
- package/dist/ai/synthesize.d.ts.map +1 -1
- package/dist/ai/synthesize.js +100 -1
- package/dist/ai/synthesize.js.map +1 -1
- package/dist/claude/format.d.ts +21 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +46 -1
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hooks.d.ts +5 -9
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +71 -58
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.js +3 -3
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/note-signals.d.ts +28 -0
- package/dist/claude/note-signals.d.ts.map +1 -0
- package/dist/claude/note-signals.js +325 -0
- package/dist/claude/note-signals.js.map +1 -0
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +17 -6
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +49 -18
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/state.d.ts +4 -3
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/claude/sync-run.js +1 -1
- package/dist/claude/sync-run.js.map +1 -1
- package/dist/cli-picker.d.ts +12 -5
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +24 -8
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +149 -43
- package/dist/cli.js.map +1 -1
- package/dist/context/batches.d.ts +29 -0
- package/dist/context/batches.d.ts.map +1 -0
- package/dist/context/batches.js +130 -0
- package/dist/context/batches.js.map +1 -0
- package/dist/context/build.d.ts +7 -2
- package/dist/context/build.d.ts.map +1 -1
- package/dist/context/build.js +84 -50
- package/dist/context/build.js.map +1 -1
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +41 -4
- package/dist/context/node-file.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/graph/cards.d.ts.map +1 -1
- package/dist/graph/cards.js +9 -4
- package/dist/graph/cards.js.map +1 -1
- package/dist/graph/enrich.d.ts.map +1 -1
- package/dist/graph/enrich.js +8 -3
- package/dist/graph/enrich.js.map +1 -1
- package/dist/graph/generic.d.ts.map +1 -1
- package/dist/graph/generic.js +207 -6
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/lsp/client.d.ts +18 -0
- package/dist/graph/lsp/client.d.ts.map +1 -1
- package/dist/graph/lsp/client.js +40 -2
- package/dist/graph/lsp/client.js.map +1 -1
- package/dist/graph/lsp/enrich.d.ts.map +1 -1
- package/dist/graph/lsp/enrich.js +9 -1
- package/dist/graph/lsp/enrich.js.map +1 -1
- package/dist/graph/lsp/registry.d.ts +7 -0
- package/dist/graph/lsp/registry.d.ts.map +1 -1
- package/dist/graph/lsp/registry.js +27 -2
- package/dist/graph/lsp/registry.js.map +1 -1
- package/dist/graph/queries/gleam.scm +51 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +65 -3
- package/dist/graph/resolve.js.map +1 -1
- package/dist/graph/workspace-cli.d.ts +2 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -1
- package/dist/graph/workspace-cli.js +1 -1
- package/dist/graph/workspace-cli.js.map +1 -1
- package/dist/init-guard.d.ts +11 -0
- package/dist/init-guard.d.ts.map +1 -0
- package/dist/init-guard.js +21 -0
- package/dist/init-guard.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +2 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/notes/branches.d.ts +7 -1
- package/dist/notes/branches.d.ts.map +1 -1
- package/dist/notes/branches.js +31 -3
- package/dist/notes/branches.js.map +1 -1
- package/dist/notes/cold-start.d.ts +12 -2
- package/dist/notes/cold-start.d.ts.map +1 -1
- package/dist/notes/cold-start.js +46 -5
- package/dist/notes/cold-start.js.map +1 -1
- package/dist/notes/credit.d.ts +37 -0
- package/dist/notes/credit.d.ts.map +1 -0
- package/dist/notes/credit.js +78 -0
- package/dist/notes/credit.js.map +1 -0
- package/dist/notes/find-notes.d.ts +17 -0
- package/dist/notes/find-notes.d.ts.map +1 -0
- package/dist/notes/find-notes.js +48 -0
- package/dist/notes/find-notes.js.map +1 -0
- package/dist/notes/flatten.d.ts +31 -0
- package/dist/notes/flatten.d.ts.map +1 -0
- package/dist/notes/flatten.js +73 -0
- package/dist/notes/flatten.js.map +1 -0
- package/dist/notes/git-facts.d.ts +7 -1
- package/dist/notes/git-facts.d.ts.map +1 -1
- package/dist/notes/git-facts.js +12 -2
- package/dist/notes/git-facts.js.map +1 -1
- package/dist/notes/home.d.ts +24 -6
- package/dist/notes/home.d.ts.map +1 -1
- package/dist/notes/home.js +82 -15
- package/dist/notes/home.js.map +1 -1
- package/dist/notes/learning-fields.d.ts +102 -0
- package/dist/notes/learning-fields.d.ts.map +1 -0
- package/dist/notes/learning-fields.js +214 -0
- package/dist/notes/learning-fields.js.map +1 -0
- package/dist/notes/memory-notes.d.ts +71 -0
- package/dist/notes/memory-notes.d.ts.map +1 -0
- package/dist/notes/memory-notes.js +239 -0
- package/dist/notes/memory-notes.js.map +1 -0
- package/dist/notes/note-gate.d.ts +42 -0
- package/dist/notes/note-gate.d.ts.map +1 -0
- package/dist/notes/note-gate.js +181 -0
- package/dist/notes/note-gate.js.map +1 -0
- package/dist/notes/notes.d.ts +29 -77
- package/dist/notes/notes.d.ts.map +1 -1
- package/dist/notes/notes.js +69 -228
- package/dist/notes/notes.js.map +1 -1
- package/dist/notes/pr-reviews.d.ts +35 -7
- package/dist/notes/pr-reviews.d.ts.map +1 -1
- package/dist/notes/pr-reviews.js +156 -46
- package/dist/notes/pr-reviews.js.map +1 -1
- package/dist/notes/secrets.d.ts +28 -0
- package/dist/notes/secrets.d.ts.map +1 -0
- package/dist/notes/secrets.js +362 -0
- package/dist/notes/secrets.js.map +1 -0
- package/dist/notes/session-cost.d.ts +2 -0
- package/dist/notes/session-cost.d.ts.map +1 -1
- package/dist/notes/session-cost.js +8 -3
- package/dist/notes/session-cost.js.map +1 -1
- package/dist/notes/targets.d.ts +37 -0
- package/dist/notes/targets.d.ts.map +1 -0
- package/dist/notes/targets.js +108 -0
- package/dist/notes/targets.js.map +1 -0
- package/dist/notes/team-check.d.ts +22 -0
- package/dist/notes/team-check.d.ts.map +1 -0
- package/dist/notes/team-check.js +130 -0
- package/dist/notes/team-check.js.map +1 -0
- package/dist/notes/trail-home.d.ts +3 -0
- package/dist/notes/trail-home.d.ts.map +1 -0
- package/dist/notes/trail-home.js +12 -0
- package/dist/notes/trail-home.js.map +1 -0
- package/dist/search/grep-cli.d.ts +5 -2
- package/dist/search/grep-cli.d.ts.map +1 -1
- package/dist/search/grep-cli.js +11 -4
- package/dist/search/grep-cli.js.map +1 -1
- package/dist/search/ignored-code.d.ts +9 -0
- package/dist/search/ignored-code.d.ts.map +1 -0
- package/dist/search/ignored-code.js +73 -0
- package/dist/search/ignored-code.js.map +1 -0
- package/dist/skills/answers-prompt.d.ts +13 -0
- package/dist/skills/answers-prompt.d.ts.map +1 -0
- package/dist/skills/answers-prompt.js +83 -0
- package/dist/skills/answers-prompt.js.map +1 -0
- package/dist/skills/never-save.d.ts +5 -0
- package/dist/skills/never-save.d.ts.map +1 -0
- package/dist/skills/never-save.js +158 -0
- package/dist/skills/never-save.js.map +1 -0
- package/dist/skills/params.d.ts +39 -0
- package/dist/skills/params.d.ts.map +1 -0
- package/dist/skills/params.js +275 -0
- package/dist/skills/params.js.map +1 -0
- package/dist/skills/ran-less.d.ts +5 -0
- package/dist/skills/ran-less.d.ts.map +1 -0
- package/dist/skills/ran-less.js +51 -0
- package/dist/skills/ran-less.js.map +1 -0
- package/dist/skills/risk-delta.d.ts +32 -0
- package/dist/skills/risk-delta.d.ts.map +1 -0
- package/dist/skills/risk-delta.js +56 -0
- package/dist/skills/risk-delta.js.map +1 -0
- package/dist/skills/risk.d.ts +73 -0
- package/dist/skills/risk.d.ts.map +1 -0
- package/dist/skills/risk.js +396 -0
- package/dist/skills/risk.js.map +1 -0
- package/dist/skills/types.d.ts +422 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +8 -6
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/telemetry/notice.d.ts.map +1 -1
- package/dist/telemetry/notice.js +3 -1
- package/dist/telemetry/notice.js.map +1 -1
- package/dist/trace/check-reads.d.ts +36 -0
- package/dist/trace/check-reads.d.ts.map +1 -0
- package/dist/trace/check-reads.js +57 -0
- package/dist/trace/check-reads.js.map +1 -0
- package/dist/trace/host.d.ts +40 -0
- package/dist/trace/host.d.ts.map +1 -0
- package/dist/trace/host.js +84 -0
- package/dist/trace/host.js.map +1 -0
- package/dist/trace/redact.d.ts +8 -0
- package/dist/trace/redact.d.ts.map +1 -0
- package/dist/trace/redact.js +63 -0
- package/dist/trace/redact.js.map +1 -0
- package/dist/trace/seen-cache.d.ts +9 -0
- package/dist/trace/seen-cache.d.ts.map +1 -0
- package/dist/trace/seen-cache.js +46 -0
- package/dist/trace/seen-cache.js.map +1 -0
- package/dist/trace/shell-scan.d.ts +52 -0
- package/dist/trace/shell-scan.d.ts.map +1 -0
- package/dist/trace/shell-scan.js +318 -0
- package/dist/trace/shell-scan.js.map +1 -0
- package/dist/trace/shell-words.d.ts +91 -0
- package/dist/trace/shell-words.d.ts.map +1 -0
- package/dist/trace/shell-words.js +558 -0
- package/dist/trace/shell-words.js.map +1 -0
- package/dist/trace/shell.d.ts +91 -0
- package/dist/trace/shell.d.ts.map +1 -0
- package/dist/trace/shell.js +435 -0
- package/dist/trace/shell.js.map +1 -0
- package/dist/trace/state.d.ts +27 -0
- package/dist/trace/state.d.ts.map +1 -0
- package/dist/trace/state.js +98 -0
- package/dist/trace/state.js.map +1 -0
- package/dist/trace/tamper-code.d.ts +9 -0
- package/dist/trace/tamper-code.d.ts.map +1 -0
- package/dist/trace/tamper-code.js +174 -0
- package/dist/trace/tamper-code.js.map +1 -0
- package/dist/trace/tamper-git.d.ts +12 -0
- package/dist/trace/tamper-git.d.ts.map +1 -0
- package/dist/trace/tamper-git.js +77 -0
- package/dist/trace/tamper-git.js.map +1 -0
- package/dist/trace/types.d.ts +477 -0
- package/dist/trace/types.d.ts.map +1 -0
- package/dist/trace/types.js +12 -0
- package/dist/trace/types.js.map +1 -0
- package/dist/trace/walls-glob.d.ts +11 -0
- package/dist/trace/walls-glob.d.ts.map +1 -0
- package/dist/trace/walls-glob.js +264 -0
- package/dist/trace/walls-glob.js.map +1 -0
- package/dist/trace/walls-quoted.d.ts +25 -0
- package/dist/trace/walls-quoted.d.ts.map +1 -0
- package/dist/trace/walls-quoted.js +81 -0
- package/dist/trace/walls-quoted.js.map +1 -0
- package/dist/upkeep.d.ts +1 -1
- package/dist/upkeep.d.ts.map +1 -1
- package/dist/upkeep.js +4 -1
- package/dist/upkeep.js.map +1 -1
- package/dist/util/actions-folder.d.ts +2 -0
- package/dist/util/actions-folder.d.ts.map +1 -0
- package/dist/util/actions-folder.js +9 -0
- package/dist/util/actions-folder.js.map +1 -0
- package/dist/util/front-matter.d.ts +31 -4
- package/dist/util/front-matter.d.ts.map +1 -1
- package/dist/util/front-matter.js +95 -3
- package/dist/util/front-matter.js.map +1 -1
- package/dist/util/private-file.d.ts +14 -0
- package/dist/util/private-file.d.ts.map +1 -0
- package/dist/util/private-file.js +82 -0
- package/dist/util/private-file.js.map +1 -0
- package/dist/util/private-fs.d.ts +25 -0
- package/dist/util/private-fs.d.ts.map +1 -0
- package/dist/util/private-fs.js +253 -0
- package/dist/util/private-fs.js.map +1 -0
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +3 -0
- package/dist/util/state.js.map +1 -1
- package/dist/viewer/app.js +2 -2
- package/package.json +5 -5
- package/scripts/run-tests.mjs +14 -2
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ import { contextDirFor } from "./context/node-file.js";
|
|
|
31
31
|
import { loadGraphCached } from "./graph/load.js";
|
|
32
32
|
import { ensureFreshChildren, ensureFreshGraph, refreshNote } from "./graph/refresh.js";
|
|
33
33
|
import { isWorkspaceBuildRoot, readWorkspace } from "./graph/workspace.js";
|
|
34
|
+
import { NOT_GIT_LINE, homeFolderRefusal, isGitWorkTree } from "./init-guard.js";
|
|
34
35
|
import { hasGraftIndex, nearestGraftRoot } from "./graph/root.js";
|
|
35
36
|
import { unsupportedExtensions, supportedExtensions } from "./graph/source-files.js";
|
|
36
37
|
import { discoverWorkspaceChildren } from "./graph/scopes.js";
|
|
@@ -48,10 +49,13 @@ import { normalizePathPrefix } from "./util/paths.js";
|
|
|
48
49
|
import { latestSession, formatSessionStats, sessionInputRate } from "./claude/session-metrics.js";
|
|
49
50
|
import { listSessionIds, readSession, sessionDir } from "./claude/state.js";
|
|
50
51
|
import { costLabel, listNotes, noteName, notePlace, noteTargets, readingTokens, savedByNote, commitLines, PERSONAL_SAVED_LINE, PR_SPEEDUP, SECTIONS, tokensWorthSaying, sectionLead, shortDate, tokensLabel, writeNote, mentionedFiles } from "./notes/notes.js";
|
|
52
|
+
import { findLearning, liveLearnings } from "./notes/learning-fields.js";
|
|
51
53
|
import { aboutTerms, currentSessionCost } from "./notes/session-cost.js";
|
|
52
54
|
import { beginPastRead, finishPastRead } from "./notes/cold-start.js";
|
|
53
|
-
import { beginReviewRead, finishReviewRead } from "./notes/pr-reviews.js";
|
|
55
|
+
import { anchoredReviews, beginReviewRead, finishReviewRead } from "./notes/pr-reviews.js";
|
|
56
|
+
import { finishMemoryRead, memoryCandidates, offeredBefore } from "./notes/memory-notes.js";
|
|
54
57
|
import { currentBranch, noteAuthor, teammatesOn } from "./notes/git-facts.js";
|
|
58
|
+
import { gateNote, namesRepoSymbol, symbolCandidates, trackedFiles } from "./notes/note-gate.js";
|
|
55
59
|
import { confirm, fold, getSkill, learn, listSkills, publish, skillForAgent, skillName, skillStatus } from "./skills/skills.js";
|
|
56
60
|
import { clearPending, cloudLines, loginLink, sessionKey, waitForConnect } from "./cloud/nudge.js";
|
|
57
61
|
import { addRepo, clearCloud, cloudEnabled, fetchMe, fetchTeam, isError, readCloud, writeCloud } from "./cloud/workspace.js";
|
|
@@ -389,6 +393,8 @@ grouped(program.command("build"), GROUP.setup)
|
|
|
389
393
|
.option("--deep", "run the LLM pass: concept nodes (graft/*.md) + per-symbol summary/crux")
|
|
390
394
|
.option("-e, --extensions <exts...>", 'code extensions to include (e.g. ".ts" ".py"); an extension with no parser is ignored with a warning that lists the supported set')
|
|
391
395
|
.option("-j, --concurrency <n>", "files summarized in parallel during --deep (default 5)")
|
|
396
|
+
.option("--synth-concurrency <n>", "concept synthesis batches in flight at once during --deep (default 4). Separate from -j: " +
|
|
397
|
+
"each synthesis call carries a whole batch of summaries, not one file")
|
|
392
398
|
.option("--no-reuse", "re-parse every file instead of replaying unchanged ones from the extraction cache")
|
|
393
399
|
.option("--lsp", "add compiler-grade call edges via a language server if one is installed (opt-in, slower; e.g. rust-analyzer, clangd)")
|
|
394
400
|
.option("--allow-partial", "with --deep: exit 0 even when some files' summaries failed (default: a degraded meaning tier exits 1)")
|
|
@@ -422,6 +428,11 @@ grouped(program.command("build"), GROUP.setup)
|
|
|
422
428
|
console.error(`✗ --concurrency must be a number, got "${opts.concurrency}"`);
|
|
423
429
|
process.exit(1);
|
|
424
430
|
}
|
|
431
|
+
const synthConcurrency = opts.synthConcurrency ? Math.max(1, Number(opts.synthConcurrency)) : undefined;
|
|
432
|
+
if (opts.synthConcurrency && !Number.isFinite(synthConcurrency)) {
|
|
433
|
+
console.error(`✗ --synth-concurrency must be a number, got "${opts.synthConcurrency}"`);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
425
436
|
warnUnsupportedExtensions(opts.extensions);
|
|
426
437
|
// Persisted BEFORE the build itself runs, so this invocation's walks (and
|
|
427
438
|
// every later no-flag build / hooks refresh) see it identically — the
|
|
@@ -498,6 +509,7 @@ grouped(program.command("build"), GROUP.setup)
|
|
|
498
509
|
deep: !!deep,
|
|
499
510
|
extensions: opts.extensions,
|
|
500
511
|
concurrency,
|
|
512
|
+
synthConcurrency,
|
|
501
513
|
childConfig: cliConfig(),
|
|
502
514
|
override: buildGlobalDir,
|
|
503
515
|
includeDirs: opts.includeDir,
|
|
@@ -514,6 +526,7 @@ grouped(program.command("build"), GROUP.setup)
|
|
|
514
526
|
const c = await engine.init(dir, {
|
|
515
527
|
extensions: opts.extensions,
|
|
516
528
|
onlyDirs,
|
|
529
|
+
synthConcurrency,
|
|
517
530
|
onProgress: ({ phase, index, total, file }) => process.stderr.write(`\r${phase === "summarize" ? "reading" : "writing"} concepts ${index + 1}/${total}: ${file.slice(0, 40).padEnd(40)}`),
|
|
518
531
|
}).catch((err) => {
|
|
519
532
|
// Only the stage and a code enum; the message stays on this machine.
|
|
@@ -521,7 +534,11 @@ grouped(program.command("build"), GROUP.setup)
|
|
|
521
534
|
throw err;
|
|
522
535
|
});
|
|
523
536
|
process.stderr.write("\n");
|
|
524
|
-
|
|
537
|
+
const conceptLine = `concepts: ${c.nodes} nodes, ${c.links} links from ${c.files} files (${c.summarized} read, ${c.cached} cached)`;
|
|
538
|
+
if (c.nodes === 0)
|
|
539
|
+
console.error(`⚠ ${conceptLine}`);
|
|
540
|
+
else
|
|
541
|
+
console.log(`✓ ${conceptLine}`);
|
|
525
542
|
for (const e of c.errors)
|
|
526
543
|
console.error(`✗ ${e}`);
|
|
527
544
|
conceptErrors = c.errors;
|
|
@@ -983,6 +1000,8 @@ grouped(program.command("note", { hidden: !TRAIL }), GROUP.memory)
|
|
|
983
1000
|
.option("--author <name>", "who wrote it (default: the first word of git config user.name)")
|
|
984
1001
|
.option("--minutes <n>", "what it took to work out, when the agent's transcript can't say")
|
|
985
1002
|
.option("--tokens <n>", "the same, in tokens")
|
|
1003
|
+
.option("--replaces <file>", "an earlier learning this one takes over from (its file name in .trail/learnings/); the old file stays")
|
|
1004
|
+
.option("--anyway", "save it even if trail's checks (names code, says why, not status, not a repeat) turn it away")
|
|
986
1005
|
.option("--json", "print the saved learning as JSON")
|
|
987
1006
|
.argument("[dir]", "repository root", ".")
|
|
988
1007
|
.action(async (dir, opts) => {
|
|
@@ -1198,19 +1217,60 @@ async function runNoteCommand(repo, opts) {
|
|
|
1198
1217
|
// session edited. Explicit --touches means just this repo.
|
|
1199
1218
|
const targets = opts.touches
|
|
1200
1219
|
? [{ dir: repo, touches: opts.touches.split(",").map((t) => t.trim()).filter(Boolean) }]
|
|
1201
|
-
: noteTargets(repo, spent?.edited ?? [], spent?.startedAt).map((t) => ({
|
|
1202
|
-
|
|
1203
|
-
//
|
|
1204
|
-
touches: [...new Set([...t.touches, ...mentionedFiles(t.dir, body)])].slice(0, 10),
|
|
1220
|
+
: noteTargets(repo, spent?.edited ?? [], spent?.startedAt, spent ? { read: spent.read } : undefined).map((t) => ({
|
|
1221
|
+
dir: t.dir,
|
|
1222
|
+
// Then the files the note names, then the ones the session only read.
|
|
1223
|
+
touches: [...new Set([...t.touches, ...mentionedFiles(t.dir, body), ...(t.read ?? [])])].slice(0, 10),
|
|
1205
1224
|
}));
|
|
1225
|
+
// A touch is a file git tracks or one this session edited: never a file
|
|
1226
|
+
// that was just lying around (a token, a scratch file).
|
|
1227
|
+
const real = (dir, files) => {
|
|
1228
|
+
const tracked = trackedFiles(dir, files.map((f) => f.split("#")[0]));
|
|
1229
|
+
const edited = new Set((spent?.edited ?? []).map((f) => relative(dir, f).split("\\").join("/")));
|
|
1230
|
+
return files.filter((f) => tracked.has(f.split("#")[0]) || edited.has(f.split("#")[0]));
|
|
1231
|
+
};
|
|
1232
|
+
for (const t of targets)
|
|
1233
|
+
t.touches = real(t.dir, t.touches);
|
|
1234
|
+
const scope = opts.personal ? "personal" : "repo";
|
|
1235
|
+
const primary = targets[0];
|
|
1236
|
+
const existing = listNotes(primary.dir, { branches: false });
|
|
1237
|
+
let old;
|
|
1238
|
+
if (opts.replaces) {
|
|
1239
|
+
const want = basename(opts.replaces.trim());
|
|
1240
|
+
old = findLearning(existing, want);
|
|
1241
|
+
if (!old) {
|
|
1242
|
+
console.error(`✗ --replaces: no learning named ${want} here. The names are the files in .trail/learnings/.`);
|
|
1243
|
+
process.exitCode = 1;
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
// What `replaces:` holds is the old learning's id (learning-fields.ts), not its file name.
|
|
1248
|
+
const replaces = old?.id;
|
|
1249
|
+
const text = `${opts.title}\n${body}`;
|
|
1250
|
+
const gate = gateNote({
|
|
1251
|
+
title: opts.title,
|
|
1252
|
+
body,
|
|
1253
|
+
personal: scope === "personal",
|
|
1254
|
+
files: real(primary.dir, [...new Set([...mentionedFiles(primary.dir, text), ...(opts.touches ? primary.touches : [])])]),
|
|
1255
|
+
namesSymbol: namesRepoSymbol(primary.dir, symbolCandidates(text)),
|
|
1256
|
+
touches: primary.touches,
|
|
1257
|
+
replaces,
|
|
1258
|
+
}, liveLearnings(existing).filter((n) => (n.scope ?? "repo") === scope));
|
|
1259
|
+
if (gate.problems.length && !opts.anyway) {
|
|
1260
|
+
console.error("✗ not saved yet:");
|
|
1261
|
+
for (const p of gate.problems)
|
|
1262
|
+
console.error(` · ${p}`);
|
|
1263
|
+
console.error(`Fix it and run ${cmd("graft note")} again, or add --anyway if a check is wrong.`);
|
|
1264
|
+
process.exitCode = 1;
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1206
1267
|
const author = opts.author?.trim() || noteAuthor(repo);
|
|
1207
1268
|
const date = new Date().toISOString().slice(0, 10);
|
|
1208
|
-
const scope = opts.personal ? "personal" : "repo";
|
|
1209
1269
|
const earlier = [];
|
|
1210
1270
|
const saved = [];
|
|
1211
1271
|
for (const t of targets) {
|
|
1212
1272
|
earlier.push(...listNotes(t.dir));
|
|
1213
|
-
saved.push(writeNote(t.dir, { title: opts.title, body, author, date, branch: currentBranch(t.dir), cost, touches: t.touches }, scope));
|
|
1273
|
+
saved.push(writeNote(t.dir, { title: opts.title, body, author, date, branch: currentBranch(t.dir), cost, touches: t.touches, replaces }, scope));
|
|
1214
1274
|
}
|
|
1215
1275
|
const touches = targets.flatMap((t) => t.touches);
|
|
1216
1276
|
track("note_saved", { has_cost: String(cost?.tokens !== undefined || cost?.minutes !== undefined), touches_bucket: countBucket(touches.length), scope: saved[0]?.scope ?? scope }, { repo });
|
|
@@ -1239,6 +1299,10 @@ async function runNoteCommand(repo, opts) {
|
|
|
1239
1299
|
console.log(`· builds on ${prior.author ? `${prior.author}'s` : "a"} learning from ${shortDate(prior.date)}`);
|
|
1240
1300
|
if (!SECTIONS.some((s) => sectionLead(body, s.heading) !== null))
|
|
1241
1301
|
console.log("· tip: learnings read best under ## Decided, ## Tried and ruled out and ## Watch out");
|
|
1302
|
+
for (const w of gate.warnings)
|
|
1303
|
+
console.log(`· ${w}`);
|
|
1304
|
+
if (old)
|
|
1305
|
+
console.log(`· takes over from ${noteName(old)}, which stays as the record`);
|
|
1242
1306
|
const repoSaved = saved.filter((n) => n.scope === "repo");
|
|
1243
1307
|
if (repoSaved.length) {
|
|
1244
1308
|
// Who else works on these files, for the case for committing it.
|
|
@@ -1246,7 +1310,8 @@ async function runNoteCommand(repo, opts) {
|
|
|
1246
1310
|
console.log(`${lines.uncommitted} · git add ${repoSaved.map(notePlace).join(" ")}`);
|
|
1247
1311
|
if (!process.stdout.isTTY && TRAIL)
|
|
1248
1312
|
console.log(`[trail] Close your reply with one line, word for word. If this learning is committed by the end of your turn: ${lines.committed}\n` +
|
|
1249
|
-
` If it isn't: ${
|
|
1313
|
+
` If it isn't: ${commitAsk(note.title, about)}\n` +
|
|
1314
|
+
" On a yes, commit it with the change: in the change's own commit when that isn't pushed yet.");
|
|
1250
1315
|
}
|
|
1251
1316
|
else {
|
|
1252
1317
|
console.log("· just for you: kept on this machine, out of the repo");
|
|
@@ -1259,6 +1324,11 @@ async function runNoteCommand(repo, opts) {
|
|
|
1259
1324
|
}
|
|
1260
1325
|
printLines(cloudLines(repo, { command: "note" }));
|
|
1261
1326
|
}
|
|
1327
|
+
/** What the agent asks after saving a team learning it hasn't committed: who reads it, and when. */
|
|
1328
|
+
export function commitAsk(title, about) {
|
|
1329
|
+
const when = about ? `when they open ${basename(about.split("#")[0])}` : "on their next session here";
|
|
1330
|
+
return `📝 Learning: "${title.replace(/"/g, "'")}" · your teammates' Claude reads it ${when}. Commit it with this change? (y/n)`;
|
|
1331
|
+
}
|
|
1262
1332
|
/** Lines that close a command's output, with a blank line before them. */
|
|
1263
1333
|
function printLines(lines) {
|
|
1264
1334
|
if (lines.length)
|
|
@@ -1277,6 +1347,14 @@ async function runInitCommand(dir, opts) {
|
|
|
1277
1347
|
}
|
|
1278
1348
|
const repo = resolve(dir);
|
|
1279
1349
|
const explicit = Array.isArray(opts.agents) ? opts.agents : undefined;
|
|
1350
|
+
// Checked before anything is written: run from the home folder, init wired
|
|
1351
|
+
// it, mapped every code file under it, then said to commit.
|
|
1352
|
+
const refusal = homeFolderRefusal(repo);
|
|
1353
|
+
if (refusal) {
|
|
1354
|
+
console.error(refusal);
|
|
1355
|
+
process.exitCode = 1;
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1280
1358
|
if (explicit) {
|
|
1281
1359
|
const validIds = [...hostIds(), "claude"];
|
|
1282
1360
|
const unknown = explicit.filter((id) => !validIds.includes(id));
|
|
@@ -1429,23 +1507,29 @@ async function runInitCommand(dir, opts) {
|
|
|
1429
1507
|
console.error(`✓ ${id.padEnd(width)} ${summary}`.trimEnd());
|
|
1430
1508
|
}
|
|
1431
1509
|
}
|
|
1432
|
-
// Under trail: what init learned
|
|
1433
|
-
//
|
|
1510
|
+
// Under trail: what init learned, where this repo's learnings are, and how
|
|
1511
|
+
// many there already are. The team's come from what is tied to the code:
|
|
1512
|
+
// what reviewers kept flagging on merged PRs, and Claude Code's notes about
|
|
1513
|
+
// the repo on this machine. What the person kept telling Claude is theirs.
|
|
1434
1514
|
let learnedNow = false;
|
|
1435
|
-
|
|
1515
|
+
let teamSaved = 0;
|
|
1516
|
+
const memory = TRAIL && wantClaude ? memoryCandidates(repo, { offered: offeredBefore(repo) }) : [];
|
|
1517
|
+
if (TRAIL && (pastRead || reviewRead || memory.length)) {
|
|
1436
1518
|
if (pastRead && !pastRead.done)
|
|
1437
1519
|
console.error("· still reading your past sessions…");
|
|
1438
1520
|
const fromSessions = pastRead ? (await pastRead.result).findings : [];
|
|
1439
1521
|
if (reviewRead && !reviewRead.done)
|
|
1440
1522
|
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
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1445
|
-
|
|
1523
|
+
const fromReviews = reviewRead ? anchoredReviews(repo, (await reviewRead.result).findings) : [];
|
|
1524
|
+
// On a terminal, the person ticks which to keep in one list, the team's
|
|
1525
|
+
// first and ticked, their own unticked. A scripted run, or one with
|
|
1526
|
+
// --yes, keeps the team's and what they told Claude. Cancelling saves
|
|
1527
|
+
// none, and the next init asks again.
|
|
1528
|
+
const picked = process.stdin.isTTY && process.stderr.isTTY && !opts.yes && fromReviews.length + memory.length + fromSessions.length
|
|
1446
1529
|
? await pickLearnings([
|
|
1447
|
-
...
|
|
1448
|
-
...
|
|
1530
|
+
...fromReviews.map((f) => ({ title: f.title, forTeam: true, prs: f.prs })),
|
|
1531
|
+
...memory.map((c) => ({ title: c.title, forTeam: c.forTeam, memory: true })),
|
|
1532
|
+
...fromSessions.map((f) => ({ title: f.title, forTeam: false, sessions: f.sessions.length })),
|
|
1449
1533
|
])
|
|
1450
1534
|
: undefined;
|
|
1451
1535
|
// The picked rows that are `found`'s, which start at `from` in the list.
|
|
@@ -1453,56 +1537,70 @@ async function runInitCommand(dir, opts) {
|
|
|
1453
1537
|
? undefined
|
|
1454
1538
|
: async (found) => picked && picked.filter((i) => i >= from && i < from + found.length).map((i) => found[i - from]);
|
|
1455
1539
|
const learned = [];
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
const
|
|
1540
|
+
// Reviews first: which of them are offered depends on the learnings already saved.
|
|
1541
|
+
if (reviewRead) {
|
|
1542
|
+
const { result, saved, kept, cancelled } = await finishReviewRead(repo, reviewRead, keepFrom(0));
|
|
1543
|
+
const offered = fromReviews.length;
|
|
1459
1544
|
if (saved.length) {
|
|
1460
1545
|
const n = saved.length;
|
|
1461
1546
|
const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
|
|
1462
|
-
console.error(`✓ ${what}
|
|
1463
|
-
//
|
|
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
|
-
});
|
|
1547
|
+
console.error(`✓ ${what} reviewers made authors change, from the last ${result.prs} merged PRs here`);
|
|
1548
|
+
// saveReviewFindings keeps the findings' order.
|
|
1549
|
+
saved.forEach((note, i) => console.error(` for your team · from review on ${(kept[i]?.prs ?? []).map((n) => `#${n}`).join(", ")}: ${note.title}`));
|
|
1469
1550
|
}
|
|
1470
1551
|
else if (cancelled) {
|
|
1471
|
-
console.error(`· saved none of the ${offered} things
|
|
1552
|
+
console.error(`· saved none of the ${offered} things reviewers made authors change; ${cmd("graft init")} again asks again`);
|
|
1472
1553
|
}
|
|
1473
1554
|
else if (offered) {
|
|
1474
|
-
console.error(`· kept none of the ${offered} things
|
|
1555
|
+
console.error(`· kept none of the ${offered} things reviewers made authors change`);
|
|
1475
1556
|
}
|
|
1476
1557
|
else if (result.reason === "failed") {
|
|
1477
|
-
console.error(`· couldn't read
|
|
1558
|
+
console.error(`· couldn't read PR reviews this time; ${cmd("graft init")} again tries again`);
|
|
1478
1559
|
}
|
|
1479
1560
|
learned.push(...saved);
|
|
1480
1561
|
}
|
|
1481
|
-
if (
|
|
1482
|
-
const {
|
|
1562
|
+
if (memory.length) {
|
|
1563
|
+
const { saved, cancelled } = await finishMemoryRead(repo, memory, keepFrom(fromReviews.length));
|
|
1564
|
+
if (saved.length) {
|
|
1565
|
+
console.error(`✓ kept ${saved.length} of the ${memory.length} notes your Claude keeps about this repo`);
|
|
1566
|
+
for (const note of saved)
|
|
1567
|
+
console.error(` ${note.scope === "personal" ? "just for you " : "for your team"} · ${note.title}`);
|
|
1568
|
+
}
|
|
1569
|
+
else if (cancelled) {
|
|
1570
|
+
console.error(`· saved none of the ${memory.length} notes your Claude keeps about this repo; ${cmd("graft init")} again asks again`);
|
|
1571
|
+
}
|
|
1572
|
+
learned.push(...saved);
|
|
1573
|
+
}
|
|
1574
|
+
if (pastRead) {
|
|
1575
|
+
const { result, saved, kept, cancelled } = await finishPastRead(repo, pastRead, keepFrom(fromReviews.length + memory.length));
|
|
1483
1576
|
const offered = result.findings.length;
|
|
1484
1577
|
if (saved.length) {
|
|
1485
1578
|
const n = saved.length;
|
|
1486
1579
|
const what = n < offered ? `kept ${n} of the ${offered} things` : `learned ${n} thing${n === 1 ? "" : "s"}`;
|
|
1487
|
-
console.error(`✓ ${what}
|
|
1488
|
-
//
|
|
1489
|
-
saved.forEach((note, i) =>
|
|
1580
|
+
console.error(`✓ ${what} you told Claude more than once, from your last ${result.read} sessions here, just for you`);
|
|
1581
|
+
// Each with how many sessions it was said in: saveFindings keeps the findings' order.
|
|
1582
|
+
saved.forEach((note, i) => {
|
|
1583
|
+
const said = kept[i]?.sessions.length ?? 0;
|
|
1584
|
+
const count = said > 1 ? `told Claude in ${said} sessions: ` : "";
|
|
1585
|
+
console.error(` just for you · ${count}${note.title}`);
|
|
1586
|
+
});
|
|
1490
1587
|
}
|
|
1491
1588
|
else if (cancelled) {
|
|
1492
|
-
console.error(`· saved none of the ${offered} things
|
|
1589
|
+
console.error(`· saved none of the ${offered} things you told Claude more than once; ${cmd("graft init")} again asks again`);
|
|
1493
1590
|
}
|
|
1494
1591
|
else if (offered) {
|
|
1495
|
-
console.error(`· kept none of the ${offered} things
|
|
1592
|
+
console.error(`· kept none of the ${offered} things you told Claude more than once`);
|
|
1496
1593
|
}
|
|
1497
1594
|
else if (result.reason === "failed") {
|
|
1498
|
-
console.error(`· couldn't read
|
|
1595
|
+
console.error(`· couldn't read your past sessions this time; ${cmd("graft init")} again tries again`);
|
|
1499
1596
|
}
|
|
1500
1597
|
learned.push(...saved);
|
|
1501
1598
|
}
|
|
1502
1599
|
learnedNow = learned.length > 0;
|
|
1600
|
+
teamSaved = learned.filter((s) => s.scope === "repo").length;
|
|
1503
1601
|
if (learnedNow) {
|
|
1504
1602
|
console.error("· Claude follows them from your next session");
|
|
1505
|
-
if (
|
|
1603
|
+
if (teamSaved)
|
|
1506
1604
|
console.error(commitLines({}, teammatesOn(repo), "repo").uncommitted);
|
|
1507
1605
|
}
|
|
1508
1606
|
}
|
|
@@ -1511,7 +1609,7 @@ async function runInitCommand(dir, opts) {
|
|
|
1511
1609
|
if (home) {
|
|
1512
1610
|
const learnings = listNotes(repo, { branches: false }).filter((n) => n.scope === "repo");
|
|
1513
1611
|
if (learnings.length === 0)
|
|
1514
|
-
console.error(`✓ learnings ${LEARNINGS_DIR}/ ·
|
|
1612
|
+
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`);
|
|
1515
1613
|
else
|
|
1516
1614
|
console.error(`✓ learnings ${learnings.length} in ${LEARNINGS_DIR}/, from ${new Set(learnings.map((n) => n.author || "someone")).size} ${new Set(learnings.map((n) => n.author)).size === 1 ? "person" : "people"}`);
|
|
1517
1615
|
}
|
|
@@ -1540,7 +1638,11 @@ async function runInitCommand(dir, opts) {
|
|
|
1540
1638
|
return r.status !== 0 || r.stdout.trim().length > 0;
|
|
1541
1639
|
});
|
|
1542
1640
|
tops.splice(0, tops.length, ...changedTops);
|
|
1543
|
-
|
|
1641
|
+
// Outside git there is nothing to commit to, and `git status` failing above
|
|
1642
|
+
// made every path look changed.
|
|
1643
|
+
if (TRAIL && !children.length && !isGitWorkTree(repo))
|
|
1644
|
+
console.error(NOT_GIT_LINE);
|
|
1645
|
+
else if (tops.length)
|
|
1544
1646
|
console.error(TRAIL
|
|
1545
1647
|
? `· commit ${tops.slice(0, 5).join(" and ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} once — after that, teammates' Claude reads your learnings even before they install trail`
|
|
1546
1648
|
: `· commit ${tops.slice(0, 5).join(" ")}${tops.length > 5 ? ` +${tops.length - 5} more` : ""} to share it — graft/ stays local and git-ignored`);
|
|
@@ -1558,6 +1660,10 @@ async function runInitCommand(dir, opts) {
|
|
|
1558
1660
|
nodes: graphs.reduce((n, g) => n + g.meta.nodeCount, 0),
|
|
1559
1661
|
edges: graphs.reduce((n, g) => n + g.meta.edgeCount, 0),
|
|
1560
1662
|
}));
|
|
1663
|
+
// Run by an agent, not a person: the last line tells it to ask about the
|
|
1664
|
+
// commit, since the person never saw the picker and Claude Code folds the rest.
|
|
1665
|
+
if (teamSaved && !process.stderr.isTTY)
|
|
1666
|
+
console.error(`→ ${teamSaved} team learning${teamSaved === 1 ? "" : "s"} in ${LEARNINGS_DIR}/ ${teamSaved === 1 ? "isn't" : "aren't"} committed yet. Ask the user: "Commit ${teamSaved === 1 ? "it" : `the ${teamSaved}`} so your teammates' Claude starts from ${teamSaved === 1 ? "it" : "them"}? (yes / no)"`);
|
|
1561
1667
|
// Sorted so `claude,cursor` and `cursor,claude` aggregate as one value.
|
|
1562
1668
|
track("init_completed", { agents: [...ids].sort().join(","), consent: consent === undefined ? "unasked" : String(consent) }, { repo });
|
|
1563
1669
|
return { ids };
|