@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.
Files changed (167) hide show
  1. package/README.md +10 -3
  2. package/dist/app/brain-build-process.d.ts +22 -0
  3. package/dist/app/brain-build-process.d.ts.map +1 -0
  4. package/dist/app/brain-build-process.js +129 -0
  5. package/dist/app/brain-build-process.js.map +1 -0
  6. package/dist/app/brain-build-worker.d.ts +35 -0
  7. package/dist/app/brain-build-worker.d.ts.map +1 -0
  8. package/dist/app/brain-build-worker.js +56 -0
  9. package/dist/app/brain-build-worker.js.map +1 -0
  10. package/dist/app/brain-build.d.ts +160 -0
  11. package/dist/app/brain-build.d.ts.map +1 -0
  12. package/dist/app/brain-build.js +278 -0
  13. package/dist/app/brain-build.js.map +1 -0
  14. package/dist/app/checkout.d.ts +53 -3
  15. package/dist/app/checkout.d.ts.map +1 -1
  16. package/dist/app/checkout.js +195 -27
  17. package/dist/app/checkout.js.map +1 -1
  18. package/dist/app/events.d.ts +7 -0
  19. package/dist/app/events.d.ts.map +1 -1
  20. package/dist/app/events.js +7 -2
  21. package/dist/app/events.js.map +1 -1
  22. package/dist/app/history.d.ts +128 -0
  23. package/dist/app/history.d.ts.map +1 -0
  24. package/dist/app/history.js +266 -0
  25. package/dist/app/history.js.map +1 -0
  26. package/dist/app/identity.d.ts +56 -0
  27. package/dist/app/identity.d.ts.map +1 -1
  28. package/dist/app/identity.js +119 -0
  29. package/dist/app/identity.js.map +1 -1
  30. package/dist/app/main.js +14 -0
  31. package/dist/app/main.js.map +1 -1
  32. package/dist/app/pages.d.ts +17 -0
  33. package/dist/app/pages.d.ts.map +1 -1
  34. package/dist/app/pages.js +112 -5
  35. package/dist/app/pages.js.map +1 -1
  36. package/dist/app/review-process.d.ts +66 -0
  37. package/dist/app/review-process.d.ts.map +1 -0
  38. package/dist/app/review-process.js +199 -0
  39. package/dist/app/review-process.js.map +1 -0
  40. package/dist/app/review-worker.d.ts +2 -0
  41. package/dist/app/review-worker.d.ts.map +1 -0
  42. package/dist/app/review-worker.js +108 -0
  43. package/dist/app/review-worker.js.map +1 -0
  44. package/dist/app/review.d.ts.map +1 -1
  45. package/dist/app/review.js +4 -1
  46. package/dist/app/review.js.map +1 -1
  47. package/dist/app/server.d.ts +31 -2
  48. package/dist/app/server.d.ts.map +1 -1
  49. package/dist/app/server.js +130 -3
  50. package/dist/app/server.js.map +1 -1
  51. package/dist/app/sources.d.ts +68 -0
  52. package/dist/app/sources.d.ts.map +1 -0
  53. package/dist/app/sources.js +342 -0
  54. package/dist/app/sources.js.map +1 -0
  55. package/dist/ask/ask.d.ts +6 -0
  56. package/dist/ask/ask.d.ts.map +1 -1
  57. package/dist/ask/ask.js +30 -2
  58. package/dist/ask/ask.js.map +1 -1
  59. package/dist/brain/attach.d.ts +44 -0
  60. package/dist/brain/attach.d.ts.map +1 -0
  61. package/dist/brain/attach.js +63 -0
  62. package/dist/brain/attach.js.map +1 -0
  63. package/dist/brain/connect.d.ts +59 -0
  64. package/dist/brain/connect.d.ts.map +1 -0
  65. package/dist/brain/connect.js +80 -0
  66. package/dist/brain/connect.js.map +1 -0
  67. package/dist/brain/link.d.ts +89 -0
  68. package/dist/brain/link.d.ts.map +1 -0
  69. package/dist/brain/link.js +146 -0
  70. package/dist/brain/link.js.map +1 -0
  71. package/dist/brain/push.d.ts +72 -0
  72. package/dist/brain/push.d.ts.map +1 -0
  73. package/dist/brain/push.js +191 -0
  74. package/dist/brain/push.js.map +1 -0
  75. package/dist/brain/signup.d.ts +73 -0
  76. package/dist/brain/signup.d.ts.map +1 -0
  77. package/dist/brain/signup.js +186 -0
  78. package/dist/brain/signup.js.map +1 -0
  79. package/dist/brain/watch.d.ts +136 -0
  80. package/dist/brain/watch.d.ts.map +1 -0
  81. package/dist/brain/watch.js +172 -0
  82. package/dist/brain/watch.js.map +1 -0
  83. package/dist/brain/wire.d.ts +38 -0
  84. package/dist/brain/wire.d.ts.map +1 -0
  85. package/dist/brain/wire.js +133 -0
  86. package/dist/brain/wire.js.map +1 -0
  87. package/dist/claude/format.d.ts.map +1 -1
  88. package/dist/claude/format.js +10 -3
  89. package/dist/claude/format.js.map +1 -1
  90. package/dist/claude/hooks.d.ts.map +1 -1
  91. package/dist/claude/hooks.js +32 -1
  92. package/dist/claude/hooks.js.map +1 -1
  93. package/dist/claude/init.d.ts +5 -0
  94. package/dist/claude/init.d.ts.map +1 -1
  95. package/dist/claude/init.js +9 -1
  96. package/dist/claude/init.js.map +1 -1
  97. package/dist/claude/session-metrics.d.ts +10 -0
  98. package/dist/claude/session-metrics.d.ts.map +1 -1
  99. package/dist/claude/session-metrics.js +25 -0
  100. package/dist/claude/session-metrics.js.map +1 -1
  101. package/dist/claude/settings-merge.d.ts +18 -0
  102. package/dist/claude/settings-merge.d.ts.map +1 -1
  103. package/dist/claude/settings-merge.js +42 -8
  104. package/dist/claude/settings-merge.js.map +1 -1
  105. package/dist/claude/skill-template.d.ts.map +1 -1
  106. package/dist/claude/skill-template.js +6 -0
  107. package/dist/claude/skill-template.js.map +1 -1
  108. package/dist/claude/state.d.ts +14 -0
  109. package/dist/claude/state.d.ts.map +1 -1
  110. package/dist/claude/state.js.map +1 -1
  111. package/dist/claude/tally.d.ts +27 -0
  112. package/dist/claude/tally.d.ts.map +1 -1
  113. package/dist/claude/tally.js +74 -1
  114. package/dist/claude/tally.js.map +1 -1
  115. package/dist/cli.js +310 -3
  116. package/dist/cli.js.map +1 -1
  117. package/dist/context/node-file.d.ts +4 -10
  118. package/dist/context/node-file.d.ts.map +1 -1
  119. package/dist/context/node-file.js +6 -2
  120. package/dist/context/node-file.js.map +1 -1
  121. package/dist/context/price.d.ts +49 -0
  122. package/dist/context/price.d.ts.map +1 -0
  123. package/dist/context/price.js +83 -0
  124. package/dist/context/price.js.map +1 -0
  125. package/dist/context/savings.d.ts +13 -3
  126. package/dist/context/savings.d.ts.map +1 -1
  127. package/dist/context/savings.js +44 -5
  128. package/dist/context/savings.js.map +1 -1
  129. package/dist/hosts/claude-global.d.ts +33 -0
  130. package/dist/hosts/claude-global.d.ts.map +1 -0
  131. package/dist/hosts/claude-global.js +118 -0
  132. package/dist/hosts/claude-global.js.map +1 -0
  133. package/dist/hosts/plan.d.ts.map +1 -1
  134. package/dist/hosts/plan.js +5 -1
  135. package/dist/hosts/plan.js.map +1 -1
  136. package/dist/hosts/retract.d.ts.map +1 -1
  137. package/dist/hosts/retract.js +33 -13
  138. package/dist/hosts/retract.js.map +1 -1
  139. package/dist/hosts/sections.d.ts +23 -2
  140. package/dist/hosts/sections.d.ts.map +1 -1
  141. package/dist/hosts/sections.js +25 -9
  142. package/dist/hosts/sections.js.map +1 -1
  143. package/dist/mcp/server.d.ts.map +1 -1
  144. package/dist/mcp/server.js +33 -4
  145. package/dist/mcp/server.js.map +1 -1
  146. package/dist/mcp/tools.d.ts.map +1 -1
  147. package/dist/mcp/tools.js +5 -1
  148. package/dist/mcp/tools.js.map +1 -1
  149. package/dist/telemetry/contract.d.ts +12 -0
  150. package/dist/telemetry/contract.d.ts.map +1 -1
  151. package/dist/telemetry/contract.js +19 -0
  152. package/dist/telemetry/contract.js.map +1 -1
  153. package/dist/telemetry/gate.d.ts +14 -0
  154. package/dist/telemetry/gate.d.ts.map +1 -1
  155. package/dist/telemetry/gate.js +18 -2
  156. package/dist/telemetry/gate.js.map +1 -1
  157. package/dist/upkeep-run.d.ts.map +1 -1
  158. package/dist/upkeep-run.js +12 -2
  159. package/dist/upkeep-run.js.map +1 -1
  160. package/dist/upkeep.d.ts +18 -0
  161. package/dist/upkeep.d.ts.map +1 -1
  162. package/dist/upkeep.js +38 -0
  163. package/dist/upkeep.js.map +1 -1
  164. package/dist/util/state.d.ts +9 -0
  165. package/dist/util/state.d.ts.map +1 -1
  166. package/dist/util/state.js.map +1 -1
  167. package/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Getting a brain without leaving the terminal.
