toolroll 0.9.92 → 0.9.94
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/CHANGELOG.md +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -3
- package/dist/assignment.js +16 -3
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +50 -1
- package/dist/release.js +118 -11
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +29 -21
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +18 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code map's parser process (code-map-parse.ts): a static read of blobs Toolroll fetched, in its own process, with a
|
|
3
|
+
* capped heap and output. It reads one JSON request on stdin and writes one JSON answer on stdout. `createSourceFile`
|
|
4
|
+
* only: no Program, checker or plugin; nothing from the repository is imported or run; resolution is against the paths
|
|
5
|
+
* in the request.
|
|
6
|
+
*/
|
|
7
|
+
import { compilerOptionsOf, extractFile, resolveAll } from "./code-extract.js";
|
|
8
|
+
const ROOT = "/repo";
|
|
9
|
+
const chunks = [];
|
|
10
|
+
for await (const chunk of process.stdin)
|
|
11
|
+
chunks.push(chunk);
|
|
12
|
+
const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
13
|
+
const compiler = (await import("typescript-parser")).default;
|
|
14
|
+
const normal = (file) => { const parts = []; for (const part of file.split("/")) {
|
|
15
|
+
if (part === "" || part === ".")
|
|
16
|
+
continue;
|
|
17
|
+
if (part === "..")
|
|
18
|
+
parts.pop();
|
|
19
|
+
else
|
|
20
|
+
parts.push(part);
|
|
21
|
+
} return `/${parts.join("/")}`; };
|
|
22
|
+
const known = new Map(request.paths.map(path => [`${ROOT}/${path}`, {}]));
|
|
23
|
+
for (const file of request.files)
|
|
24
|
+
known.set(`${ROOT}/${file.path}`, { text: file.text });
|
|
25
|
+
if (request.tsconfig !== null)
|
|
26
|
+
known.set(`${ROOT}/tsconfig.json`, { text: request.tsconfig });
|
|
27
|
+
const directories = new Set([ROOT]);
|
|
28
|
+
for (const path of known.keys()) {
|
|
29
|
+
let at = path.lastIndexOf("/");
|
|
30
|
+
while (at > ROOT.length) {
|
|
31
|
+
const dir = path.slice(0, at);
|
|
32
|
+
if (directories.has(dir))
|
|
33
|
+
break;
|
|
34
|
+
directories.add(dir);
|
|
35
|
+
at = dir.lastIndexOf("/");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const { options, problem } = compilerOptionsOf(compiler, `${ROOT}/tsconfig.json`, request.tsconfig ?? undefined, ROOT);
|
|
39
|
+
const resolveOne = resolveAll(compiler, ROOT, known, directories, options, normal);
|
|
40
|
+
const facts = request.files.map(file => {
|
|
41
|
+
const fileName = `${ROOT}/${file.path}`;
|
|
42
|
+
const read = extractFile(compiler, fileName, file.text);
|
|
43
|
+
return {
|
|
44
|
+
path: file.path, parseFailed: read.parseFailed, exports: read.exports,
|
|
45
|
+
specifiers: read.specifiers.map(one => { const to = resolveOne(one.specifier, fileName); return { specifier: one.specifier, kind: one.kind, line: one.line, to: to === null ? null : to.slice(ROOT.length + 1) }; }),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
process.stdout.write(`${JSON.stringify({ version: compiler.version, tsconfig: problem, facts })}\n`);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one reader of the code map (docs/plans/code-map.md §4, §6): the lead tools, MCP, chat, the page and the CLI all come
|
|
3
|
+
* through here, so they can't drift. It checks the project's switch and the remote's identity on every read. A first
|
|
4
|
+
* answer pins the current version; later pages name that version and a cursor, so a refresh between pages never skips
|
|
5
|
+
* or repeats entries. Everything it returns came from the repository and is labelled untrusted.
|
|
6
|
+
*/
|
|
7
|
+
import { type Refresh, type VersionRow } from "./code-map-store.js";
|
|
8
|
+
import { type CodeMap, type MapArea, type MapFile } from "./code-map-shape.js";
|
|
9
|
+
import { type Store } from "./store.js";
|
|
10
|
+
export declare const ANSWER_BYTES: number;
|
|
11
|
+
export type MapStateName = "not-available" | "off" | "making" | "current" | "stale" | "couldnt-check" | "too-large" | "remote-changed" | "empty" | "no-main";
|
|
12
|
+
export type MapStatus = {
|
|
13
|
+
state: MapStateName;
|
|
14
|
+
words: string;
|
|
15
|
+
version: VersionRow | null;
|
|
16
|
+
nextTry: string | null;
|
|
17
|
+
movedTo: string | null;
|
|
18
|
+
refresh: Refresh | null;
|
|
19
|
+
};
|
|
20
|
+
/** The project's map state, as every surface states it. `at` renders a time; tests and the CLI use the plain ISO. */
|
|
21
|
+
export declare function codeMapStatus(store: Store, repo: string, now: Date, at?: (iso: string) => string): MapStatus;
|
|
22
|
+
/** The verified map at a version of this project, or why not. Identity is checked on every read. */
|
|
23
|
+
export declare function openMap(store: Store, repo: string, version?: number): {
|
|
24
|
+
ok: true;
|
|
25
|
+
map: CodeMap;
|
|
26
|
+
version: VersionRow;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
state: "off" | "remote-changed" | "none" | "gone" | "unavailable";
|
|
30
|
+
words: string;
|
|
31
|
+
};
|
|
32
|
+
export type AreaRow = {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
files: number;
|
|
36
|
+
languages: string[];
|
|
37
|
+
tests: number;
|
|
38
|
+
uses: {
|
|
39
|
+
id: string;
|
|
40
|
+
count: number;
|
|
41
|
+
}[];
|
|
42
|
+
usedBy: {
|
|
43
|
+
id: string;
|
|
44
|
+
count: number;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
export declare const areaRow: (map: CodeMap, area: MapArea) => AreaRow;
|
|
48
|
+
export declare const areaRows: (map: CodeMap) => AreaRow[];
|
|
49
|
+
export type AreaView = {
|
|
50
|
+
area: AreaRow;
|
|
51
|
+
entryPoints: string[];
|
|
52
|
+
external: string[];
|
|
53
|
+
files: MapFile[];
|
|
54
|
+
};
|
|
55
|
+
export declare function areaView(map: CodeMap, id: string): AreaView | null;
|
|
56
|
+
export type FileView = {
|
|
57
|
+
file: MapFile;
|
|
58
|
+
imports: {
|
|
59
|
+
path: string;
|
|
60
|
+
kind: string;
|
|
61
|
+
line: number;
|
|
62
|
+
}[];
|
|
63
|
+
importedBy: {
|
|
64
|
+
path: string;
|
|
65
|
+
kind: string;
|
|
66
|
+
line: number;
|
|
67
|
+
}[];
|
|
68
|
+
};
|
|
69
|
+
/** Exact lookup in the stored version: a path is never resolved, joined or normalized. */
|
|
70
|
+
export declare function fileView(map: CodeMap, path: string): FileView | null;
|
|
71
|
+
export type SearchHit = {
|
|
72
|
+
kind: "file" | "name";
|
|
73
|
+
path: string;
|
|
74
|
+
name: string | null;
|
|
75
|
+
line: number | null;
|
|
76
|
+
};
|
|
77
|
+
export declare function searchMap(map: CodeMap, query: string, limit?: number): SearchHit[];
|
|
78
|
+
export type MapView = "overview" | "area" | "file" | "search";
|
|
79
|
+
export type MapQuestion = {
|
|
80
|
+
repo: string;
|
|
81
|
+
version?: number;
|
|
82
|
+
view: MapView;
|
|
83
|
+
id?: string;
|
|
84
|
+
query?: string;
|
|
85
|
+
cursor?: string;
|
|
86
|
+
};
|
|
87
|
+
export type MapAnswer = {
|
|
88
|
+
v: 1;
|
|
89
|
+
ok: true;
|
|
90
|
+
label: string;
|
|
91
|
+
repo: string;
|
|
92
|
+
version: number;
|
|
93
|
+
commit: string;
|
|
94
|
+
view: MapView;
|
|
95
|
+
header: Record<string, unknown>;
|
|
96
|
+
items: unknown[];
|
|
97
|
+
omitted: number;
|
|
98
|
+
cursor: string | null;
|
|
99
|
+
} | {
|
|
100
|
+
v: 1;
|
|
101
|
+
ok: false;
|
|
102
|
+
state: string;
|
|
103
|
+
message: string;
|
|
104
|
+
};
|
|
105
|
+
/** The labelled answer to one question, at most `maxBytes`. The first page pins the version it read. */
|
|
106
|
+
export declare function answerMap(store: Store, question: MapQuestion, maxBytes?: number): MapAnswer;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one reader of the code map (docs/plans/code-map.md §4, §6): the lead tools, MCP, chat, the page and the CLI all come
|
|
3
|
+
* through here, so they can't drift. It checks the project's switch and the remote's identity on every read. A first
|
|
4
|
+
* answer pins the current version; later pages name that version and a cursor, so a refresh between pages never skips
|
|
5
|
+
* or repeats entries. Everything it returns came from the repository and is labelled untrusted.
|
|
6
|
+
*/
|
|
7
|
+
import { codeMapOn, currentVersion, digestOfUrl, readRefresh, readVersionMap, versionRow } from "./code-map-store.js";
|
|
8
|
+
import { codeMapLabel } from "./code-map-shape.js";
|
|
9
|
+
import { REFRESH_EVERY_MS } from "./code-map-build.js";
|
|
10
|
+
import { remoteUrlProblem } from "./remote-main.js";
|
|
11
|
+
import { trustedRemoteIdentity } from "./store.js";
|
|
12
|
+
export const ANSWER_BYTES = 6 * 1024;
|
|
13
|
+
const short = (commit) => commit.slice(0, 7);
|
|
14
|
+
/** The project's map state, as every surface states it. `at` renders a time; tests and the CLI use the plain ISO. */
|
|
15
|
+
export function codeMapStatus(store, repo, now, at = iso => iso) {
|
|
16
|
+
const identity = trustedRemoteIdentity(store, repo);
|
|
17
|
+
const refresh = readRefresh(store, repo);
|
|
18
|
+
const base = { version: null, nextTry: null, movedTo: null, refresh };
|
|
19
|
+
if (identity === null || remoteUrlProblem(identity.url) !== null)
|
|
20
|
+
return { ...base, state: "not-available", words: "This project has no remote Toolroll can read, so it has no code map." };
|
|
21
|
+
if (!codeMapOn(store, repo))
|
|
22
|
+
return { ...base, state: "off", words: "The code map is off for this project." };
|
|
23
|
+
const nextTry = refresh === null ? null : refresh.retryAfter ?? (refresh.lastCheckedAt === null ? null : new Date(Date.parse(refresh.lastCheckedAt) + REFRESH_EVERY_MS).toISOString());
|
|
24
|
+
const version = currentVersion(store, repo);
|
|
25
|
+
const when = nextTry === null ? "" : ` Next try at ${at(nextTry)}.`;
|
|
26
|
+
if (version !== null && (version.remoteDigest !== digestOfUrl(identity.url) || version.remoteRecord !== identity.record))
|
|
27
|
+
return { ...base, nextTry, state: "remote-changed", words: "The project's remote changed. A new map is on its way." };
|
|
28
|
+
if (version === null) {
|
|
29
|
+
if (refresh?.seen.state === "no-main")
|
|
30
|
+
return { ...base, nextTry, state: "no-main", words: `The remote has no main branch yet.${when}` };
|
|
31
|
+
if (refresh?.seen.state === "too-large")
|
|
32
|
+
return { ...base, nextTry, state: "too-large", words: `${refresh.lastError ?? "This repository is too large"}.${when}` };
|
|
33
|
+
if (refresh?.lastError != null)
|
|
34
|
+
return { ...base, nextTry, state: "couldnt-check", words: `Couldn't check main: ${withoutPrefix(refresh.lastError)}${when}` };
|
|
35
|
+
const earlier = store.handle.prepare("SELECT 1 FROM code_map_version WHERE repo = ? LIMIT 1").get(repo);
|
|
36
|
+
if (earlier !== undefined)
|
|
37
|
+
return { ...base, nextTry, state: "remote-changed", words: "The project's remote changed. A new map is on its way." };
|
|
38
|
+
return { ...base, nextTry, state: "making", words: "Making the first map." };
|
|
39
|
+
}
|
|
40
|
+
const held = { ...base, version, nextTry };
|
|
41
|
+
if (refresh?.seen.state === "too-large")
|
|
42
|
+
return { ...held, state: "too-large", words: `${refresh.lastError ?? "Main is too large to map"}. Showing the last map.${when}` };
|
|
43
|
+
if (refresh?.lastError != null)
|
|
44
|
+
return { ...held, state: "couldnt-check", words: `Couldn't check main: ${withoutPrefix(refresh.lastError)}${when}` };
|
|
45
|
+
if (refresh?.seen.commit != null && refresh.seen.commit !== version.commit)
|
|
46
|
+
return { ...held, state: "stale", movedTo: refresh.seen.commit, words: `Main has moved to ${short(refresh.seen.commit)}.${nextTry === null ? "" : ` The next check is at ${at(nextTry)}.`}` };
|
|
47
|
+
if (refresh?.seen.state === "empty")
|
|
48
|
+
return { ...held, state: "empty", words: "This repository is empty." };
|
|
49
|
+
return { ...held, state: "current", words: `Main at ${short(version.commit)}.` };
|
|
50
|
+
}
|
|
51
|
+
const withoutPrefix = (why) => { const clean = why.replace(/^couldn't read main: /, ""); return /[.!]$/.test(clean) ? clean : `${clean}.`; };
|
|
52
|
+
/** The verified map at a version of this project, or why not. Identity is checked on every read. */
|
|
53
|
+
export function openMap(store, repo, version) {
|
|
54
|
+
if (!codeMapOn(store, repo))
|
|
55
|
+
return { ok: false, state: "off", words: "the code map is off for this project" };
|
|
56
|
+
const identity = trustedRemoteIdentity(store, repo);
|
|
57
|
+
const row = version === undefined ? currentVersion(store, repo) : versionRow(store, version);
|
|
58
|
+
if (row === null)
|
|
59
|
+
return version === undefined ? { ok: false, state: "none", words: "there is no code map for this project yet" } : { ok: false, state: "gone", words: "the map changed; start again" };
|
|
60
|
+
if (row.repo !== repo)
|
|
61
|
+
return { ok: false, state: "gone", words: "the map changed; start again" };
|
|
62
|
+
if (identity === null || row.remoteDigest !== digestOfUrl(identity.url) || row.remoteRecord !== identity.record)
|
|
63
|
+
return { ok: false, state: "remote-changed", words: "the project's remote changed" };
|
|
64
|
+
const map = readVersionMap(store, row.id);
|
|
65
|
+
if (map === null)
|
|
66
|
+
return { ok: false, state: "unavailable", words: "the saved map couldn't be verified" };
|
|
67
|
+
return { ok: true, map, version: row };
|
|
68
|
+
}
|
|
69
|
+
const mainLanguages = (area) => Object.entries(area.languages).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0)).slice(0, 3).map(([name]) => name);
|
|
70
|
+
const connections = (map, id) => ({
|
|
71
|
+
uses: map.areaEdges.filter(one => one.from === id).sort((a, b) => b.count - a.count).map(one => ({ id: one.to, count: one.count })),
|
|
72
|
+
usedBy: map.areaEdges.filter(one => one.to === id).sort((a, b) => b.count - a.count).map(one => ({ id: one.from, count: one.count })),
|
|
73
|
+
});
|
|
74
|
+
export const areaRow = (map, area) => ({ id: area.id, name: area.name, files: area.files, languages: mainLanguages(area), tests: area.tests, ...connections(map, area.id) });
|
|
75
|
+
export const areaRows = (map) => map.areas.map(area => areaRow(map, area));
|
|
76
|
+
export function areaView(map, id) {
|
|
77
|
+
const area = map.areas.find(one => one.id === id);
|
|
78
|
+
if (area === undefined)
|
|
79
|
+
return null;
|
|
80
|
+
return { area: areaRow(map, area), entryPoints: area.entryPoints, external: map.external.find(one => one.area === id)?.packages ?? [], files: map.files.filter(one => one.area === id) };
|
|
81
|
+
}
|
|
82
|
+
/** Exact lookup in the stored version: a path is never resolved, joined or normalized. */
|
|
83
|
+
export function fileView(map, path) {
|
|
84
|
+
const file = map.files.find(one => one.path === path);
|
|
85
|
+
if (file === undefined)
|
|
86
|
+
return null;
|
|
87
|
+
return { file, imports: map.edges.filter(one => one.from === path).map(one => ({ path: one.to, kind: one.kind, line: one.line })), importedBy: map.edges.filter(one => one.to === path).map(one => ({ path: one.from, kind: one.kind, line: one.line })) };
|
|
88
|
+
}
|
|
89
|
+
const wordsOf = (query) => query.toLowerCase().split(/\s+/).filter(Boolean).slice(0, 8);
|
|
90
|
+
export function searchMap(map, query, limit = 500) {
|
|
91
|
+
const words = wordsOf(query);
|
|
92
|
+
if (words.length === 0)
|
|
93
|
+
return [];
|
|
94
|
+
const hits = [];
|
|
95
|
+
for (const file of map.files) {
|
|
96
|
+
const lower = file.path.toLowerCase();
|
|
97
|
+
if (words.every(word => lower.includes(word)))
|
|
98
|
+
hits.push({ kind: "file", path: file.path, name: null, line: null });
|
|
99
|
+
for (const symbol of file.symbols) {
|
|
100
|
+
const name = symbol.name.toLowerCase();
|
|
101
|
+
if (words.every(word => name.includes(word) || lower.includes(word)) && words.some(word => name.includes(word)))
|
|
102
|
+
hits.push({ kind: "name", path: file.path, name: symbol.name, line: symbol.line });
|
|
103
|
+
}
|
|
104
|
+
if (hits.length >= limit)
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
return hits.slice(0, limit);
|
|
108
|
+
}
|
|
109
|
+
const refused = (state, message) => ({ v: 1, ok: false, state, message });
|
|
110
|
+
const thinFile = (file) => ({ path: file.path, bytes: file.bytes, lang: file.lang, test: file.test, names: file.symbols.slice(0, 40).map(one => one.name), more: Math.max(0, file.symbols.length - 40) });
|
|
111
|
+
/** The labelled answer to one question, at most `maxBytes`. The first page pins the version it read. */
|
|
112
|
+
export function answerMap(store, question, maxBytes = ANSWER_BYTES) {
|
|
113
|
+
let offset = 0, pinned = question.version;
|
|
114
|
+
if (question.cursor !== undefined) {
|
|
115
|
+
const read = /^(\d+)\.(\d+)$/.exec(question.cursor);
|
|
116
|
+
if (read === null || (pinned !== undefined && Number(read[1]) !== pinned))
|
|
117
|
+
return refused("gone", "the map changed; start again");
|
|
118
|
+
pinned = Number(read[1]);
|
|
119
|
+
offset = Number(read[2]);
|
|
120
|
+
}
|
|
121
|
+
const opened = openMap(store, question.repo, pinned);
|
|
122
|
+
if (!opened.ok)
|
|
123
|
+
return refused(opened.state, opened.words);
|
|
124
|
+
const { map, version } = opened;
|
|
125
|
+
let header;
|
|
126
|
+
let items;
|
|
127
|
+
if (question.view === "overview") {
|
|
128
|
+
header = { counts: map.counts, packages: map.packages.map(one => ({ root: one.root, name: one.name, entryPoints: one.entryPoints.slice(0, 8) })).slice(0, 8), warnings: map.warnings };
|
|
129
|
+
items = areaRows(map).map(one => ({ ...one, uses: one.uses.slice(0, 8), usedBy: one.usedBy.slice(0, 8) }));
|
|
130
|
+
}
|
|
131
|
+
else if (question.view === "area") {
|
|
132
|
+
const view = question.id === undefined ? null : areaView(map, question.id);
|
|
133
|
+
if (view === null)
|
|
134
|
+
return refused("not-found", "no area with that id in this version");
|
|
135
|
+
header = { area: { ...view.area, uses: view.area.uses.slice(0, 12), usedBy: view.area.usedBy.slice(0, 12) }, entryPoints: view.entryPoints.slice(0, 12), external: view.external.slice(0, 30) };
|
|
136
|
+
items = view.files.map(thinFile);
|
|
137
|
+
}
|
|
138
|
+
else if (question.view === "file") {
|
|
139
|
+
const view = question.id === undefined ? null : fileView(map, question.id);
|
|
140
|
+
if (view === null)
|
|
141
|
+
return refused("not-found", "no file with that path in this version");
|
|
142
|
+
header = { path: view.file.path, area: view.file.area, bytes: view.file.bytes, lang: view.file.lang, state: view.file.state, test: view.file.test };
|
|
143
|
+
items = [...view.file.symbols.map(one => ({ exported: one.name, kind: one.kind, line: one.line })), ...view.imports.map(one => ({ imports: one.path, kind: one.kind, line: one.line })), ...view.importedBy.map(one => ({ importedBy: one.path, kind: one.kind, line: one.line }))];
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (question.query === undefined || wordsOf(question.query).length === 0)
|
|
147
|
+
return refused("usage", "say what to search for");
|
|
148
|
+
header = { query: question.query.slice(0, 200) };
|
|
149
|
+
items = searchMap(map, question.query);
|
|
150
|
+
}
|
|
151
|
+
const answer = { v: 1, ok: true, label: codeMapLabel(version.commit), repo: question.repo, version: version.id, commit: version.commit, view: question.view, header, items: [], omitted: 0, cursor: null };
|
|
152
|
+
const size = () => Buffer.byteLength(JSON.stringify(answer));
|
|
153
|
+
// Room for the cursor it may need, and never more than the limit.
|
|
154
|
+
const budget = maxBytes - 48;
|
|
155
|
+
let at = offset;
|
|
156
|
+
for (; at < items.length; at++) {
|
|
157
|
+
answer.items.push(items[at]);
|
|
158
|
+
if (size() > budget) {
|
|
159
|
+
answer.items.pop();
|
|
160
|
+
// One item wider than a whole answer is skipped and counted, so paging always moves on.
|
|
161
|
+
if (answer.items.length === 0) {
|
|
162
|
+
answer.omitted++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (at < items.length)
|
|
169
|
+
answer.cursor = `${version.id}.${at}`;
|
|
170
|
+
return answer;
|
|
171
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Store } from "./store.js";
|
|
2
|
+
export declare const ENOUGH_FAMILIES = 10;
|
|
3
|
+
export type Arm = "map" | "holdout";
|
|
4
|
+
export type ArmReport = {
|
|
5
|
+
families: number;
|
|
6
|
+
notEligible: number;
|
|
7
|
+
complete: number;
|
|
8
|
+
stillRunning: number;
|
|
9
|
+
inWindow: number;
|
|
10
|
+
deliveredDifferently: number;
|
|
11
|
+
/** Per complete family; null with no known value. */
|
|
12
|
+
personRevisions: number | null;
|
|
13
|
+
firstCheckPassed: {
|
|
14
|
+
passed: number;
|
|
15
|
+
known: number;
|
|
16
|
+
};
|
|
17
|
+
apiUsd: number | null;
|
|
18
|
+
subscriptionTokens: number | null;
|
|
19
|
+
turns: number | null;
|
|
20
|
+
records: Record<string, number>;
|
|
21
|
+
};
|
|
22
|
+
export type CodeMapReport = {
|
|
23
|
+
v: 1;
|
|
24
|
+
repo: string;
|
|
25
|
+
asOf: string;
|
|
26
|
+
enrollUntil: string | null;
|
|
27
|
+
enough: boolean;
|
|
28
|
+
arms: Record<Arm, ArmReport>;
|
|
29
|
+
};
|
|
30
|
+
export declare function codeMapReport(store: Store, repo: string, now: Date): CodeMapReport;
|
|
31
|
+
/** The rows every surface shows: a label and each arm's value. Below 10 complete families in an arm there is no comparison. */
|
|
32
|
+
export declare function reportRows(report: CodeMapReport): {
|
|
33
|
+
label: string;
|
|
34
|
+
map: string;
|
|
35
|
+
holdout: string;
|
|
36
|
+
}[];
|
|
37
|
+
export declare const NOT_ENOUGH = "Not enough results yet.";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds with and without the map (docs/plans/code-map.md §7), by assigned arm. Run usage is overwritten in place, so no
|
|
3
|
+
* instant inside a run can be rebuilt: a run counts when it was admitted before its family's cutoff and has finished,
|
|
4
|
+
* and then its whole usage counts. Measures come from those runs, never route_record's totals, which grow until `closed`.
|
|
5
|
+
* People read this; nothing switches on it. Unknown is null, shown as —, never 0.
|
|
6
|
+
*/
|
|
7
|
+
import { firstCheckPassedOf, personRevisionsOf } from "./route-results.js";
|
|
8
|
+
import { runAccounting } from "./store.js";
|
|
9
|
+
export const ENOUGH_FAMILIES = 10;
|
|
10
|
+
/** The family's counted runs: admitted before the cutoff. A run after it never counts. */
|
|
11
|
+
function countedRuns(store, refs, cutoff) {
|
|
12
|
+
return refs.flatMap(ref => store.runsFor(ref)).filter(run => (run.role === "builder" || run.role === "repair") && Date.parse(run.startedAt) < cutoff).sort((a, b) => a.id - b.id);
|
|
13
|
+
}
|
|
14
|
+
function readFamily(store, row, now) {
|
|
15
|
+
const root = String(row["root_task"]), arm = String(row["arm"]), cutoff = Date.parse(String(row["observe_until"]));
|
|
16
|
+
const family = store.taskFamilyOf(root, null, true);
|
|
17
|
+
const versions = family !== null && family.problem === null ? family.versions : [];
|
|
18
|
+
const refs = versions.length > 0 ? versions.map(one => one.refId) : [store.refFor("built-in", root).id];
|
|
19
|
+
const runs = countedRuns(store, refs, cutoff);
|
|
20
|
+
const unfinished = runs.some(run => run.finishedAt === null);
|
|
21
|
+
const status = unfinished ? "still-running" : now.getTime() < cutoff ? "in-window" : "complete";
|
|
22
|
+
const ids = JSON.stringify(runs.map(run => run.id));
|
|
23
|
+
const cohort = store.handle.prepare("SELECT ineligible FROM run_cohort WHERE run IN (SELECT value FROM json_each(?)) ORDER BY run LIMIT 1").get(ids);
|
|
24
|
+
const eligible = cohort !== undefined && cohort["ineligible"] === null;
|
|
25
|
+
let apiMicrousd = 0, subscriptionTokens = 0, turns = 0;
|
|
26
|
+
for (const run of runs) {
|
|
27
|
+
if (run.providerStartedAt === null)
|
|
28
|
+
continue;
|
|
29
|
+
const accounting = runAccounting(store, run.id);
|
|
30
|
+
if (accounting === null || accounting.amount.kind === "unknown") {
|
|
31
|
+
apiMicrousd = null;
|
|
32
|
+
subscriptionTokens = null;
|
|
33
|
+
}
|
|
34
|
+
else if (accounting.amount.kind === "api") {
|
|
35
|
+
if (apiMicrousd !== null)
|
|
36
|
+
apiMicrousd += accounting.amount.microusd;
|
|
37
|
+
}
|
|
38
|
+
else if (subscriptionTokens !== null)
|
|
39
|
+
subscriptionTokens += (accounting.usage.in ?? 0) + (accounting.usage.out ?? 0);
|
|
40
|
+
if (run.agentTurns == null)
|
|
41
|
+
turns = null;
|
|
42
|
+
else if (turns !== null)
|
|
43
|
+
turns += run.agentTurns;
|
|
44
|
+
}
|
|
45
|
+
// A revision counts when it was filed before the cutoff; the root isn't one.
|
|
46
|
+
const filed = versions.filter(one => one.id !== root && Date.parse(one.createdAt) < cutoff).map(one => one.refId);
|
|
47
|
+
const states = store.handle.prepare("SELECT state FROM code_map_snapshot WHERE run IN (SELECT value FROM json_each(?))").all(ids).map(one => String(one["state"]));
|
|
48
|
+
const record = versions.length === 0 ? undefined : store.handle.prepare("SELECT state FROM route_record WHERE task_ref = ?").get(family.root.refId);
|
|
49
|
+
return { root, arm, eligible, status, revisions: personRevisionsOf(store, filed), firstCheck: firstCheckPassedOf(store, runs), apiMicrousd, subscriptionTokens, turns,
|
|
50
|
+
differently: states.some(state => state !== arm), record: record === undefined ? null : String(record["state"]) };
|
|
51
|
+
}
|
|
52
|
+
const mean = (values) => { const known = values.filter((one) => one !== null); return known.length === 0 ? null : known.reduce((a, b) => a + b, 0) / known.length; };
|
|
53
|
+
function armReport(all) {
|
|
54
|
+
const eligible = all.filter(one => one.eligible);
|
|
55
|
+
const complete = eligible.filter(one => one.status === "complete");
|
|
56
|
+
const checks = complete.filter(one => one.firstCheck !== null);
|
|
57
|
+
const api = mean(complete.map(one => one.apiMicrousd));
|
|
58
|
+
const records = {};
|
|
59
|
+
for (const one of eligible)
|
|
60
|
+
if (one.record !== null)
|
|
61
|
+
records[one.record] = (records[one.record] ?? 0) + 1;
|
|
62
|
+
return {
|
|
63
|
+
families: all.length, notEligible: all.length - eligible.length, complete: complete.length, stillRunning: eligible.filter(one => one.status === "still-running").length, inWindow: eligible.filter(one => one.status === "in-window").length,
|
|
64
|
+
deliveredDifferently: eligible.filter(one => one.differently).length, personRevisions: mean(complete.map(one => one.revisions)),
|
|
65
|
+
firstCheckPassed: { passed: checks.filter(one => one.firstCheck === true).length, known: checks.length }, apiUsd: api === null ? null : api / 1_000_000,
|
|
66
|
+
subscriptionTokens: mean(complete.map(one => one.subscriptionTokens)), turns: mean(complete.map(one => one.turns)), records,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function codeMapReport(store, repo, now) {
|
|
70
|
+
const rows = store.handle.prepare("SELECT root_task, arm, observe_until FROM code_map_family WHERE repo = ? ORDER BY root_task").all(repo);
|
|
71
|
+
const reads = rows.flatMap(row => readFamily(store, row, now) ?? []);
|
|
72
|
+
const arms = { map: armReport(reads.filter(one => one.arm === "map")), holdout: armReport(reads.filter(one => one.arm === "holdout")) };
|
|
73
|
+
const until = store.handle.prepare("SELECT code_map_enroll_until AS at FROM project WHERE path = ?").get(repo)?.["at"];
|
|
74
|
+
return { v: 1, repo, asOf: now.toISOString(), enrollUntil: typeof until === "string" ? until : null, enough: arms.map.complete >= ENOUGH_FAMILIES && arms.holdout.complete >= ENOUGH_FAMILIES, arms };
|
|
75
|
+
}
|
|
76
|
+
const number = (value, digits = 1) => (value === null ? "—" : value.toFixed(digits));
|
|
77
|
+
/** The rows every surface shows: a label and each arm's value. Below 10 complete families in an arm there is no comparison. */
|
|
78
|
+
export function reportRows(report) {
|
|
79
|
+
const both = (label, read) => ({ label, map: read(report.arms.map), holdout: read(report.arms.holdout) });
|
|
80
|
+
const counts = [
|
|
81
|
+
both("Builds enrolled", arm => String(arm.families)), both("Compared", arm => String(arm.complete)), both("Still running", arm => String(arm.stillRunning)),
|
|
82
|
+
both("Still in their window", arm => String(arm.inWindow)), both("Not eligible", arm => String(arm.notEligible)), both("Delivered differently", arm => String(arm.deliveredDifferently)),
|
|
83
|
+
];
|
|
84
|
+
if (!report.enough)
|
|
85
|
+
return counts;
|
|
86
|
+
return [...counts,
|
|
87
|
+
both("Revisions by a person, per build", arm => number(arm.personRevisions, 2)),
|
|
88
|
+
both("First check passed", arm => (arm.firstCheckPassed.known === 0 ? "—" : `${arm.firstCheckPassed.passed} of ${arm.firstCheckPassed.known}`)),
|
|
89
|
+
both("API dollars, per build", arm => (arm.apiUsd === null ? "—" : `$${arm.apiUsd.toFixed(2)}`)),
|
|
90
|
+
both("Subscription tokens, per build", arm => number(arm.subscriptionTokens, 0)),
|
|
91
|
+
both("Turns, per build", arm => number(arm.turns, 1)),
|
|
92
|
+
both(`Result records as of ${report.asOf.slice(0, 10)}`, arm => Object.entries(arm.records).sort().map(([state, count]) => `${count} ${state}`).join(", ") || "—"),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
export const NOT_ENOUGH = "Not enough results yet.";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated code map's shape and the pure work of making it from a tree and parsed facts (docs/plans/code-map.md §1).
|
|
3
|
+
* No file text is stored: only names, lines, sizes and edges. Every name here came from the repository and is untrusted.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CODE_MAP_VERSION = 1;
|
|
6
|
+
export declare const EXTRACTOR_TAG = "map-v1";
|
|
7
|
+
/** On every excerpt, answer and file. `<commit>` is the full commit the map was made from. */
|
|
8
|
+
export declare const codeMapLabel: (commit: string) => string;
|
|
9
|
+
export declare const LIMITS: {
|
|
10
|
+
readonly treeEntries: 50000;
|
|
11
|
+
readonly pathBytes: 1024;
|
|
12
|
+
readonly pathDepth: 32;
|
|
13
|
+
readonly fileBytes: number;
|
|
14
|
+
readonly filesRead: 20000;
|
|
15
|
+
readonly bytesRead: number;
|
|
16
|
+
readonly symbolsPerFile: 200;
|
|
17
|
+
readonly symbols: 200000;
|
|
18
|
+
readonly symbolChars: 200;
|
|
19
|
+
readonly edges: 300000;
|
|
20
|
+
readonly storedBytes: number;
|
|
21
|
+
readonly areas: 400;
|
|
22
|
+
readonly groupAbove: 40;
|
|
23
|
+
readonly groupAtLeast: 3;
|
|
24
|
+
readonly manifestBytes: number;
|
|
25
|
+
readonly parserOutput: number;
|
|
26
|
+
readonly parserHeapMb: 768;
|
|
27
|
+
readonly parserMs: 120000;
|
|
28
|
+
};
|
|
29
|
+
export type Lang = "ts" | "js" | "python" | "go" | "rust" | "java" | "kotlin" | "swift" | "ruby" | "php" | "c" | "cpp" | "csharp" | "shell" | "sql" | "docs" | "config" | "binary" | "other";
|
|
30
|
+
export type FileState = "read" | "path-only" | "too-large" | "parse-error" | "withheld";
|
|
31
|
+
export type MapSymbol = {
|
|
32
|
+
name: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
line: number;
|
|
35
|
+
};
|
|
36
|
+
export type MapFile = {
|
|
37
|
+
path: string;
|
|
38
|
+
area: string;
|
|
39
|
+
kind: "file" | "exec" | "link" | "submodule";
|
|
40
|
+
bytes: number;
|
|
41
|
+
lang: Lang;
|
|
42
|
+
state: FileState;
|
|
43
|
+
test: string | null;
|
|
44
|
+
symbols: MapSymbol[];
|
|
45
|
+
};
|
|
46
|
+
export type MapEdge = {
|
|
47
|
+
from: string;
|
|
48
|
+
to: string;
|
|
49
|
+
kind: "imports" | "re-exports" | "dynamic-import";
|
|
50
|
+
line: number;
|
|
51
|
+
};
|
|
52
|
+
export type MapArea = {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
root: string;
|
|
56
|
+
package: string;
|
|
57
|
+
files: number;
|
|
58
|
+
bytes: number;
|
|
59
|
+
languages: Partial<Record<Lang, number>>;
|
|
60
|
+
tests: number;
|
|
61
|
+
entryPoints: string[];
|
|
62
|
+
};
|
|
63
|
+
export type MapPackage = {
|
|
64
|
+
root: string;
|
|
65
|
+
name: string | null;
|
|
66
|
+
manifest: string | null;
|
|
67
|
+
entryPoints: string[];
|
|
68
|
+
};
|
|
69
|
+
export type MapCounts = {
|
|
70
|
+
entries: number;
|
|
71
|
+
files: number;
|
|
72
|
+
read: number;
|
|
73
|
+
pathOnly: number;
|
|
74
|
+
tooLarge: number;
|
|
75
|
+
parseErrors: number;
|
|
76
|
+
withheld: number;
|
|
77
|
+
links: number;
|
|
78
|
+
submodules: number;
|
|
79
|
+
vendored: number;
|
|
80
|
+
};
|
|
81
|
+
export type MapWarning = "symbols-truncated" | "edges-truncated" | "read-limit" | "areas-folded" | "tsconfig-extends" | "tsconfig-unreadable";
|
|
82
|
+
export type CodeMapManifest = {
|
|
83
|
+
v: 1;
|
|
84
|
+
label: string;
|
|
85
|
+
repo: string;
|
|
86
|
+
commit: string;
|
|
87
|
+
remote: {
|
|
88
|
+
urlDigest: string;
|
|
89
|
+
record: number;
|
|
90
|
+
};
|
|
91
|
+
builtAt: string;
|
|
92
|
+
extractor: string;
|
|
93
|
+
counts: MapCounts;
|
|
94
|
+
packages: MapPackage[];
|
|
95
|
+
areas: MapArea[];
|
|
96
|
+
files: MapFile[];
|
|
97
|
+
areaEdges: {
|
|
98
|
+
from: string;
|
|
99
|
+
to: string;
|
|
100
|
+
count: number;
|
|
101
|
+
}[];
|
|
102
|
+
external: {
|
|
103
|
+
area: string;
|
|
104
|
+
packages: string[];
|
|
105
|
+
}[];
|
|
106
|
+
vendored: {
|
|
107
|
+
path: string;
|
|
108
|
+
files: number;
|
|
109
|
+
bytes: number;
|
|
110
|
+
}[];
|
|
111
|
+
warnings: MapWarning[];
|
|
112
|
+
};
|
|
113
|
+
export type CodeMapGraph = {
|
|
114
|
+
edges: MapEdge[];
|
|
115
|
+
};
|
|
116
|
+
export type CodeMap = CodeMapManifest & CodeMapGraph;
|
|
117
|
+
export declare const CODE_FILE: RegExp;
|
|
118
|
+
export declare const isParsedLanguage: (path: string) => boolean;
|
|
119
|
+
/** The vendored or built folder a path sits under, as a path; null when it doesn't. */
|
|
120
|
+
export declare function vendoredRoot(path: string): string | null;
|
|
121
|
+
export declare function languageOf(path: string): Lang;
|
|
122
|
+
/** A name with control characters, bidirectional controls or invalid UTF-8 (read as U+FFFD) is withheld. Secrets are
|
|
123
|
+
* scanned over the whole list at once (`secretLines`). */
|
|
124
|
+
export declare const unsafeName: (name: string) => boolean;
|
|
125
|
+
export type TreeEntry = {
|
|
126
|
+
path: string;
|
|
127
|
+
mode: string;
|
|
128
|
+
bytes: number;
|
|
129
|
+
};
|
|
130
|
+
export type FileFacts = {
|
|
131
|
+
exports: MapSymbol[];
|
|
132
|
+
imports: {
|
|
133
|
+
to: string;
|
|
134
|
+
kind: MapEdge["kind"];
|
|
135
|
+
line: number;
|
|
136
|
+
}[];
|
|
137
|
+
external: string[];
|
|
138
|
+
parseFailed: boolean;
|
|
139
|
+
};
|
|
140
|
+
export type BuildInput = {
|
|
141
|
+
repo: string;
|
|
142
|
+
commit: string;
|
|
143
|
+
remote: {
|
|
144
|
+
urlDigest: string;
|
|
145
|
+
record: number;
|
|
146
|
+
};
|
|
147
|
+
builtAt: string;
|
|
148
|
+
extractor: string;
|
|
149
|
+
/** Every tree entry whose name passed, vendored ones included. */
|
|
150
|
+
entries: readonly TreeEntry[];
|
|
151
|
+
/** Tree entries in all, withheld ones included. */
|
|
152
|
+
entryCount: number;
|
|
153
|
+
withheld: number;
|
|
154
|
+
/** path → what was parsed; a path missing here wasn't read. */
|
|
155
|
+
facts: ReadonlyMap<string, FileFacts>;
|
|
156
|
+
/** path → why a TS/JS file wasn't read. */
|
|
157
|
+
unread: ReadonlyMap<string, "too-large" | "path-only">;
|
|
158
|
+
/** package.json files' read values, by path. */
|
|
159
|
+
manifests: ReadonlyMap<string, {
|
|
160
|
+
name: string | null;
|
|
161
|
+
entries: string[];
|
|
162
|
+
}>;
|
|
163
|
+
warnings: readonly MapWarning[];
|
|
164
|
+
};
|
|
165
|
+
export declare function buildCodeMap(input: BuildInput): CodeMap;
|
|
166
|
+
/** The bare package a specifier names (`@scope/x/y` → `@scope/x`); null for a relative, absolute, built-in or `#` one. */
|
|
167
|
+
export declare function externalPackage(specifier: string): string | null;
|
|
168
|
+
/** package.json's string `main`, `module`, `bin` and `exports` values; everything else is ignored. */
|
|
169
|
+
export declare function manifestEntries(text: string): {
|
|
170
|
+
name: string | null;
|
|
171
|
+
entries: string[];
|
|
172
|
+
} | null;
|