Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
578 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
2 * What a project's files say about it, read without running anything: the
3 * languages and packages in its manifests, the APIs it exposes, its docs,
4 * whether its workflows run tests, who owns it, and short facts and
5 * conventions worth remembering. Pure: given a file's path and text, the
6 * same facts every time, so a file whose blob has not changed is never read
7 * again.
8 */
9
10import { parse as parseYaml } from "yaml";
11
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared12import { aspirational, bulletsOf, classify, bulletSection, MAX_DOC_HINT_CHARS, planLike, planSection, plain, SETUP_HEADING, wholeSentences, type MemoryDocsConfig } from "./harvest.ts";
13
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor14export type Ecosystem = "npm" | "cargo" | "go" | "pypi";
15
16export type PackageFact = {
17 ecosystem: Ecosystem;
18 name: string;
19 version: string | null;
20 /** Its dependencies' names, at most `MAX_DEPENDENCIES`. */
21 dependencies: string[];
22 /** For npm: the scripts people run. */
23 scripts?: Record<string, string>;
24 /** For a monorepo's root: where its packages are. */
25 members?: string[];
26};
27
28export type ApiFact = {
29 /** `openapi` for a described HTTP API, `worker` for a Worker's routes. */
30 kind: "openapi" | "worker";
31 name: string;
32 summary: string | null;
33 /** `GET /users`, or a route pattern such as `api.example.com/*`. */
34 routes: string[];
35};
36
37export type DocFact = {
38 path: string;
39 title: string;
40 summary: string | null;
41 /** The doc in pieces of at most `CHUNK_CHARS`, each starting with its heading. */
42 chunks: string[];
43 /** README, AGENTS.md (or CLAUDE.md), CONTRIBUTING, or another doc. */
44 role: "readme" | "agents" | "contributing" | "doc";
45};
46
47/** Something worth remembering, as a memory candidate. */
48export type Hint = {
49 kind: "fact" | "convention" | "decision" | "gotcha";
50 text: string;
51 /** 0 to 1. A doc's hint at or above 0.85 is kept without review. */
52 confidence: number;
53 evidence: string;
54};
55
56export type FileFacts = {
57 languages: string[];
58 packages: PackageFact[];
59 apis: ApiFact[];
60 doc: DocFact | null;
61 /** A workflow that runs tests. */
62 tests: boolean;
63 /** Usernames this file names as owners. */
64 owners: string[];
65 hints: Hint[];
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared66 /** For `.g1t/project.yml`: which docs memory is suggested from. */
67 memory?: MemoryDocsConfig | null;
68 /** The `EXTRACT_VERSION` that read it; facts from an older one are read again. */
69 version?: number;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor70};
71
72/** What extraction knows besides the file itself. */
73export type ExtractContext = {
74 /** The project's name, for writing hints. */
75 project: string;
76 /** The names of the files beside the manifest: lockfiles, tsconfig.json. */
77 siblings: string[];
78};
79
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared80/** Bumped when what is read from a file changes, so stored facts are read again. */
81export const EXTRACT_VERSION = 2;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor82export const MAX_DEPENDENCIES = 60;
83export const CHUNK_CHARS = 1500;
84export const MAX_CHUNKS = 12;
85const MAX_ROUTES = 40;
86const MAX_HINTS_PER_FILE = 12;
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared87const MAX_HINT_CHARS = MAX_DOC_HINT_CHARS;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor88
89const EMPTY: FileFacts = { languages: [], packages: [], apis: [], doc: null, tests: false, owners: [], hints: [] };
90
91const DOC_NAME = /^(readme|agents|claude|contributing)(\.(md|markdown|txt))?$/i;
92const OPENAPI_NAME = /^(openapi|swagger)\.(json|ya?ml)$/i;
93const MANIFEST_NAMES = new Set([
94 "package.json",
95 "Cargo.toml",
96 "go.mod",
97 "pyproject.toml",
98 "requirements.txt",
99 "wrangler.jsonc",
100 "wrangler.json",
101 "wrangler.toml",
102 "CODEOWNERS",
103]);
104
105/** The base name of a path. */
106function base(path: string): string {
107 return path.slice(path.lastIndexOf("/") + 1);
108}
109
110/**
111 * Whether a file at `path` (relative to the project's root) is one this
112 * reads: a manifest, an API description, a doc, a workflow or ownership.
113 */
114export function interesting(path: string): boolean {
115 const name = base(path);
116 const dir = path.slice(0, Math.max(0, path.lastIndexOf("/")));
117 if (dir === "" && (MANIFEST_NAMES.has(name) || DOC_NAME.test(name) || OPENAPI_NAME.test(name))) return true;
118 if ((dir === "docs" || dir === "doc" || dir === "runbooks" || dir === "docs/runbooks") && /\.(md|markdown)$/i.test(name)) return true;
119 if ((dir === ".g1t/workflows" || dir === ".github/workflows") && /\.ya?ml$/i.test(name)) return true;
120 if (path === ".g1t/project.yml" || path === ".github/CODEOWNERS" || path === "docs/CODEOWNERS") return true;
121 if (dir === "" && OPENAPI_NAME.test(name)) return true;
122 return false;
123}
124
125/** One line, at most `max` characters. */
126export function clip(text: string, max = MAX_HINT_CHARS): string {
127 const line = text.replace(/\s+/g, " ").trim();
128 return line.length <= max ? line : `${line.slice(0, max - 1).replace(/\s+\S*$/, "")}…`;
129}
130
131/** JSON with comments and trailing commas, as wrangler.jsonc and tsconfig.json allow. */
132export function parseJsonc(text: string): unknown {
133 let out = "";
134 let inString = false;
135 for (let i = 0; i < text.length; i++) {
136 const c = text[i];
137 if (inString) {
138 out += c;
139 if (c === "\\") {
140 out += text[++i] ?? "";
141 } else if (c === '"') {
142 inString = false;
143 }
144 continue;
145 }
146 if (c === '"') {
147 inString = true;
148 out += c;
149 } else if (c === "/" && text[i + 1] === "/") {
150 while (i < text.length && text[i] !== "\n") i++;
151 out += "\n";
152 } else if (c === "/" && text[i + 1] === "*") {
153 i += 2;
154 while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
155 i++;
156 } else {
157 out += c;
158 }
159 }
160 return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1"));
161}
162
163type Toml = Record<string, Record<string, unknown>>;
164
165/**
166 * The little of TOML that manifests use: `[sections]`, `key = "string"`,
167 * numbers, booleans, one-line and multi-line string arrays, and inline
168 * tables kept as text. Keys outside a section are under "".
169 */
170export function parseToml(text: string): Toml {
171 const out: Toml = { "": {} };
172 let section = "";
173 const lines = text.split(/\r?\n/);
174 for (let i = 0; i < lines.length; i++) {
175 let line = lines[i].replace(/(^|\s)#.*$/, "").trim();
176 if (!line) continue;
177 const header = /^\[\[?([^\]]+)\]\]?$/.exec(line);
178 if (header) {
179 section = header[1].trim().replace(/"/g, "");
180 out[section] ??= {};
181 continue;
182 }
183 const eq = line.indexOf("=");
184 if (eq < 0) continue;
185 const key = line.slice(0, eq).trim().replace(/"/g, "");
186 let value = line.slice(eq + 1).trim();
187 // A multi-line array: gather until its closing bracket.
188 if (value.startsWith("[") && !value.includes("]")) {
189 while (i + 1 < lines.length && !value.includes("]")) value += " " + lines[++i].replace(/#.*$/, "").trim();
190 }
191 out[section] ??= {};
192 out[section][key] = tomlValue(value);
193 }
194 return out;
195}
196
197function tomlValue(value: string): unknown {
198 if (value.startsWith('"') || value.startsWith("'")) return value.slice(1, value.lastIndexOf(value[0]));
199 if (value === "true" || value === "false") return value === "true";
200 if (value.startsWith("[")) {
201 return [...value.matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2]);
202 }
203 if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
204 return value;
205}
206
207function str(value: unknown): string | null {
208 return typeof value === "string" && value.trim() ? value.trim() : null;
209}
210
211function keys(value: unknown): string[] {
212 return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
213}
214
215/** The package manager a JavaScript project's lockfile says it uses. */
216export function packageManager(siblings: string[]): "pnpm" | "yarn" | "bun" | "npm" | null {
217 const has = (name: string) => siblings.includes(name);
218 if (has("pnpm-lock.yaml")) return "pnpm";
219 if (has("yarn.lock")) return "yarn";
220 if (has("bun.lockb") || has("bun.lock")) return "bun";
221 if (has("package-lock.json")) return "npm";
222 return null;
223}
224
225function npm(path: string, text: string, ctx: ExtractContext): FileFacts {
226 const json = JSON.parse(text) as Record<string, unknown>;
227 const scripts = (json.scripts && typeof json.scripts === "object" ? json.scripts : {}) as Record<string, string>;
228 const deps = [...keys(json.dependencies), ...keys(json.devDependencies)];
229 const typescript = deps.includes("typescript") || ctx.siblings.includes("tsconfig.json");
230 const workspaces = Array.isArray(json.workspaces)
231 ? (json.workspaces as unknown[]).filter((w): w is string => typeof w === "string")
232 : Array.isArray((json.workspaces as { packages?: unknown })?.packages)
233 ? ((json.workspaces as { packages: unknown[] }).packages.filter((w): w is string => typeof w === "string"))
234 : [];
235 const pm = packageManager(ctx.siblings);
236 const run = (script: string) => (script === "test" ? `${pm ?? "npm"} test` : `${pm ?? "npm"} run ${script}`);
237 const hints: Hint[] = [];
238 if (pm) {
239 const others = ["npm", "pnpm", "yarn", "bun"].filter((other) => other !== pm).join(" or ");
240 hints.push({
241 kind: "convention",
242 text: `${ctx.project} uses ${pm} (its lockfile is committed): install with \`${pm} install\`, not ${others}.`,
243 confidence: 0.9,
244 evidence: `${path} beside its ${pm} lockfile`,
245 });
246 }
247 for (const script of ["test", "typecheck", "lint", "build"]) {
248 const command = str(scripts[script]);
249 if (!command) continue;
250 hints.push({
251 kind: "fact",
252 text: clip(`In ${ctx.project}, \`${run(script)}\` runs ${script === "test" ? "the tests" : `the ${script}`}: \`${command}\`.`),
253 confidence: 0.9,
254 evidence: `${path} scripts.${script}`,
255 });
256 }
257 if (workspaces.length > 0) {
258 hints.push({
259 kind: "fact",
260 text: clip(`${ctx.project} is a monorepo; its packages are in ${workspaces.join(", ")}.`),
261 confidence: 0.9,
262 evidence: `${path} workspaces`,
263 });
264 }
265 const name = str(json.name);
266 return {
267 ...EMPTY,
268 languages: typescript ? ["TypeScript"] : ["JavaScript"],
269 packages: name
270 ? [
271 {
272 ecosystem: "npm",
273 name,
274 version: str(json.version),
275 dependencies: deps.slice(0, MAX_DEPENDENCIES),
276 scripts: Object.fromEntries(Object.entries(scripts).filter(([, v]) => typeof v === "string").slice(0, 20)),
277 ...(workspaces.length ? { members: workspaces } : {}),
278 },
279 ]
280 : [],
281 hints,
282 };
283}
284
285function cargo(path: string, text: string, ctx: ExtractContext): FileFacts {
286 const toml = parseToml(text);
287 const name = str(toml.package?.name);
288 const members = Array.isArray(toml.workspace?.members) ? (toml.workspace.members as string[]) : [];
289 const deps = [...keys(toml.dependencies), ...keys(toml["dev-dependencies"]), ...keys(toml["workspace.dependencies"])];
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared290 // Where its crates live (apps/*, crates/*), not each one: the list
291 // changes with every crate added, and the fact does not.
292 const places = [...new Set(members.map((member) => (member.includes("/") ? `${member.slice(0, member.indexOf("/"))}/*` : member)))];
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor293 const hints: Hint[] = [
294 {
295 kind: "fact",
296 text: clip(
297 members.length
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared298 ? `${ctx.project} is a Cargo workspace (${places.join(", ")}); \`cargo test\` runs its tests.`
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor299 : `${ctx.project} is written in Rust; \`cargo test\` runs its tests.`,
300 ),
301 confidence: 0.9,
302 evidence: path,
303 },
304 ];
305 return {
306 ...EMPTY,
307 languages: ["Rust"],
308 packages: name
309 ? [{ ecosystem: "cargo", name, version: str(toml.package?.version), dependencies: [...new Set(deps)].slice(0, MAX_DEPENDENCIES) }]
310 : members.length
311 ? [{ ecosystem: "cargo", name: `${ctx.project} (workspace)`, version: null, dependencies: [...new Set(deps)].slice(0, MAX_DEPENDENCIES), members }]
312 : [],
313 hints,
314 };
315}
316
317function goMod(path: string, text: string, ctx: ExtractContext): FileFacts {
318 const module = /^module\s+(\S+)/m.exec(text)?.[1] ?? null;
319 const version = /^go\s+(\S+)/m.exec(text)?.[1] ?? null;
320 const requires = [...text.matchAll(/^\s*(?:require\s+)?([\w.-]+\.[\w.-]+\/\S+)\s+v[\w.+-]+/gm)].map((m) => m[1]);
321 return {
322 ...EMPTY,
323 languages: ["Go"],
324 packages: module ? [{ ecosystem: "go", name: module, version, dependencies: requires.slice(0, MAX_DEPENDENCIES) }] : [],
325 hints: [
326 {
327 kind: "fact",
328 text: clip(`${ctx.project} is a Go module${module ? ` (${module}${version ? `, go ${version}` : ""})` : ""}; \`go test ./...\` runs its tests.`),
329 confidence: 0.9,
330 evidence: path,
331 },
332 ],
333 };
334}
335
336function python(path: string, text: string, ctx: ExtractContext): FileFacts {
337 let name: string | null = null;
338 let version: string | null = null;
339 let deps: string[] = [];
340 if (base(path) === "pyproject.toml") {
341 const toml = parseToml(text);
342 name = str(toml.project?.name) ?? str(toml["tool.poetry"]?.name);
343 version = str(toml.project?.version) ?? str(toml["tool.poetry"]?.version);
344 deps = [
345 ...(Array.isArray(toml.project?.dependencies) ? (toml.project.dependencies as string[]) : []),
346 ...keys(toml["tool.poetry.dependencies"]),
347 ];
348 } else {
349 deps = text.split(/\r?\n/).map((line) => line.replace(/#.*$/, "").trim()).filter((line) => line && !line.startsWith("-"));
350 }
351 deps = deps.map((dep) => dep.split(/[<>=!~;\[\s]/)[0]).filter(Boolean);
352 const hints: Hint[] = deps.includes("pytest")
353 ? [{ kind: "fact", text: `In ${ctx.project}, \`pytest\` runs the tests.`, confidence: 0.85, evidence: path }]
354 : [];
355 return {
356 ...EMPTY,
357 languages: ["Python"],
358 packages: name ? [{ ecosystem: "pypi", name, version, dependencies: deps.slice(0, MAX_DEPENDENCIES) }] : [],
359 hints,
360 };
361}
362
363function wrangler(path: string, text: string): FileFacts {
364 let config: Record<string, unknown> = {};
365 if (path.endsWith(".toml")) {
366 const toml = parseToml(text);
367 config = { ...toml[""], routes: Array.isArray(toml[""].routes) ? toml[""].routes : [] };
368 if (toml.routes) config.routes = [toml.routes.pattern];
369 } else {
370 config = parseJsonc(text) as Record<string, unknown>;
371 }
372 const routes: string[] = [];
373 const add = (value: unknown) => {
374 const pattern = typeof value === "string" ? value : str((value as { pattern?: unknown })?.pattern);
375 if (pattern) routes.push(pattern);
376 };
377 if (Array.isArray(config.routes)) config.routes.forEach(add);
378 add(config.route);
379 const name = str(config.name);
380 if (!name) return EMPTY;
381 return {
382 ...EMPTY,
383 apis: [
384 {
385 kind: "worker",
386 name,
387 summary: routes.length ? `Served at ${routes.slice(0, 5).join(", ")}` : "A Worker reached through service bindings",
388 routes: routes.slice(0, MAX_ROUTES),
389 },
390 ],
391 };
392}
393
394const METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
395
396function openapi(path: string, text: string): FileFacts {
397 const spec = (/\.json$/i.test(path) ? JSON.parse(text) : parseYaml(text)) as {
398 info?: { title?: unknown; description?: unknown; version?: unknown };
399 paths?: Record<string, Record<string, unknown>>;
400 };
401 const routes: string[] = [];
402 for (const [route, operations] of Object.entries(spec?.paths ?? {})) {
403 for (const method of Object.keys(operations ?? {})) {
404 if (METHODS.includes(method)) routes.push(`${method.toUpperCase()} ${route}`);
405 }
406 }
407 const title = str(spec?.info?.title) ?? base(path);
408 return {
409 ...EMPTY,
410 apis: [
411 {
412 kind: "openapi",
413 name: title,
414 summary: clip(str(spec?.info?.description) ?? `${routes.length} operations${str(spec?.info?.version) ? `, version ${spec.info!.version}` : ""}`),
415 routes: routes.slice(0, MAX_ROUTES),
416 },
417 ],
418 };
419}
420
421/** A doc split at its headings into pieces of at most `CHUNK_CHARS`. */
422export function chunk(text: string, title: string): string[] {
423 const sections: string[] = [];
424 let current = "";
425 for (const line of text.split(/\r?\n/)) {
426 if (/^#{1,3}\s/.test(line) && current.trim()) {
427 sections.push(current.trim());
428 current = "";
429 }
430 current += line + "\n";
431 }
432 if (current.trim()) sections.push(current.trim());
433 const pieces: string[] = [];
434 for (const section of sections) {
435 const heading = /^#{1,3}\s+(.*)$/m.exec(section)?.[1]?.trim() ?? title;
436 for (let at = 0; at < section.length && pieces.length < MAX_CHUNKS; at += CHUNK_CHARS) {
437 const body = section.slice(at, at + CHUNK_CHARS);
438 pieces.push(at === 0 ? body : `${heading} (continued)\n${body}`);
439 }
440 }
441 return pieces.slice(0, MAX_CHUNKS);
442}
443
444const COMMAND = /^\s*(?:\$\s*)?((?:npm|pnpm|yarn|bun|npx|cargo|go|make|pytest|python3?|poetry|uv|docker|wrangler|just|mix|bundle|rake|gradle|\.\/gradlew|mvn|dotnet)\b[^\n]{0,160})$/;
445
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared446/**
447 * What a doc says worth remembering: the commands in its setup sections,
448 * and the bullets of its conventions and setup sections, each whole (see
449 * `./harvest`). A doc that reads as a plan, a report or feedback says
450 * nothing; nor does a plan section in any doc, or a line that says what
451 * someone wants rather than how things are.
452 */
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor453function docHints(path: string, text: string, role: DocFact["role"], ctx: ExtractContext): Hint[] {
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared454 const agents = role === "agents";
455 if (!agents && planLike(text)) return [];
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor456 const hints: Hint[] = [];
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared457 const where = (heading: string) => `${path}${heading ? ` (${heading})` : ""}`;
458
459 // Commands in code blocks under a setup heading (any, in AGENTS.md).
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor460 let heading = "";
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared461 let skipping = 0;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor462 let fenced = false;
463 for (const raw of text.split(/\r?\n/)) {
464 const line = raw.trimEnd();
465 if (/^\s*(```|~~~)/.test(line)) {
466 fenced = !fenced;
467 continue;
468 }
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared469 const h = /^(#{1,6})\s+(.*)$/.exec(line);
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor470 if (h && !fenced) {
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared471 const level = h[1].length;
472 if (skipping && level <= skipping) skipping = 0;
473 heading = h[2].trim();
474 if (!skipping && planSection(heading)) skipping = level;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor475 continue;
476 }
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared477 if (!fenced || skipping) continue;
478 const command = COMMAND.exec(line)?.[1];
479 if (command && (agents || SETUP_HEADING.test(heading))) {
480 const what = heading ? plain(heading).replace(/[:.]$/, "").toLowerCase() : "work on it";
481 const said = wholeSentences(`In ${ctx.project}, for ${what}: \`${command.trim()}\`.`, MAX_HINT_CHARS);
482 if (said) hints.push({ kind: "fact", text: said, confidence: agents ? 0.9 : 0.7, evidence: where(heading) });
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor483 }
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared484 }
485
486 // Bullets, whole, from the sections that say how things are done.
487 for (const bullet of bulletsOf(text)) {
488 if (!bulletSection(bullet.heading, role)) continue;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor489 // A bullet that is only a link is a table of contents.
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared490 if (/^\[[^\]]*\]\([^)]*\)\.?$/.test(bullet.text)) continue;
491 if (aspirational(bullet.text, role)) continue;
492 const words = plain(bullet.text);
493 // Too short to mean anything, or a lead-in to a list of its own.
494 if (words.length < 20 || /:$/.test(words)) continue;
495 const said = wholeSentences(words, MAX_HINT_CHARS);
496 if (!said) continue;
497 hints.push({ ...classify(said, bullet.heading, role), text: said, evidence: where(bullet.heading) });
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor498 }
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared499 // The same line under two headings is one hint.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor500 const seen = new Set<string>();
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared501 return hints
502 .filter((hint) => {
503 const key = hint.text.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
504 return !seen.has(key) && seen.add(key);
505 })
506 .slice(0, MAX_HINTS_PER_FILE);
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor507}
508
509function doc(path: string, text: string, ctx: ExtractContext): FileFacts {
510 const name = base(path);
511 const role: DocFact["role"] = /^readme/i.test(name)
512 ? "readme"
513 : /^(agents|claude)/i.test(name)
514 ? "agents"
515 : /^contributing/i.test(name)
516 ? "contributing"
517 : "doc";
518 const title = /^#\s+(.*)$/m.exec(text)?.[1]?.trim() ?? name.replace(/\.(md|markdown|txt)$/i, "");
519 const paragraph = text
520 .split(/\r?\n\s*\r?\n/)
521 .map((block) => block.trim())
522 .find((block) => block && !block.startsWith("#") && !block.startsWith("```") && !block.startsWith("<") && !block.startsWith("!["));
523 return {
524 ...EMPTY,
525 doc: { path, title: clip(title, 120), summary: paragraph ? clip(paragraph) : null, chunks: chunk(text, title), role },
526 hints: docHints(path, text, role, ctx),
527 };
528}
529
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared530/** Strings, from a YAML value that is a string or a list of them. */
531function strings(value: unknown): string[] {
532 const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
533 return list.filter((item): item is string => typeof item === "string" && item.trim() !== "").map((item) => item.trim()).slice(0, 50);
534}
535
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor536function owners(path: string, text: string): FileFacts {
537 const names = new Set<string>();
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared538 let memory: MemoryDocsConfig | null = null;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor539 if (path.endsWith("project.yml")) {
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared540 const parsed = parseYaml(text) as { owners?: unknown; memory?: { docs?: unknown; skip?: unknown } } | null;
541 if (parsed?.memory && typeof parsed.memory === "object") memory = { docs: strings(parsed.memory.docs), skip: strings(parsed.memory.skip) };
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor542 const list = Array.isArray(parsed?.owners) ? parsed.owners : typeof parsed?.owners === "string" ? [parsed.owners] : [];
543 for (const owner of list) if (typeof owner === "string") names.add(owner.replace(/^@/, "").trim());
544 } else {
545 for (const line of text.split(/\r?\n/)) {
546 if (line.trim().startsWith("#")) continue;
547 for (const match of line.matchAll(/@([A-Za-z0-9][A-Za-z0-9_.-]*)(?=\s|$)/g)) names.add(match[1]);
548 }
549 }
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared550 return { ...EMPTY, owners: [...names].filter(Boolean).slice(0, 20), ...(memory ? { memory } : {}) };
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor551}
552
553const TESTS = /\b(test|tests|pytest|vitest|jest|mocha|cargo test|go test|npm test|playwright|cypress)\b/i;
554
555/** The facts in one file. A file that cannot be parsed says nothing. */
556export function extract(path: string, text: string, ctx: ExtractContext): FileFacts {
Merge memory from docs: only docs on how to work here, whole sentences, no near-duplicates, stale doc suggestions cleared557 return { ...read(path, text, ctx), version: EXTRACT_VERSION };
558}
559
560function read(path: string, text: string, ctx: ExtractContext): FileFacts {
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor561 const name = base(path);
562 try {
563 if (path.startsWith(".g1t/workflows/") || path.startsWith(".github/workflows/")) {
564 return { ...EMPTY, tests: TESTS.test(text) };
565 }
566 if (path === ".g1t/project.yml" || name === "CODEOWNERS") return owners(path, text);
567 if (name === "package.json") return npm(path, text, ctx);
568 if (name === "Cargo.toml") return cargo(path, text, ctx);
569 if (name === "go.mod") return goMod(path, text, ctx);
570 if (name === "pyproject.toml" || name === "requirements.txt") return python(path, text, ctx);
571 if (/^wrangler\.(jsonc|json|toml)$/.test(name)) return wrangler(path, text);
572 if (OPENAPI_NAME.test(name)) return openapi(path, text);
573 if (/\.(md|markdown|txt)$/i.test(name) || DOC_NAME.test(name)) return doc(path, text, ctx);
574 } catch {
575 return EMPTY;
576 }
577 return EMPTY;
578}

This file's history is long; its oldest lines are credited to the oldest commit read.