3
+ *
4
+ * Until now a brain could only begin in the browser. You signed up on Trail, it
5
+ * created the brain, and handed you a `graft init --brain <id>:<token>` line to
6
+ * paste. That is the right way round when the website is where you already are,
7
+ * and the wrong way round when you are standing in a repository with graft
8
+ * already installed — which is where most people meet graft first.
9
+ *
10
+ * So `graft brain push` on an unlinked repo starts here instead of stopping.
11
+ * Graft opens a listener on loopback, sends the browser to Trail carrying the
12
+ * repository it is standing in and the port to answer on, and waits. Trail does
13
+ * the signing up, makes a brain for that repository, and redirects back to the
14
+ * listener with the brain id and its read token. The push then carries on as if
15
+ * the repo had been linked all along.
16
+ *
17
+ * Two things keep that safe. The listener binds to 127.0.0.1, so nothing off
18
+ * this machine can reach it. And it accepts only a handoff echoing the random
19
+ * state it just generated, so some other page the user happens to have open
20
+ * cannot push a brain of its own choosing into their repository.
21
+ */
22
+ import { spawn } from "node:child_process";
23
+ import { createServer } from "node:http";
24
+ import { randomBytes, timingSafeEqual } from "node:crypto";
25
+ /** Default web host. Overridden by GRAFT_BRAIN_URL, for staging and self-hosted.
26
+ *
27
+ * Trail's own front end, not the shared `agents.nanonets.com` one that `link.ts`
28
+ * calls for the API. Both are served by the same backend, so either would mint a
29
+ * working token — but this URL is the one a person looks at, and it has to be
30
+ * the Trail-branded build, with Trail's Auth0 redirect behind the signup. */
31
+ const DEFAULT_WEB_BASE_URL = "https://app.trailhq.com";
32
+ /** How long the listener waits for the browser before giving up. A signup can
33
+ * involve reading an email, so this is minutes rather than seconds. */
34
+ export const HANDOFF_TIMEOUT_MS = 5 * 60 * 1000;
35
+ /** The one path the listener answers on. */
36
+ export const CALLBACK_PATH = "/graft/callback";
37
+ /** Constant-time compare of two states, length included. */
38
+ function sameState(got, want) {
39
+ const a = Buffer.from(got);
40
+ const b = Buffer.from(want);
41
+ if (a.length !== b.length)
42
+ return false;
43
+ return timingSafeEqual(a, b);
44
+ }
45
+ /** The page the user is left looking at. Deliberately plain: it exists to say
46
+ * "go back to your terminal", and it is served from a throwaway port that is
47
+ * about to close, so there is nothing to style around. */
48
+ function donePage(ok) {
49
+ const msg = ok
50
+ ? "Your brain is connected. Return to your terminal — the push is already running."
51
+ : "That handoff did not match this terminal. Run `graft brain push` again.";
52
+ return `<!doctype html><meta charset="utf-8"><title>graft</title><body style="font:15px/1.5 system-ui,sans-serif;margin:3rem auto;max-width:32rem;color:#1F2129"><p>${msg}</p><p style="color:#676767">You can close this tab.</p>`;
53
+ }
54
+ /**
55
+ * Start listening for the browser's handoff.
56
+ *
57
+ * Port 0 so the OS picks a free one: a fixed port would collide with whatever
58
+ * else the developer is running, and a second graft in another terminal.
59
+ */
60
+ export async function startHandoff() {
61
+ const state = randomBytes(24).toString("base64url");
62
+ let settle = () => { };
63
+ const answered = new Promise((resolve) => {
64
+ settle = resolve;
65
+ });
66
+ const server = createServer((req, res) => {
67
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
68
+ if (url.pathname !== CALLBACK_PATH) {
69
+ res.writeHead(404, { "content-type": "text/plain" });
70
+ res.end("not found");
71
+ return;
72
+ }
73
+ // Checked before anything is read out of the query, so a mismatched handoff
74
+ // never reaches the link-writing path at all.
75
+ if (!sameState(url.searchParams.get("state") ?? "", state)) {
76
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
77
+ res.end(donePage(false));
78
+ return;
79
+ }
80
+ const brainId = (url.searchParams.get("brain") ?? "").trim();
81
+ const token = (url.searchParams.get("token") ?? "").trim();
82
+ if (!brainId || !token) {
83
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
84
+ res.end(donePage(false));
85
+ settle({ error: "the browser came back without a brain id and token", reason: "bad_callback" });
86
+ return;
87
+ }
88
+ // 303 so the browser issues a plain GET, and Location built from
89
+ // GRAFT_BRAIN_URL rather than anything the query carried — the same reason
90
+ // the Trail side builds its callback rather than being told one.
91
+ res.writeHead(303, { location: brainUrl(brainId), "cache-control": "no-store" });
92
+ res.end();
93
+ // No `baseUrl` stored, matching `connect`: GRAFT_BRAIN_URL is read again on
94
+ // every use, so persisting it here would only freeze a host that the env
95
+ // var is already free to move.
96
+ settle({ link: { brainId, token } });
97
+ });
98
+ await new Promise((resolve, reject) => {
99
+ server.once("error", reject);
100
+ server.listen(0, "127.0.0.1", () => {
101
+ server.removeAllListeners("error");
102
+ resolve();
103
+ });
104
+ });
105
+ const port = server.address().port;
106
+ let closed = false;
107
+ const close = () => {
108
+ if (closed)
109
+ return;
110
+ closed = true;
111
+ server.close();
112
+ };
113
+ return {
114
+ port,
115
+ state,
116
+ close,
117
+ async wait(timeoutMs = HANDOFF_TIMEOUT_MS) {
118
+ let timer;
119
+ const timeout = new Promise((resolve) => {
120
+ timer = setTimeout(() => resolve({ error: "timed out waiting for the browser — run `graft brain push` again, or use the link above", reason: "timed_out" }), timeoutMs);
121
+ // The timer must not hold the process open once the browser has answered.
122
+ timer.unref?.();
123
+ });
124
+ try {
125
+ return await Promise.race([answered, timeout]);
126
+ }
127
+ finally {
128
+ if (timer)
129
+ clearTimeout(timer);
130
+ close();
131
+ }
132
+ },
133
+ };
134
+ }
135
+ /** The Trail front end this machine is pointed at, without a trailing slash.
136
+ *
137
+ * Read at the moment it is needed rather than captured once, matching the rest
138
+ * of the brain code: GRAFT_BRAIN_URL is free to move between calls. */
139
+ export function webBaseUrl(baseUrl) {
140
+ return (process.env.GRAFT_BRAIN_URL || baseUrl || DEFAULT_WEB_BASE_URL).replace(/\/+$/, "");
141
+ }
142
+ /** Where the browser is sent once the handoff has been accepted.
143
+ *
144
+ * Back into Trail, at the screen that shows the brain being built. The
145
+ * alternative — leaving the person on a local page that says "go back to your
146
+ * terminal" — ends the flow on a blank throwaway served by a port that is about
147
+ * to close, at exactly the moment there is something to watch.
148
+ *
149
+ * NOT `/brain/<id>`, which is where this pointed first. That route redirects to
150
+ * the brain's graph, and the redirect fires the instant the row exists — which
151
+ * is minutes before it has any rules in it. Every terminal signup therefore
152
+ * landed on an empty visualisation of a brain that was, at that moment, being
153
+ * built perfectly well. The build screen is the same wait the browser-first
154
+ * flow shows, and it leads to the graph once there is a graph. */
155
+ export function brainUrl(brainId, baseUrl) {
156
+ return `${webBaseUrl(baseUrl)}/get-started?step=build&brain=${encodeURIComponent(brainId)}`;
157
+ }
158
+ /** Where to send the browser for a repo's brain. */
159
+ export function signupUrl(opts) {
160
+ const base = webBaseUrl(opts.baseUrl);
161
+ const q = new URLSearchParams({
162
+ step: "repo",
163
+ graft_repo: opts.repo,
164
+ graft_port: String(opts.port),
165
+ graft_state: opts.state,
166
+ });
167
+ return `${base}/get-started?${q.toString()}`;
168
+ }
169
+ /** Open the user's browser, best effort. A machine with no opener is not an
170
+ * error: the URL is printed too, and that is the fallback everywhere.
171
+ *
172
+ * GRAFT_NO_BROWSER leaves the printed URL as the only route, which is what a
173
+ * test wants, and what someone on a machine whose `open` does something
174
+ * surprising wants too. */
175
+ export function openBrowser(url) {
176
+ if (process.env.GRAFT_NO_BROWSER)
177
+ return;
178
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
179
+ try {
180
+ spawn(opener, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
181
+ }
182
+ catch {
183
+ /* the printed URL is the fallback */
184
+ }
185
+ }
186
+ //# sourceMappingURL=signup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup.js","sourceRoot":"","sources":["../../src/brain/signup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Watching a brain being built, from the terminal.
3
+ *
4
+ * `graft brain push` used to end at "it is being mined into rules now — a few
5
+ * minutes. Watch it finish in your browser." and hand the prompt straight back.
6
+ * That sentence is the last thing this process ever says about the work, and it
7
+ * is said BEFORE the part that actually fails: of the repo brains attempted in
8
+ * production, roughly a third die inside the miner, after the push succeeded.
9
+ * The terminal has already returned by then, so on this side the failure does
10
+ * not exist at all — and on a CI runner or over SSH, where nobody is going to
11
+ * open a browser, it exists nowhere.
12
+ *
13
+ * So the push now holds the line and reports the same four stages the browser
14
+ * shows, off the same row. Ctrl-C detaches without cancelling anything, because
15
+ * the work is server-side and killing a watcher must never look like killing
16
+ * the build.
17
+ *
18
+ * What it holds the line UNTIL changed with slicing. The history is now mined in
19
+ * several calls rather than one, and the first of them returns in about ten
20
+ * seconds; filing all of the rules into the graph takes minutes more. Mining is
21
+ * where the failures are — the thing this watcher exists to report — and filing
22
+ * is long, dull and reliable, so the line is held through the first and handed
23
+ * back before the second. Holding a terminal through five minutes of successful
24
+ * filing buys nobody anything, and on a CI runner it is five minutes of a job
25
+ * sitting idle.
26
+ */
27
+ import type { BrainLink } from "./link.js";
28
+ /** The stages, in the order they happen. The wording matches Trail's build
29
+ * screen deliberately: two products describing one process differently is how
30
+ * a person ends up unsure whether they are looking at the same thing. */
31
+ export declare const STAGES: readonly [{
32
+ readonly id: "reach";
33
+ readonly label: "reaching the repository";
34
+ }, {
35
+ readonly id: "read";
36
+ readonly label: "reading its history";
37
+ }, {
38
+ readonly id: "mine";
39
+ readonly label: "mining the rules";
40
+ }, {
41
+ readonly id: "file";
42
+ readonly label: "filing them into the brain";
43
+ }];
44
+ export type StageId = (typeof STAGES)[number]["id"];
45
+ export type StageState = "waiting" | "doing" | "done" | "failed";
46
+ export interface Stage {
47
+ id: StageId;
48
+ label: string;
49
+ state: StageState;
50
+ /** A count worth printing beside a stage that has produced one. */
51
+ detail?: string;
52
+ }
53
+ /** The repo row, as the public endpoint returns it. Only the fields the stages
54
+ * read, so a field added server-side cannot quietly change what is printed. */
55
+ export interface RepoState {
56
+ status: string;
57
+ errorMessage?: string;
58
+ ruleCount: number;
59
+ commitCount: number;
60
+ threadCount: number;
61
+ /** Rules in the graph right now, from the job still running. The row's own
62
+ * `ruleCount` is only written when that job finishes, so mid-build it is
63
+ * zero however many rules are already there.
64
+ *
65
+ * Optional because an older Trail does not send it, and a graft pointed at
66
+ * one must fall back to waiting for the finished row rather than reading a
67
+ * missing field as "no rules". */
68
+ filedSoFar?: number;
69
+ /** Rules the miner has handed over so far. Grows with every slice of the
70
+ * history that comes back, so it is a floor and never a total. */
71
+ foundSoFar?: number;
72
+ }
73
+ export interface BuildView {
74
+ stages: Stage[];
75
+ /**
76
+ * True once rules are in the graph — which is when this watcher has nothing
77
+ * left to report, not when the job ends. The rest of the slices keep mining
78
+ * and filing after the prompt comes back.
79
+ */
80
+ ready: boolean;
81
+ done: boolean;
82
+ /** The server's own words, when it failed. */
83
+ error: string | null;
84
+ }
85
+ /**
86
+ * Which stage the work is in.
87
+ *
88
+ * The rule that matters is where a failure lands. A row that never left
89
+ * `pending` failed at the access check; one that read 412 commits and then died
90
+ * failed in the miner. Reporting both as "could not reach it" would send someone
91
+ * to fix repository access for a problem that has nothing to do with it.
92
+ */
93
+ export declare function stagesFrom(repo: RepoState | null): BuildView;
94
+ /**
95
+ * Read the repo row this brain is building.
96
+ *
97
+ * The same endpoint `fetchExpectedRepo` uses, read for its counts rather than
98
+ * its slug. Null on any failure, so a watcher that cannot reach the API prints
99
+ * nothing new rather than inventing a failure the build did not have.
100
+ */
101
+ export declare function fetchRepoState(link: BrainLink, fetchImpl?: typeof fetch): Promise<RepoState | null>;
102
+ /** One printable line per stage that has moved since it was last printed. */
103
+ export declare function linesFor(view: BuildView, already: Set<string>): string[];
104
+ export interface WatchOptions {
105
+ /** How long to hold before giving up on a build that is still running. */
106
+ timeoutMs?: number;
107
+ pollMs?: number;
108
+ fetchImpl?: typeof fetch;
109
+ /** Where the lines go. Injected so the tests do not need a console. */
110
+ write?: (line: string) => void;
111
+ sleep?: (ms: number) => Promise<void>;
112
+ }
113
+ /**
114
+ * How the watch ended.
115
+ *
116
+ * `building` is the ordinary success: rules are in the brain and the rest of the
117
+ * history is still being read behind the prompt. `completed` is the same thing
118
+ * after the whole job has finished, which only happens for a repository small
119
+ * enough to be mined in one slice.
120
+ */
121
+ export type WatchOutcome = "completed" | "building" | "failed" | "timed_out" | "unreachable";
122
+ /**
123
+ * Hold until the brain has rules, printing each stage as it settles.
124
+ *
125
+ * Returns on the first rules rather than on the finished job: mining is what
126
+ * fails and it is done by then, and filing the rest is minutes of work nobody
127
+ * needs to watch. The caller prints where to see the rest.
128
+ *
129
+ * Fifteen minutes by default. That is far more than the mining now takes, and it
130
+ * stays generous on purpose: a watcher that gives up before the build does would
131
+ * report a healthy build as a timeout, which is the exact confusion this is
132
+ * meant to remove. Giving up says so and says the work continues, because it
133
+ * does.
134
+ */
135
+ export declare function watchBuild(link: BrainLink, opts?: WatchOptions): Promise<WatchOutcome>;
136
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/brain/watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C;;0EAE0E;AAC1E,eAAO,MAAM,MAAM;;;;;;;;;;;;EAKT,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AACpD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;gFACgF;AAChF,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;uCAMmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;uEACmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAsD5D;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA2BhH;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAcxE;AAED,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,uEAAuE;IACvE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAE7F;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAgChG"}
@@ -0,0 +1,172 @@
1
+ import { baseUrlFor } from "./link.js";
2
+ /** The stages, in the order they happen. The wording matches Trail's build
3
+ * screen deliberately: two products describing one process differently is how
4
+ * a person ends up unsure whether they are looking at the same thing. */
5
+ export const STAGES = [
6
+ { id: "reach", label: "reaching the repository" },
7
+ { id: "read", label: "reading its history" },
8
+ { id: "mine", label: "mining the rules" },
9
+ { id: "file", label: "filing them into the brain" },
10
+ ];
11
+ const num = (v) => (typeof v === "number" && v > 0 ? v : 0);
12
+ /**
13
+ * Which stage the work is in.
14
+ *
15
+ * The rule that matters is where a failure lands. A row that never left
16
+ * `pending` failed at the access check; one that read 412 commits and then died
17
+ * failed in the miner. Reporting both as "could not reach it" would send someone
18
+ * to fix repository access for a problem that has nothing to do with it.
19
+ */
20
+ export function stagesFrom(repo) {
21
+ const commits = num(repo?.commitCount);
22
+ const threads = num(repo?.threadCount);
23
+ const rules = num(repo?.ruleCount);
24
+ const filedSoFar = num(repo?.filedSoFar);
25
+ const foundSoFar = num(repo?.foundSoFar);
26
+ const reached = !!repo && repo.status !== "pending";
27
+ const readIt = commits > 0 || threads > 0;
28
+ // Rules that exist, whether the job has finished writing its count or not.
29
+ const hasRules = filedSoFar > 0 || rules > 0;
30
+ const filed = rules > 0;
31
+ const failed = repo?.status === "failed";
32
+ const failedAt = !failed ? null : !reached || !readIt ? "reach" : !hasRules ? "mine" : "file";
33
+ const order = STAGES.map((s) => s.id);
34
+ const state = (id) => {
35
+ if (failedAt === id)
36
+ return "failed";
37
+ if (failedAt && order.indexOf(id) > order.indexOf(failedAt))
38
+ return "waiting";
39
+ switch (id) {
40
+ case "reach":
41
+ return reached ? "done" : "doing";
42
+ case "read":
43
+ return readIt ? "done" : reached ? "doing" : "waiting";
44
+ case "mine":
45
+ // Done on the first slice's rules. The remaining slices are still being
46
+ // mined at this point, behind the prompt this watcher is about to
47
+ // return — which is the whole change.
48
+ return hasRules ? "done" : readIt ? "doing" : "waiting";
49
+ case "file":
50
+ return filed ? "done" : hasRules ? "doing" : readIt ? "waiting" : "waiting";
51
+ }
52
+ };
53
+ const detail = (id) => {
54
+ if (id === "read" && (commits || threads)) {
55
+ return [commits ? `${commits} commits` : null, threads ? `${threads} discussions` : null].filter(Boolean).join(", ");
56
+ }
57
+ // "so far" because the count grows with each slice that lands. Printing it
58
+ // as a total would promise history that has not been read yet.
59
+ if (id === "mine" && foundSoFar)
60
+ return `${foundSoFar} rules so far`;
61
+ if (id === "file" && rules)
62
+ return `${rules} rules`;
63
+ if (id === "file" && filedSoFar)
64
+ return `${filedSoFar} rules so far`;
65
+ return undefined;
66
+ };
67
+ return {
68
+ stages: STAGES.map((s) => ({ id: s.id, label: s.label, state: state(s.id), detail: detail(s.id) })),
69
+ ready: hasRules && !failed,
70
+ done: repo?.status === "completed" && filed,
71
+ error: failed ? (repo?.errorMessage?.trim() || "the read stopped before it finished") : null,
72
+ };
73
+ }
74
+ /**
75
+ * Read the repo row this brain is building.
76
+ *
77
+ * The same endpoint `fetchExpectedRepo` uses, read for its counts rather than
78
+ * its slug. Null on any failure, so a watcher that cannot reach the API prints
79
+ * nothing new rather than inventing a failure the build did not have.
80
+ */
81
+ export async function fetchRepoState(link, fetchImpl = fetch) {
82
+ try {
83
+ const res = await fetchImpl(`${baseUrlFor(link)}/api/public/brains/${encodeURIComponent(link.brainId)}/repo`, {
84
+ headers: { authorization: `Bearer ${link.token}`, accept: "application/json" },
85
+ signal: AbortSignal.timeout(5000),
86
+ });
87
+ if (!res.ok)
88
+ return null;
89
+ const body = (await res.json());
90
+ if (!body.repo)
91
+ return null;
92
+ return {
93
+ status: String(body.repo.status ?? ""),
94
+ errorMessage: body.repo.error_message,
95
+ ruleCount: num(body.repo.rule_count),
96
+ commitCount: num(body.repo.commit_count),
97
+ threadCount: num(body.repo.thread_count),
98
+ filedSoFar: num(body.build?.filed_so_far),
99
+ foundSoFar: num(body.build?.found_so_far),
100
+ };
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ /** One printable line per stage that has moved since it was last printed. */
107
+ export function linesFor(view, already) {
108
+ const out = [];
109
+ for (const s of view.stages) {
110
+ // Only settled states are printed. A line per poll for a stage that is
111
+ // merely still running would bury the four that matter under a hundred
112
+ // that do not — this is a log, not a redrawn frame.
113
+ if (s.state !== "done" && s.state !== "failed")
114
+ continue;
115
+ const key = `${s.id}:${s.state}`;
116
+ if (already.has(key))
117
+ continue;
118
+ already.add(key);
119
+ const mark = s.state === "done" ? "✓" : "✗";
120
+ out.push(` ${mark} ${s.label}${s.detail ? ` — ${s.detail}` : ""}`);
121
+ }
122
+ return out;
123
+ }
124
+ /**
125
+ * Hold until the brain has rules, printing each stage as it settles.
126
+ *
127
+ * Returns on the first rules rather than on the finished job: mining is what
128
+ * fails and it is done by then, and filing the rest is minutes of work nobody
129
+ * needs to watch. The caller prints where to see the rest.
130
+ *
131
+ * Fifteen minutes by default. That is far more than the mining now takes, and it
132
+ * stays generous on purpose: a watcher that gives up before the build does would
133
+ * report a healthy build as a timeout, which is the exact confusion this is
134
+ * meant to remove. Giving up says so and says the work continues, because it
135
+ * does.
136
+ */
137
+ export async function watchBuild(link, opts = {}) {
138
+ const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1000;
139
+ const pollMs = opts.pollMs ?? 4000;
140
+ const write = opts.write ?? ((l) => console.error(l));
141
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms).unref?.()));
142
+ const printed = new Set();
143
+ const startedAt = Date.now();
144
+ // A row we could never read at all, as opposed to one that answered once and
145
+ // then stopped answering: the first is a broken link or a host that is down
146
+ // and is worth saying, the second is a blip the next poll usually covers.
147
+ let everRead = false;
148
+ for (;;) {
149
+ const repo = await fetchRepoState(link, opts.fetchImpl);
150
+ if (repo) {
151
+ everRead = true;
152
+ const view = stagesFrom(repo);
153
+ for (const line of linesFor(view, printed))
154
+ write(line);
155
+ if (view.error) {
156
+ write(`✗ ${view.error}`);
157
+ return "failed";
158
+ }
159
+ if (view.done)
160
+ return "completed";
161
+ // The ordinary ending now. The job is still filing the rules the miner
162
+ // has handed over, and still mining the older slices of the history, and
163
+ // neither is worth a terminal sitting on it.
164
+ if (view.ready)
165
+ return "building";
166
+ }
167
+ if (Date.now() - startedAt >= timeoutMs)
168
+ return everRead ? "timed_out" : "unreachable";
169
+ await sleep(pollMs);
170
+ }
171
+ }
172
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/brain/watch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type HostTarget } from '../hosts/registry.js';
2
+ import { type UpsertAction } from '../hosts/sections.js';
3
+ import type { BrainRule } from './link.js';
4
+ /** What a rules write did to one host's file. */
5
+ export interface BrainWrite {
6
+ id: string;
7
+ path: string;
8
+ action: UpsertAction;
9
+ }
10
+ /**
11
+ * Render the rules block for an instruction file.
12
+ *
13
+ * Grouped by the file the rule governs, because that is how someone reads a
14
+ * codebase, and repo-wide rules (no symbol) come first — they apply everywhere,
15
+ * so they should not be buried under a path heading.
16
+ */
17
+ export declare function renderBrainSection(rules: BrainRule[], repoLabel: string): string;
18
+ /**
19
+ * Every instruction file a brain section should go in.
20
+ *
21
+ * `section`-kind hosts only. An `owned`-kind host (Cursor's `.mdc`, Kiro's
22
+ * steering file, a `SKILL.md`) is a file graft rewrites whole from a pure
23
+ * template, so a second managed region inside one would be erased by the next
24
+ * `init` — those agents get their rules through `graft ask` instead, which they
25
+ * all call.
26
+ *
27
+ * Deduplicated by path: three hosts target `AGENTS.md`, and writing it three
28
+ * times in one pass is just three identical upserts.
29
+ */
30
+ export declare function brainSectionTargets(repo: string, home: string, ids?: string[]): HostTarget[];
31
+ /**
32
+ * Write the brain's rules into each selected host's instruction file.
33
+ *
34
+ * Best-effort per file: one unwritable path must not stop the rest, since a
35
+ * partially delivered rulebook is strictly better than none.
36
+ */
37
+ export declare function writeBrainSections(repo: string, home: string, rules: BrainRule[], repoLabel: string, ids?: string[]): BrainWrite[];
38
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/brain/wire.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC7F,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,iDAAiD;AACjD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAqChF;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAa5F;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,EAAE,GACb,UAAU,EAAE,CAed"}