@nanonets/graft 0.16.0 → 0.19.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 +10 -3
- package/dist/app/brain-build-process.d.ts +22 -0
- package/dist/app/brain-build-process.d.ts.map +1 -0
- package/dist/app/brain-build-process.js +129 -0
- package/dist/app/brain-build-process.js.map +1 -0
- package/dist/app/brain-build-worker.d.ts +35 -0
- package/dist/app/brain-build-worker.d.ts.map +1 -0
- package/dist/app/brain-build-worker.js +56 -0
- package/dist/app/brain-build-worker.js.map +1 -0
- package/dist/app/brain-build.d.ts +160 -0
- package/dist/app/brain-build.d.ts.map +1 -0
- package/dist/app/brain-build.js +278 -0
- package/dist/app/brain-build.js.map +1 -0
- package/dist/app/checkout.d.ts +53 -3
- package/dist/app/checkout.d.ts.map +1 -1
- package/dist/app/checkout.js +195 -27
- package/dist/app/checkout.js.map +1 -1
- package/dist/app/events.d.ts +7 -0
- package/dist/app/events.d.ts.map +1 -1
- package/dist/app/events.js +7 -2
- package/dist/app/events.js.map +1 -1
- package/dist/app/history.d.ts +128 -0
- package/dist/app/history.d.ts.map +1 -0
- package/dist/app/history.js +266 -0
- package/dist/app/history.js.map +1 -0
- package/dist/app/identity.d.ts +56 -0
- package/dist/app/identity.d.ts.map +1 -1
- package/dist/app/identity.js +119 -0
- package/dist/app/identity.js.map +1 -1
- package/dist/app/main.js +14 -0
- package/dist/app/main.js.map +1 -1
- package/dist/app/pages.d.ts +17 -0
- package/dist/app/pages.d.ts.map +1 -1
- package/dist/app/pages.js +112 -5
- package/dist/app/pages.js.map +1 -1
- package/dist/app/review-process.d.ts +66 -0
- package/dist/app/review-process.d.ts.map +1 -0
- package/dist/app/review-process.js +199 -0
- package/dist/app/review-process.js.map +1 -0
- package/dist/app/review-worker.d.ts +2 -0
- package/dist/app/review-worker.d.ts.map +1 -0
- package/dist/app/review-worker.js +108 -0
- package/dist/app/review-worker.js.map +1 -0
- package/dist/app/review.d.ts.map +1 -1
- package/dist/app/review.js +4 -1
- package/dist/app/review.js.map +1 -1
- package/dist/app/server.d.ts +31 -2
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +130 -3
- package/dist/app/server.js.map +1 -1
- package/dist/app/sources.d.ts +68 -0
- package/dist/app/sources.d.ts.map +1 -0
- package/dist/app/sources.js +342 -0
- package/dist/app/sources.js.map +1 -0
- package/dist/ask/ask.d.ts +6 -0
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +30 -2
- package/dist/ask/ask.js.map +1 -1
- package/dist/brain/attach.d.ts +44 -0
- package/dist/brain/attach.d.ts.map +1 -0
- package/dist/brain/attach.js +63 -0
- package/dist/brain/attach.js.map +1 -0
- package/dist/brain/connect.d.ts +59 -0
- package/dist/brain/connect.d.ts.map +1 -0
- package/dist/brain/connect.js +80 -0
- package/dist/brain/connect.js.map +1 -0
- package/dist/brain/link.d.ts +89 -0
- package/dist/brain/link.d.ts.map +1 -0
- package/dist/brain/link.js +146 -0
- package/dist/brain/link.js.map +1 -0
- package/dist/brain/push.d.ts +72 -0
- package/dist/brain/push.d.ts.map +1 -0
- package/dist/brain/push.js +191 -0
- package/dist/brain/push.js.map +1 -0
- package/dist/brain/signup.d.ts +73 -0
- package/dist/brain/signup.d.ts.map +1 -0
- package/dist/brain/signup.js +186 -0
- package/dist/brain/signup.js.map +1 -0
- package/dist/brain/watch.d.ts +136 -0
- package/dist/brain/watch.d.ts.map +1 -0
- package/dist/brain/watch.js +172 -0
- package/dist/brain/watch.js.map +1 -0
- package/dist/brain/wire.d.ts +38 -0
- package/dist/brain/wire.d.ts.map +1 -0
- package/dist/brain/wire.js +133 -0
- package/dist/brain/wire.js.map +1 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +10 -3
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +32 -1
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts +5 -0
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +9 -1
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/session-metrics.d.ts +10 -0
- package/dist/claude/session-metrics.d.ts.map +1 -1
- package/dist/claude/session-metrics.js +25 -0
- package/dist/claude/session-metrics.js.map +1 -1
- package/dist/claude/settings-merge.d.ts +18 -0
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +42 -8
- package/dist/claude/settings-merge.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +6 -0
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/state.d.ts +14 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/claude/tally.d.ts +27 -0
- package/dist/claude/tally.d.ts.map +1 -1
- package/dist/claude/tally.js +74 -1
- package/dist/claude/tally.js.map +1 -1
- package/dist/cli.js +310 -3
- package/dist/cli.js.map +1 -1
- package/dist/context/node-file.d.ts +4 -10
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +6 -2
- package/dist/context/node-file.js.map +1 -1
- package/dist/context/price.d.ts +49 -0
- package/dist/context/price.d.ts.map +1 -0
- package/dist/context/price.js +83 -0
- package/dist/context/price.js.map +1 -0
- package/dist/context/savings.d.ts +13 -3
- package/dist/context/savings.d.ts.map +1 -1
- package/dist/context/savings.js +44 -5
- package/dist/context/savings.js.map +1 -1
- package/dist/hosts/claude-global.d.ts +33 -0
- package/dist/hosts/claude-global.d.ts.map +1 -0
- package/dist/hosts/claude-global.js +118 -0
- package/dist/hosts/claude-global.js.map +1 -0
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +5 -1
- package/dist/hosts/plan.js.map +1 -1
- package/dist/hosts/retract.d.ts.map +1 -1
- package/dist/hosts/retract.js +33 -13
- package/dist/hosts/retract.js.map +1 -1
- package/dist/hosts/sections.d.ts +23 -2
- package/dist/hosts/sections.d.ts.map +1 -1
- package/dist/hosts/sections.js +25 -9
- package/dist/hosts/sections.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +33 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +5 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/telemetry/contract.d.ts +12 -0
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +19 -0
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/telemetry/gate.d.ts +14 -0
- package/dist/telemetry/gate.d.ts.map +1 -1
- package/dist/telemetry/gate.js +18 -2
- package/dist/telemetry/gate.js.map +1 -1
- package/dist/upkeep-run.d.ts.map +1 -1
- package/dist/upkeep-run.js +12 -2
- package/dist/upkeep-run.js.map +1 -1
- package/dist/upkeep.d.ts +18 -0
- package/dist/upkeep.d.ts.map +1 -1
- package/dist/upkeep.js +38 -0
- package/dist/upkeep.js.map +1 -1
- package/dist/util/state.d.ts +9 -0
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The link between a repo's graft graph and a Trail brain.
|
|
3
|
+
*
|
|
4
|
+
* A brain holds the rules mined out of this repository's history — what the
|
|
5
|
+
* team decided and why, which the code graph cannot see. Graft holds the code
|
|
6
|
+
* and can say which symbols an answer touches. This module is the join: the
|
|
7
|
+
* token identifying the brain, and the rules cached beside it so `ask` never
|
|
8
|
+
* waits on the network.
|
|
9
|
+
*
|
|
10
|
+
* The link lives in the per-repo, git-ignored `.graft/config.json`, next to the
|
|
11
|
+
* other persisted build choices. Not in `~/.graft/`: a brain belongs to one
|
|
12
|
+
* repository, and two checkouts of different repos on one machine must not
|
|
13
|
+
* share one.
|
|
14
|
+
*/
|
|
15
|
+
import { readBuildConfig, patchBuildConfig, cacheDir, readJson, writeJsonAtomic } from '../util/state.js';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { ensureGitignored, LINK_NOTE } from '../context/node-file.js';
|
|
18
|
+
import { BUILD_CONFIG_DIR } from '../util/state.js';
|
|
19
|
+
/** Default API host. Overridden by GRAFT_BRAIN_URL, for staging and self-hosted. */
|
|
20
|
+
const DEFAULT_BRAIN_BASE_URL = 'https://agents.nanonets.com';
|
|
21
|
+
/** How long a cached rule set is served before upkeep refreshes it. */
|
|
22
|
+
export const RULES_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
23
|
+
/**
|
|
24
|
+
* The TTL used while the cache holds NO rules.
|
|
25
|
+
*
|
|
26
|
+
* A brain is connected during onboarding while it is still being mined, so the
|
|
27
|
+
* first pull legitimately returns nothing. Six hours of that is the difference
|
|
28
|
+
* between the feature working and the user concluding it does not: they wired
|
|
29
|
+
* graft up, got an empty rulebook, and nothing would go back for the real one
|
|
30
|
+
* until tomorrow. Two minutes costs one cheap request per session until the
|
|
31
|
+
* rules land, and then the normal TTL takes over.
|
|
32
|
+
*/
|
|
33
|
+
export const EMPTY_RULES_TTL_MS = 2 * 60 * 1000;
|
|
34
|
+
/**
|
|
35
|
+
* The link for repo `dir`, or null when it has none.
|
|
36
|
+
*
|
|
37
|
+
* `GRAFT_BRAIN_TOKEN` / `GRAFT_BRAIN_ID` override the stored pair, so CI can
|
|
38
|
+
* attach a brain without writing to the checkout.
|
|
39
|
+
*/
|
|
40
|
+
export function readLink(dir) {
|
|
41
|
+
const envToken = process.env.GRAFT_BRAIN_TOKEN;
|
|
42
|
+
const envBrain = process.env.GRAFT_BRAIN_ID;
|
|
43
|
+
if (envToken && envBrain) {
|
|
44
|
+
return { brainId: envBrain, token: envToken, baseUrl: process.env.GRAFT_BRAIN_URL };
|
|
45
|
+
}
|
|
46
|
+
const stored = readBuildConfig(dir)?.brain;
|
|
47
|
+
if (!stored?.brainId || !stored?.token)
|
|
48
|
+
return null;
|
|
49
|
+
return stored;
|
|
50
|
+
}
|
|
51
|
+
/** Persist the link for repo `dir`, merging into any existing build config.
|
|
52
|
+
*
|
|
53
|
+
* The write is what creates `.graft/config.json`, and that file holds the read
|
|
54
|
+
* token — which is exactly why this is also where it gets ignored. The comment
|
|
55
|
+
* on {@link BrainLink} has claimed `.graft/` was "git-ignored" since the day it
|
|
56
|
+
* was written, and nothing ever made it true: `ensureGitignored` only ever ran
|
|
57
|
+
* for the graph cache. Every repository anyone ran `graft brain connect` in was
|
|
58
|
+
* therefore one `git add -A` away from publishing a credential. */
|
|
59
|
+
export function writeLink(dir, link) {
|
|
60
|
+
patchBuildConfig(dir, { brain: link });
|
|
61
|
+
ensureGitignored(dir, join(dir, BUILD_CONFIG_DIR), LINK_NOTE);
|
|
62
|
+
}
|
|
63
|
+
/** Forget the link for repo `dir`. Leaves the cached rules for `uninstall` to remove. */
|
|
64
|
+
export function clearLink(dir) {
|
|
65
|
+
patchBuildConfig(dir, { brain: undefined });
|
|
66
|
+
}
|
|
67
|
+
/** Where the rule cache lives: beside the graph, since it is derived data. */
|
|
68
|
+
export function rulesCachePath(dir) {
|
|
69
|
+
return join(cacheDir(dir), 'brain-rules.json');
|
|
70
|
+
}
|
|
71
|
+
export function readRulesCache(dir) {
|
|
72
|
+
return readJson(rulesCachePath(dir));
|
|
73
|
+
}
|
|
74
|
+
export function writeRulesCache(dir, cache) {
|
|
75
|
+
writeJsonAtomic(rulesCachePath(dir), cache, true);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether a cached set is old enough to refetch.
|
|
79
|
+
*
|
|
80
|
+
* Keyed off the last ATTEMPT, not the last success, and with a much shorter
|
|
81
|
+
* window while the cache is empty — see EMPTY_RULES_TTL_MS for why that case
|
|
82
|
+
* is the one that matters.
|
|
83
|
+
*/
|
|
84
|
+
export function cacheIsStale(cache, now = Date.now()) {
|
|
85
|
+
if (!cache)
|
|
86
|
+
return true;
|
|
87
|
+
const ttl = cache.rules.length === 0 ? EMPTY_RULES_TTL_MS : RULES_TTL_MS;
|
|
88
|
+
return now - (cache.checkedAt ?? cache.fetchedAt) > ttl;
|
|
89
|
+
}
|
|
90
|
+
/** Record that a refresh was attempted, without claiming rules arrived. */
|
|
91
|
+
export function markRulesChecked(dir, now = Date.now()) {
|
|
92
|
+
const cache = readRulesCache(dir);
|
|
93
|
+
if (!cache)
|
|
94
|
+
return;
|
|
95
|
+
writeRulesCache(dir, { ...cache, checkedAt: now });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The base URL for a link, honouring the env override first so a misconfigured
|
|
99
|
+
* stored value can always be corrected without editing a file.
|
|
100
|
+
*/
|
|
101
|
+
export function baseUrlFor(link) {
|
|
102
|
+
return (process.env.GRAFT_BRAIN_URL || link.baseUrl || DEFAULT_BRAIN_BASE_URL).replace(/\/+$/, '');
|
|
103
|
+
}
|
|
104
|
+
/** Timeout for one rules fetch. Short: `ask` must never block on this. */
|
|
105
|
+
const FETCH_TIMEOUT_MS = 5000;
|
|
106
|
+
/**
|
|
107
|
+
* Fetch the brain's symbol-anchored rules.
|
|
108
|
+
*
|
|
109
|
+
* Returns null on any failure — an unreachable brain, a revoked token, a
|
|
110
|
+
* malformed body. Never throws: `ask` degrades to the code-only answer it gave
|
|
111
|
+
* before a brain was attached, which is the whole reason the cache exists.
|
|
112
|
+
*/
|
|
113
|
+
export async function fetchRules(link, fetchImpl = fetch) {
|
|
114
|
+
// The PUBLIC, token-authenticated route: graft runs on a laptop with no
|
|
115
|
+
// session, and the token it holds is scoped to this one brain rather than the
|
|
116
|
+
// workspace. The protected route is for the app itself.
|
|
117
|
+
const url = `${baseUrlFor(link)}/api/public/brains/${encodeURIComponent(link.brainId)}/rules/anchors`;
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetchImpl(url, {
|
|
120
|
+
headers: { authorization: `Bearer ${link.token}`, accept: 'application/json' },
|
|
121
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok)
|
|
124
|
+
return null;
|
|
125
|
+
const body = (await res.json());
|
|
126
|
+
if (!Array.isArray(body.anchors))
|
|
127
|
+
return null;
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const a of body.anchors) {
|
|
130
|
+
if (!a?.symbol || !a?.rule)
|
|
131
|
+
continue;
|
|
132
|
+
out.push({
|
|
133
|
+
ruleId: String(a.rule_id ?? ''),
|
|
134
|
+
symbol: a.symbol,
|
|
135
|
+
fingerprint: String(a.fingerprint ?? ''),
|
|
136
|
+
rule: a.rule,
|
|
137
|
+
sourceUrl: a.source_url || undefined,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/brain/link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type RepoDigest } from "../app/history.js";
|
|
2
|
+
import type { GraphV1 } from "../graph/types.js";
|
|
3
|
+
import { type BrainLink } from "./link.js";
|
|
4
|
+
/** What a local ingest read, for the caller to print. */
|
|
5
|
+
export interface PushResult {
|
|
6
|
+
jobId: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
commits: number;
|
|
9
|
+
threads: number;
|
|
10
|
+
symbols: number;
|
|
11
|
+
sources: number;
|
|
12
|
+
/** Set when discussion could not be read; the ingest still went ahead. */
|
|
13
|
+
warning?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* owner/name for this checkout, from its origin remote.
|
|
17
|
+
*
|
|
18
|
+
* Only GitHub, because the discussion reader and the forge links are
|
|
19
|
+
* GitHub-shaped; another forge would need its own reader, not a looser regex
|
|
20
|
+
* here that produces links to nowhere.
|
|
21
|
+
*/
|
|
22
|
+
export declare function repoSlugFromGit(root: string): {
|
|
23
|
+
owner: string;
|
|
24
|
+
name: string;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* A GitHub token from wherever the user already keeps one.
|
|
28
|
+
*
|
|
29
|
+
* `gh auth token` first, because anyone who works with private repositories on
|
|
30
|
+
* the command line has it and it needs no setup at all. The env vars are the
|
|
31
|
+
* CI path. Absent, the ingest still runs on commits alone — pull-request
|
|
32
|
+
* discussion is the richest source, not a required one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function githubToken(): string | null;
|
|
35
|
+
/** What the brain is waiting for, when it is waiting for something. */
|
|
36
|
+
export interface ExpectedRepo {
|
|
37
|
+
slug: string;
|
|
38
|
+
status: string;
|
|
39
|
+
brainName: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The repository this brain expects, or null when it expects none.
|
|
43
|
+
*
|
|
44
|
+
* The website records it when the user chooses the local route, so this is how
|
|
45
|
+
* the CLI knows whether the directory it is standing in is the one they asked
|
|
46
|
+
* for. Mining the wrong repo into a brain is silent and very hard to notice
|
|
47
|
+
* afterwards — the rules simply describe someone else's codebase — so the round
|
|
48
|
+
* trip is worth it.
|
|
49
|
+
*
|
|
50
|
+
* Null on any failure, which keeps an older brain (or an unreachable one)
|
|
51
|
+
* working exactly as it did before this check existed.
|
|
52
|
+
*/
|
|
53
|
+
export declare function fetchExpectedRepo(link: BrainLink, fetchImpl?: typeof fetch): Promise<ExpectedRepo | null>;
|
|
54
|
+
/** Whether two repo slugs name the same repository. GitHub is case-insensitive. */
|
|
55
|
+
export declare function sameRepo(a: string, b: string): boolean;
|
|
56
|
+
/** Build the digest for the checkout at `root`. Reads nothing but text. */
|
|
57
|
+
export declare function buildLocalDigest(root: string, graph: GraphV1 | null, opts?: {
|
|
58
|
+
autoApprove?: boolean;
|
|
59
|
+
fetchImpl?: typeof fetch;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
digest: RepoDigest;
|
|
62
|
+
warning?: string;
|
|
63
|
+
} | {
|
|
64
|
+
error: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** Send a digest to the brain and return the job to poll. */
|
|
67
|
+
export declare function pushDigest(link: BrainLink, digest: RepoDigest, fetchImpl?: typeof fetch): Promise<{
|
|
68
|
+
jobId: string;
|
|
69
|
+
} | {
|
|
70
|
+
error: string;
|
|
71
|
+
}>;
|
|
72
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/brain/push.ts"],"names":[],"mappings":"AAiBA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAY3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAEvD,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMpF;AAQD;;;;;;;GAOG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAM3C;AAgBD,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAatH;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,2EAA2E;AAC3E,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAC7D,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoDvE;AAED,6DAA6D;AAC7D,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,UAAU,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBhD"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building a brain from the repository already on this machine.
|
|
3
|
+
*
|
|
4
|
+
* The server path needs graft's GitHub App installed on the repo, which is an
|
|
5
|
+
* admin action on someone else's org and the first thing asked of a person who
|
|
6
|
+
* has not seen a single rule yet. For a private repository it is often simply
|
|
7
|
+
* unavailable.
|
|
8
|
+
*
|
|
9
|
+
* This is the way round that: the clone is already here, and the user is
|
|
10
|
+
* already authenticated to GitHub for their own work. So read it locally and
|
|
11
|
+
* send up the same digest the server would have built.
|
|
12
|
+
*
|
|
13
|
+
* What leaves the machine is what people wrote — commit messages, pull-request
|
|
14
|
+
* comments, the docs and config already in the tree — plus symbol ids and
|
|
15
|
+
* hashes. No file contents, no diffs, no source.
|
|
16
|
+
*/
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { buildDigest, readCommits, readSymbols, readThreads, } from "../app/history.js";
|
|
19
|
+
import { budgetSources, readAgentInstructions, readBranchProtection, readCodeowners, readCodifiedRules, readDecisionDocs, readDeclinedIssues, readReverts, readTestNames, } from "../app/sources.js";
|
|
20
|
+
import { baseUrlFor } from "./link.js";
|
|
21
|
+
function git(root, args) {
|
|
22
|
+
const res = spawnSync("git", ["-c", "core.quotePath=false", ...args], {
|
|
23
|
+
cwd: root,
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
});
|
|
27
|
+
if (res.error || res.status !== 0 || typeof res.stdout !== "string")
|
|
28
|
+
return null;
|
|
29
|
+
return res.stdout.trim();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* owner/name for this checkout, from its origin remote.
|
|
33
|
+
*
|
|
34
|
+
* Only GitHub, because the discussion reader and the forge links are
|
|
35
|
+
* GitHub-shaped; another forge would need its own reader, not a looser regex
|
|
36
|
+
* here that produces links to nowhere.
|
|
37
|
+
*/
|
|
38
|
+
export function repoSlugFromGit(root) {
|
|
39
|
+
const url = git(root, ["remote", "get-url", "origin"]);
|
|
40
|
+
if (!url)
|
|
41
|
+
return null;
|
|
42
|
+
const m = url.match(/github\.com[:/]+([^/]+)\/(.+?)(?:\.git)?\/?$/i);
|
|
43
|
+
if (!m)
|
|
44
|
+
return null;
|
|
45
|
+
return { owner: m[1], name: m[2] };
|
|
46
|
+
}
|
|
47
|
+
/** The branch to attribute the ingest to. */
|
|
48
|
+
function currentBranch(root) {
|
|
49
|
+
const head = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
50
|
+
return head && head !== "HEAD" ? head : "";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A GitHub token from wherever the user already keeps one.
|
|
54
|
+
*
|
|
55
|
+
* `gh auth token` first, because anyone who works with private repositories on
|
|
56
|
+
* the command line has it and it needs no setup at all. The env vars are the
|
|
57
|
+
* CI path. Absent, the ingest still runs on commits alone — pull-request
|
|
58
|
+
* discussion is the richest source, not a required one.
|
|
59
|
+
*/
|
|
60
|
+
export function githubToken() {
|
|
61
|
+
const env = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
62
|
+
if (env)
|
|
63
|
+
return env;
|
|
64
|
+
const res = spawnSync("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
65
|
+
const out = typeof res.stdout === "string" ? res.stdout.trim() : "";
|
|
66
|
+
return res.status === 0 && out ? out : null;
|
|
67
|
+
}
|
|
68
|
+
/** Whether the repository is private, so the digest records it honestly. */
|
|
69
|
+
async function isPrivate(owner, name, token, fetchImpl) {
|
|
70
|
+
if (!token)
|
|
71
|
+
return true; // unknown counts as private
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetchImpl(`https://api.github.com/repos/${owner}/${name}`, {
|
|
74
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json" },
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
return true;
|
|
78
|
+
return (await res.json()).private !== false;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The repository this brain expects, or null when it expects none.
|
|
86
|
+
*
|
|
87
|
+
* The website records it when the user chooses the local route, so this is how
|
|
88
|
+
* the CLI knows whether the directory it is standing in is the one they asked
|
|
89
|
+
* for. Mining the wrong repo into a brain is silent and very hard to notice
|
|
90
|
+
* afterwards — the rules simply describe someone else's codebase — so the round
|
|
91
|
+
* trip is worth it.
|
|
92
|
+
*
|
|
93
|
+
* Null on any failure, which keeps an older brain (or an unreachable one)
|
|
94
|
+
* working exactly as it did before this check existed.
|
|
95
|
+
*/
|
|
96
|
+
export async function fetchExpectedRepo(link, fetchImpl = fetch) {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetchImpl(`${baseUrlFor(link)}/api/public/brains/${encodeURIComponent(link.brainId)}/repo`, {
|
|
99
|
+
headers: { authorization: `Bearer ${link.token}`, accept: "application/json" },
|
|
100
|
+
signal: AbortSignal.timeout(5000),
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok)
|
|
103
|
+
return null;
|
|
104
|
+
const body = (await res.json());
|
|
105
|
+
if (!body.repo?.slug)
|
|
106
|
+
return null;
|
|
107
|
+
return { slug: body.repo.slug, status: String(body.repo.status ?? ""), brainName: String(body.brain_name ?? "") };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Whether two repo slugs name the same repository. GitHub is case-insensitive. */
|
|
114
|
+
export function sameRepo(a, b) {
|
|
115
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
/** Build the digest for the checkout at `root`. Reads nothing but text. */
|
|
118
|
+
export async function buildLocalDigest(root, graph, opts = {}) {
|
|
119
|
+
const slug = repoSlugFromGit(root);
|
|
120
|
+
if (!slug) {
|
|
121
|
+
return { error: "this directory has no GitHub `origin` remote — graft can only push a GitHub repository today" };
|
|
122
|
+
}
|
|
123
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
124
|
+
const token = githubToken();
|
|
125
|
+
const commits = readCommits(root);
|
|
126
|
+
if (commits.length === 0) {
|
|
127
|
+
return { error: "no commits found here — is this a shallow clone with no history?" };
|
|
128
|
+
}
|
|
129
|
+
const symbols = readSymbols(graph);
|
|
130
|
+
let threads = [];
|
|
131
|
+
let warning;
|
|
132
|
+
if (token) {
|
|
133
|
+
try {
|
|
134
|
+
threads = await readThreads(slug.owner, slug.name, token, fetchImpl);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
warning = "could not read pull-request discussion; mining commits and repo files only";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
warning =
|
|
142
|
+
"no GitHub token found (`gh auth login`, or GH_TOKEN) — mining commits and repo files only, without pull-request discussion";
|
|
143
|
+
}
|
|
144
|
+
const branch = currentBranch(root);
|
|
145
|
+
const sources = budgetSources([
|
|
146
|
+
...readAgentInstructions(root),
|
|
147
|
+
...readDecisionDocs(root),
|
|
148
|
+
...readCodeowners(root),
|
|
149
|
+
...readCodifiedRules(root),
|
|
150
|
+
...readReverts(root),
|
|
151
|
+
...readTestNames(symbols.map((s) => ({ name: s.name, path: s.path }))),
|
|
152
|
+
...(token ? await readBranchProtection(slug.owner, slug.name, branch, token, fetchImpl) : []),
|
|
153
|
+
...(token ? await readDeclinedIssues(slug.owner, slug.name, token, fetchImpl) : []),
|
|
154
|
+
]);
|
|
155
|
+
const digest = buildDigest({
|
|
156
|
+
owner: slug.owner,
|
|
157
|
+
name: slug.name,
|
|
158
|
+
headSha: git(root, ["rev-parse", "HEAD"]) ?? "",
|
|
159
|
+
defaultBranch: branch,
|
|
160
|
+
isPrivate: await isPrivate(slug.owner, slug.name, token, fetchImpl),
|
|
161
|
+
commits,
|
|
162
|
+
threads,
|
|
163
|
+
symbols,
|
|
164
|
+
sources,
|
|
165
|
+
autoApprove: opts.autoApprove ?? true,
|
|
166
|
+
});
|
|
167
|
+
return { digest, warning };
|
|
168
|
+
}
|
|
169
|
+
/** Send a digest to the brain and return the job to poll. */
|
|
170
|
+
export async function pushDigest(link, digest, fetchImpl = fetch) {
|
|
171
|
+
const url = `${baseUrlFor(link)}/api/public/brains/${encodeURIComponent(link.brainId)}/repo`;
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetchImpl(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
authorization: `Bearer ${link.token}`,
|
|
177
|
+
"content-type": "application/json",
|
|
178
|
+
accept: "application/json",
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify(digest),
|
|
181
|
+
});
|
|
182
|
+
const text = await res.text();
|
|
183
|
+
if (!res.ok)
|
|
184
|
+
return { error: `the brain refused the ingest: ${res.status} ${text.slice(0, 200)}` };
|
|
185
|
+
return { jobId: String(JSON.parse(text).job_id ?? "") };
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
return { error: `could not reach the brain: ${e instanceof Error ? e.message : e}` };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/brain/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { BrainLink } from "./link.js";
|
|
2
|
+
/** How long the listener waits for the browser before giving up. A signup can
|
|
3
|
+
* involve reading an email, so this is minutes rather than seconds. */
|
|
4
|
+
export declare const HANDOFF_TIMEOUT_MS: number;
|
|
5
|
+
/** The one path the listener answers on. */
|
|
6
|
+
export declare const CALLBACK_PATH = "/graft/callback";
|
|
7
|
+
/**
|
|
8
|
+
* What the browser sends back, or why it could not be accepted.
|
|
9
|
+
*
|
|
10
|
+
* `error` is the sentence printed to the user and `reason` is the same fact as a
|
|
11
|
+
* category. They are separate because the sentence names the repo and the link,
|
|
12
|
+
* so it can never be the thing telemetry reports — and matching on its text to
|
|
13
|
+
* recover the category would put a user-facing string in a position where
|
|
14
|
+
* rewording it silently changes what gets counted.
|
|
15
|
+
*/
|
|
16
|
+
export type HandoffFailure = "timed_out" | "bad_callback";
|
|
17
|
+
export type HandoffResult = {
|
|
18
|
+
link: BrainLink;
|
|
19
|
+
} | {
|
|
20
|
+
error: string;
|
|
21
|
+
reason: HandoffFailure;
|
|
22
|
+
};
|
|
23
|
+
export interface Handoff {
|
|
24
|
+
/** The loopback port Trail must redirect to. */
|
|
25
|
+
port: number;
|
|
26
|
+
/** The nonce Trail must echo back. */
|
|
27
|
+
state: string;
|
|
28
|
+
/** Resolves once the browser answers, or when `timeoutMs` passes. */
|
|
29
|
+
wait(timeoutMs?: number): Promise<HandoffResult>;
|
|
30
|
+
/** Stop listening. Safe to call more than once. */
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start listening for the browser's handoff.
|
|
35
|
+
*
|
|
36
|
+
* Port 0 so the OS picks a free one: a fixed port would collide with whatever
|
|
37
|
+
* else the developer is running, and a second graft in another terminal.
|
|
38
|
+
*/
|
|
39
|
+
export declare function startHandoff(): Promise<Handoff>;
|
|
40
|
+
/** The Trail front end this machine is pointed at, without a trailing slash.
|
|
41
|
+
*
|
|
42
|
+
* Read at the moment it is needed rather than captured once, matching the rest
|
|
43
|
+
* of the brain code: GRAFT_BRAIN_URL is free to move between calls. */
|
|
44
|
+
export declare function webBaseUrl(baseUrl?: string): string;
|
|
45
|
+
/** Where the browser is sent once the handoff has been accepted.
|
|
46
|
+
*
|
|
47
|
+
* Back into Trail, at the screen that shows the brain being built. The
|
|
48
|
+
* alternative — leaving the person on a local page that says "go back to your
|
|
49
|
+
* terminal" — ends the flow on a blank throwaway served by a port that is about
|
|
50
|
+
* to close, at exactly the moment there is something to watch.
|
|
51
|
+
*
|
|
52
|
+
* NOT `/brain/<id>`, which is where this pointed first. That route redirects to
|
|
53
|
+
* the brain's graph, and the redirect fires the instant the row exists — which
|
|
54
|
+
* is minutes before it has any rules in it. Every terminal signup therefore
|
|
55
|
+
* landed on an empty visualisation of a brain that was, at that moment, being
|
|
56
|
+
* built perfectly well. The build screen is the same wait the browser-first
|
|
57
|
+
* flow shows, and it leads to the graph once there is a graph. */
|
|
58
|
+
export declare function brainUrl(brainId: string, baseUrl?: string): string;
|
|
59
|
+
/** Where to send the browser for a repo's brain. */
|
|
60
|
+
export declare function signupUrl(opts: {
|
|
61
|
+
repo: string;
|
|
62
|
+
port: number;
|
|
63
|
+
state: string;
|
|
64
|
+
baseUrl?: string;
|
|
65
|
+
}): string;
|
|
66
|
+
/** Open the user's browser, best effort. A machine with no opener is not an
|
|
67
|
+
* error: the URL is printed too, and that is the fallback everywhere.
|
|
68
|
+
*
|
|
69
|
+
* GRAFT_NO_BROWSER leaves the printed URL as the only route, which is what a
|
|
70
|
+
* test wants, and what someone on a machine whose `open` does something
|
|
71
|
+
* surprising wants too. */
|
|
72
|
+
export declare function openBrowser(url: string): void;
|
|
73
|
+
//# sourceMappingURL=signup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../src/brain/signup.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAU3C;uEACuE;AACvE,eAAO,MAAM,kBAAkB,QAAgB,CAAC;AAEhD,4CAA4C;AAC5C,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,cAAc,CAAC;AAC1D,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,CAAC;AAE5F,MAAM,WAAW,OAAO;IACtB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACjD,mDAAmD;IACnD,KAAK,IAAI,IAAI,CAAC;CACf;AAoBD;;;;;GAKG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAkFrD;AAED;;;uEAGuE;AACvE,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;;kEAYkE;AAClE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,oDAAoD;AACpD,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CASvG;AAED;;;;;2BAK2B;AAC3B,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ7C"}
|