@illuminis/comprism 0.1.5 → 0.1.6
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/out/agent/command.js +4 -4
- package/out/agent/session.d.ts +6 -1
- package/out/agent/session.js +17 -8
- package/out/commands/agents.js +3 -3
- package/out/commands/ask.js +8 -8
- package/out/commands/codemap.d.ts +1 -1
- package/out/commands/codemap.js +3 -3
- package/out/commands/commands-thin.js +4 -4
- package/out/commands/config.js +1 -1
- package/out/commands/cost.js +4 -4
- package/out/commands/hooks.js +1 -1
- package/out/commands/install.d.ts +1 -1
- package/out/commands/install.js +3 -2
- package/out/commands/instructions.js +1 -1
- package/out/commands/integrations.js +4 -4
- package/out/commands/keys.js +6 -6
- package/out/commands/login.js +37 -21
- package/out/commands/permissions.js +2 -2
- package/out/commands/plugins.js +3 -3
- package/out/commands/privacy.js +2 -2
- package/out/commands/repl.js +107 -83
- package/out/commands/review.js +6 -6
- package/out/commands/settings.js +30 -123
- package/out/commands/skills.js +2 -2
- package/out/commands/unattended.js +6 -6
- package/out/commands/update.js +1 -1
- package/out/commands/welcome.js +20 -29
- package/out/commands/worktrees.d.ts +1 -1
- package/out/graph/sync.d.ts +1 -1
- package/out/graph/sync.js +6 -6
- package/out/lib/attach.js +4 -3
- package/out/lib/commandlist.d.ts +1 -1
- package/out/lib/commandlist.js +2 -2
- package/out/lib/config.d.ts +3 -2
- package/out/lib/config.js +7 -8
- package/out/lib/connection.js +1 -1
- package/out/lib/gateway.d.ts +5 -5
- package/out/lib/machine.js +1 -1
- package/out/lib/project-ops.d.ts +1 -1
- package/out/lib/project-ops.js +4 -4
- package/out/lib/queue.js +1 -1
- package/out/lib/readiness.d.ts +3 -30
- package/out/lib/readiness.js +13 -82
- package/out/lib/servicecommand.d.ts +14 -0
- package/out/lib/servicecommand.js +74 -0
- package/out/lib/sessions.js +3 -3
- package/out/lib/ui.d.ts +9 -6
- package/out/lib/ui.js +20 -26
- package/out/lib/voice.js +1 -1
- package/out/lib/words.d.ts +20 -0
- package/out/lib/words.js +27 -0
- package/out/machine/codemap/build.d.ts +45 -0
- package/out/machine/codemap/build.js +91 -0
- package/out/machine/codemap/facts.d.ts +47 -0
- package/out/machine/codemap/facts.js +12 -0
- package/out/machine/codemap/files.d.ts +45 -0
- package/out/machine/codemap/files.js +207 -0
- package/out/machine/codemap/read-locales.d.ts +29 -0
- package/out/machine/codemap/read-locales.js +246 -0
- package/out/machine/codemap/read-python.d.ts +11 -0
- package/out/machine/codemap/read-python.js +116 -0
- package/out/machine/codemap/read-typescript.d.ts +16 -0
- package/out/machine/codemap/read-typescript.js +292 -0
- package/out/machine/executor/browser.d.ts +14 -0
- package/out/machine/executor/browser.js +270 -0
- package/out/machine/executor/diagnostics.d.ts +2 -0
- package/out/machine/executor/diagnostics.js +181 -0
- package/out/machine/executor/documents.d.ts +40 -0
- package/out/machine/executor/documents.js +170 -0
- package/out/machine/executor/files.d.ts +2 -0
- package/out/machine/executor/files.js +590 -0
- package/out/machine/executor/git.d.ts +48 -0
- package/out/machine/executor/git.js +145 -0
- package/out/machine/executor/hooks.d.ts +51 -0
- package/out/machine/executor/hooks.js +154 -0
- package/out/machine/executor/index.d.ts +45 -0
- package/out/machine/executor/index.js +367 -0
- package/out/machine/executor/notebook.d.ts +2 -0
- package/out/machine/executor/notebook.js +147 -0
- package/out/machine/executor/paths.d.ts +20 -0
- package/out/machine/executor/paths.js +154 -0
- package/out/machine/executor/sandbox.d.ts +40 -0
- package/out/machine/executor/sandbox.js +299 -0
- package/out/machine/executor/shell.d.ts +86 -0
- package/out/machine/executor/shell.js +582 -0
- package/out/machine/executor/toolservers.d.ts +20 -0
- package/out/machine/executor/toolservers.js +189 -0
- package/out/machine/executor/worktree.d.ts +9 -0
- package/out/machine/executor/worktree.js +119 -0
- package/out/machine/folder/project.d.ts +28 -0
- package/out/machine/folder/project.js +114 -0
- package/out/machine/runtime/home.d.ts +2 -0
- package/out/machine/runtime/home.js +48 -0
- package/out/machine/runtime/needs.d.ts +30 -0
- package/out/machine/runtime/needs.js +89 -0
- package/out/machine/runtime/self.d.ts +23 -0
- package/out/machine/runtime/self.js +124 -0
- package/out/providers/index.js +2 -1
- package/out/thin.js +11 -10
- package/package.json +4 -4
package/out/lib/sessions.js
CHANGED
|
@@ -51,7 +51,7 @@ const gateway_1 = require("./gateway");
|
|
|
51
51
|
const out = (s = '') => process.stdout.write(s + '\n');
|
|
52
52
|
/** Ask the service one sessions question, with where this machine is. */
|
|
53
53
|
async function sessions(op, extra = {}, folder) {
|
|
54
|
-
const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('
|
|
54
|
+
const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('../machine/folder/project')));
|
|
55
55
|
const { currentWorkspace } = await Promise.resolve().then(() => __importStar(require('../commands/settings')));
|
|
56
56
|
const facts = resolveProject(folder ?? currentWorkspace().path, []);
|
|
57
57
|
return (0, gateway_1.ask)({
|
|
@@ -69,9 +69,9 @@ async function sessions(op, extra = {}, folder) {
|
|
|
69
69
|
/** Why a sessions question got no answer, in the service's words when it has them. */
|
|
70
70
|
function failure(reply) {
|
|
71
71
|
if (reply.served === false) {
|
|
72
|
-
return 'Your
|
|
72
|
+
return 'Your organization is running a build without sessions yet.';
|
|
73
73
|
}
|
|
74
|
-
return reply.message || 'Your
|
|
74
|
+
return reply.message || 'Your organization could not be reached.';
|
|
75
75
|
}
|
|
76
76
|
/** The exit number for a sessions question that got no answer (Appendix B). */
|
|
77
77
|
function exitFor(reply) {
|
package/out/lib/ui.d.ts
CHANGED
|
@@ -36,13 +36,16 @@ export declare const c: {
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function spectrumAt(index: number, glyph: string): string;
|
|
38
38
|
/**
|
|
39
|
-
* A small picture,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* A small picture, one pixel to a character cell, each pixel a colored space.
|
|
40
|
+
*
|
|
41
|
+
* A cell's background is painted edge to edge by every terminal, so the
|
|
42
|
+
* picture has no seams. Half block glyphs pack two pixels into a cell but
|
|
43
|
+
* leave thin gaps and offsets in some fonts, which made the mark look broken
|
|
44
|
+
* in the macOS Terminal (owner, 28 September 2026). Each row of `pixels` is
|
|
45
|
+
* six hex digits per pixel, `......` for a clear one. Returns nothing without
|
|
46
|
+
* color, because the picture is only color.
|
|
44
47
|
*/
|
|
45
|
-
export declare function
|
|
48
|
+
export declare function cellArt(pixels: string[]): string[];
|
|
46
49
|
export declare function prismRule(w?: number): string;
|
|
47
50
|
export declare function banner(subtitle?: string): string;
|
|
48
51
|
export declare function money(v: number | null): string;
|
package/out/lib/ui.js
CHANGED
|
@@ -19,7 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.LiveIndicator = exports.c = void 0;
|
|
20
20
|
exports.width = width;
|
|
21
21
|
exports.spectrumAt = spectrumAt;
|
|
22
|
-
exports.
|
|
22
|
+
exports.cellArt = cellArt;
|
|
23
23
|
exports.prismRule = prismRule;
|
|
24
24
|
exports.banner = banner;
|
|
25
25
|
exports.money = money;
|
|
@@ -88,37 +88,31 @@ function spectrumAt(index, glyph) {
|
|
|
88
88
|
return `\x1b[38;2;${r};${g};${b}m${glyph}\x1b[0m`;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* A small picture,
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* A small picture, one pixel to a character cell, each pixel a colored space.
|
|
92
|
+
*
|
|
93
|
+
* A cell's background is painted edge to edge by every terminal, so the
|
|
94
|
+
* picture has no seams. Half block glyphs pack two pixels into a cell but
|
|
95
|
+
* leave thin gaps and offsets in some fonts, which made the mark look broken
|
|
96
|
+
* in the macOS Terminal (owner, 28 September 2026). Each row of `pixels` is
|
|
97
|
+
* six hex digits per pixel, `......` for a clear one. Returns nothing without
|
|
98
|
+
* color, because the picture is only color.
|
|
96
99
|
*/
|
|
97
|
-
function
|
|
100
|
+
function cellArt(pixels) {
|
|
98
101
|
if (!useColorNow())
|
|
99
102
|
return [];
|
|
100
|
-
|
|
101
|
-
const hex = row?.slice(x * 6, x * 6 + 6);
|
|
102
|
-
return !hex || hex === '......' ? null
|
|
103
|
-
: [0, 2, 4].map((k) => parseInt(hex.slice(k, k + 2), 16)).join(';');
|
|
104
|
-
};
|
|
105
|
-
const lines = [];
|
|
106
|
-
for (let y = 0; y < pixels.length; y += 2) {
|
|
103
|
+
return pixels.map((row) => {
|
|
107
104
|
let line = '';
|
|
108
|
-
for (let x = 0; x <
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
line += `\x1b[38;2;${top}m\x1b[48;2;${bottom}m▀\x1b[0m`;
|
|
112
|
-
else if (top)
|
|
113
|
-
line += `\x1b[38;2;${top}m▀\x1b[0m`;
|
|
114
|
-
else if (bottom)
|
|
115
|
-
line += `\x1b[38;2;${bottom}m▄\x1b[0m`;
|
|
116
|
-
else
|
|
105
|
+
for (let x = 0; x < row.length / 6; x++) {
|
|
106
|
+
const hex = row.slice(x * 6, x * 6 + 6);
|
|
107
|
+
if (hex === '......') {
|
|
117
108
|
line += ' ';
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const rgb = [0, 2, 4].map((k) => parseInt(hex.slice(k, k + 2), 16)).join(';');
|
|
112
|
+
line += `\x1b[48;2;${rgb}m \x1b[0m`;
|
|
118
113
|
}
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
return lines;
|
|
114
|
+
return line;
|
|
115
|
+
});
|
|
122
116
|
}
|
|
123
117
|
function prismRule(w = width()) {
|
|
124
118
|
if (!useColorNow())
|
package/out/lib/voice.js
CHANGED
|
@@ -364,7 +364,7 @@ async function transcribe(baseUrl, credential, file) {
|
|
|
364
364
|
const granted = await (0, gateway_1.ask)({ intent: 'transcribe', prompt: '' });
|
|
365
365
|
const pass = granted.ok && granted.served !== false ? granted.upload : null;
|
|
366
366
|
if (granted.ok && granted.served !== false && !pass) {
|
|
367
|
-
throw new Error(granted.message || 'your
|
|
367
|
+
throw new Error(granted.message || 'your organization would not allow that recording');
|
|
368
368
|
}
|
|
369
369
|
const where = pass?.upload_path || '/api/v1/agent/transcribe';
|
|
370
370
|
const res = await fetch(`${baseUrl.replace(/\/+$/, '')}${where}${pass?.ticket ? `?ticket=${encodeURIComponent(pass.ticket)}` : ''}`, { method: 'POST', headers: { Authorization: `Bearer ${credential}` }, body: form });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The few words the published command line tool says on its own, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Everything about the work arrives from the service already worded (rule 17).
|
|
5
|
+
* What is left here is what the tool must say when the service, a file or the
|
|
6
|
+
* operating system gives it nothing to say: a refused address, a file it cannot
|
|
7
|
+
* read, a call that failed without a reason. Same shape as the editor's
|
|
8
|
+
* `i18n.ts` and the desktop app's `labels.js`.
|
|
9
|
+
*/
|
|
10
|
+
declare const EN: {
|
|
11
|
+
readonly connectorNotStarted: "the connector could not be started";
|
|
12
|
+
readonly addressRefused: "the organization address was refused";
|
|
13
|
+
readonly fileNotFound: "not found: {{file}}";
|
|
14
|
+
readonly fileNoPermission: "cannot read {{file}}: permission denied";
|
|
15
|
+
readonly providerCallFailed: "provider call failed";
|
|
16
|
+
readonly somethingWrong: "something went wrong";
|
|
17
|
+
};
|
|
18
|
+
export type WordKey = keyof typeof EN;
|
|
19
|
+
export declare function word(key: WordKey, vars?: Record<string, string | number>): string;
|
|
20
|
+
export {};
|
package/out/lib/words.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.word = word;
|
|
4
|
+
/**
|
|
5
|
+
* The few words the published command line tool says on its own, in one place.
|
|
6
|
+
*
|
|
7
|
+
* Everything about the work arrives from the service already worded (rule 17).
|
|
8
|
+
* What is left here is what the tool must say when the service, a file or the
|
|
9
|
+
* operating system gives it nothing to say: a refused address, a file it cannot
|
|
10
|
+
* read, a call that failed without a reason. Same shape as the editor's
|
|
11
|
+
* `i18n.ts` and the desktop app's `labels.js`.
|
|
12
|
+
*/
|
|
13
|
+
const EN = {
|
|
14
|
+
connectorNotStarted: 'the connector could not be started',
|
|
15
|
+
addressRefused: 'the organization address was refused',
|
|
16
|
+
fileNotFound: 'not found: {{file}}',
|
|
17
|
+
fileNoPermission: 'cannot read {{file}}: permission denied',
|
|
18
|
+
providerCallFailed: 'provider call failed',
|
|
19
|
+
somethingWrong: 'something went wrong',
|
|
20
|
+
};
|
|
21
|
+
function word(key, vars) {
|
|
22
|
+
let s = EN[key];
|
|
23
|
+
if (vars)
|
|
24
|
+
for (const [k, v] of Object.entries(vars))
|
|
25
|
+
s = s.split(`{{${k}}}`).join(String(v));
|
|
26
|
+
return s;
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a folder of source files into code map facts, on the machine that holds
|
|
3
|
+
* them.
|
|
4
|
+
*
|
|
5
|
+
* Reading is the only part of the map that happens here. The joining, the
|
|
6
|
+
* storing and the answering all happen in the service, because a client that
|
|
7
|
+
* joined the map would be a client holding an opinion, and because a map that
|
|
8
|
+
* lived on one laptop could not be read by the same person's editor an hour
|
|
9
|
+
* later.
|
|
10
|
+
*/
|
|
11
|
+
import type { FactSet } from "./facts";
|
|
12
|
+
import { IndexedFile } from "./files";
|
|
13
|
+
export interface ReadResult {
|
|
14
|
+
/** One entry per file successfully read. */
|
|
15
|
+
facts: Record<string, FactSet>;
|
|
16
|
+
failures: {
|
|
17
|
+
file: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}[];
|
|
20
|
+
/** Files present in the project that no reader on this machine can read. */
|
|
21
|
+
unread: {
|
|
22
|
+
python: number;
|
|
23
|
+
typescript: number;
|
|
24
|
+
};
|
|
25
|
+
seconds: number;
|
|
26
|
+
}
|
|
27
|
+
/** What this machine can read at all, said plainly and once. */
|
|
28
|
+
export declare function readers(): {
|
|
29
|
+
python: boolean;
|
|
30
|
+
typescript: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** Read the named files. Everything when `rels` is absent. */
|
|
33
|
+
export declare function readFacts(root: string, rels?: string[], knownNames?: string[]): ReadResult;
|
|
34
|
+
export interface ProjectState {
|
|
35
|
+
files: IndexedFile[];
|
|
36
|
+
stamps: Record<string, string>;
|
|
37
|
+
fingerprint: {
|
|
38
|
+
hash: string;
|
|
39
|
+
files: number;
|
|
40
|
+
};
|
|
41
|
+
commit: string;
|
|
42
|
+
}
|
|
43
|
+
/** What the project looks like right now: which files, and one short string
|
|
44
|
+
* that changes the moment any of them does. Costs about ten milliseconds. */
|
|
45
|
+
export declare function projectState(root: string): ProjectState;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readers = readers;
|
|
4
|
+
exports.readFacts = readFacts;
|
|
5
|
+
exports.projectState = projectState;
|
|
6
|
+
const files_1 = require("./files");
|
|
7
|
+
const read_locales_1 = require("./read-locales");
|
|
8
|
+
const read_python_1 = require("./read-python");
|
|
9
|
+
const read_typescript_1 = require("./read-typescript");
|
|
10
|
+
/** What this machine can read at all, said plainly and once. */
|
|
11
|
+
function readers() {
|
|
12
|
+
return {
|
|
13
|
+
python: Boolean(require("./read-python").findPython()),
|
|
14
|
+
typescript: Boolean((0, read_typescript_1.loadCompiler)()),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Drop the calls that could never resolve to anything in this project.
|
|
19
|
+
*
|
|
20
|
+
* Two thirds of the raw facts read from a real 1,816 file project were calls to
|
|
21
|
+
* names the project does not define: `len`, `push`, `map`, and every library
|
|
22
|
+
* function anybody imported. The service discards every one of them when it
|
|
23
|
+
* joins the map, so sending and storing them is pure waste, measured at 17.8
|
|
24
|
+
* megabytes of 26.6.
|
|
25
|
+
*
|
|
26
|
+
* `known` is what the project already defines, which the service sends back
|
|
27
|
+
* with the file stamps, plus whatever the files just read define themselves.
|
|
28
|
+
* Nothing is dropped that the service would have kept: the join applies the same
|
|
29
|
+
* rule, only later. Correctness never depends on this, only size.
|
|
30
|
+
*/
|
|
31
|
+
function prune(facts, known) {
|
|
32
|
+
const droppable = new Set(["calls", "references"]);
|
|
33
|
+
for (const rel of Object.keys(facts)) {
|
|
34
|
+
const entry = facts[rel];
|
|
35
|
+
if (!entry)
|
|
36
|
+
continue;
|
|
37
|
+
entry.links = entry.links.filter((link) => {
|
|
38
|
+
if (!droppable.has(link.kind))
|
|
39
|
+
return true;
|
|
40
|
+
const leaf = String(link.to_name ?? "").split(".").pop() ?? "";
|
|
41
|
+
return known.has(leaf);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Read the named files. Everything when `rels` is absent. */
|
|
46
|
+
function readFacts(root, rels, knownNames) {
|
|
47
|
+
const started = Date.now();
|
|
48
|
+
const all = (0, files_1.indexedFiles)(root);
|
|
49
|
+
const wanted = rels ? new Set(rels) : null;
|
|
50
|
+
const chosen = wanted ? all.filter((f) => wanted.has(f.rel)) : all;
|
|
51
|
+
const py = chosen.filter((f) => f.lang === "python").map((f) => f.rel);
|
|
52
|
+
const ts = chosen.filter((f) => f.lang === "typescript").map((f) => f.rel);
|
|
53
|
+
const locales = chosen.filter((f) => f.lang === "translations").map((f) => f.rel);
|
|
54
|
+
const fromPython = (0, read_python_1.readPython)(root, py);
|
|
55
|
+
const fromTs = (0, read_typescript_1.readTypeScript)(root, ts);
|
|
56
|
+
// Translation files need no compiler and no interpreter, so they are always
|
|
57
|
+
// read: a machine missing Python still gets its labels mapped.
|
|
58
|
+
const fromLocales = (0, read_locales_1.readLocales)(root, locales);
|
|
59
|
+
const facts = { ...fromPython.facts, ...fromTs.facts, ...fromLocales.facts };
|
|
60
|
+
// Everything the files just read define, plus everything the service already
|
|
61
|
+
// knows this project defines. A name on neither list cannot resolve.
|
|
62
|
+
const known = new Set(knownNames ?? []);
|
|
63
|
+
for (const entry of Object.values(facts)) {
|
|
64
|
+
for (const object of entry.objects) {
|
|
65
|
+
if (object.kind === "file" || object.kind === "route" || object.kind === "table")
|
|
66
|
+
continue;
|
|
67
|
+
const leaf = String(object.name).split(".").pop();
|
|
68
|
+
if (leaf)
|
|
69
|
+
known.add(leaf);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
prune(facts, known);
|
|
73
|
+
return {
|
|
74
|
+
facts,
|
|
75
|
+
failures: [...fromPython.failures, ...fromTs.failures, ...fromLocales.failures],
|
|
76
|
+
unread: {
|
|
77
|
+
python: fromPython.available ? 0 : py.length,
|
|
78
|
+
typescript: fromTs.available ? 0 : ts.length,
|
|
79
|
+
},
|
|
80
|
+
seconds: Math.round((Date.now() - started) / 100) / 10,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** What the project looks like right now: which files, and one short string
|
|
84
|
+
* that changes the moment any of them does. Costs about ten milliseconds. */
|
|
85
|
+
function projectState(root) {
|
|
86
|
+
const files = (0, files_1.indexedFiles)(root);
|
|
87
|
+
const stamps = {};
|
|
88
|
+
for (const file of files)
|
|
89
|
+
stamps[file.rel] = (0, files_1.stampOf)(file);
|
|
90
|
+
return { files, stamps, fingerprint: (0, files_1.fingerprint)(files), commit: (0, files_1.headCommit)(root) };
|
|
91
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one shape both readers emit, and the only thing that ever leaves the
|
|
3
|
+
* machine.
|
|
4
|
+
*
|
|
5
|
+
* An object and a link, each carrying a file and a line. Nothing else. No file
|
|
6
|
+
* contents, no source text, no string a person wrote. That is what makes
|
|
7
|
+
* sending the map to the service a different act from sending the code: the
|
|
8
|
+
* service learns that a function called `price_of` exists at line 40 of a named
|
|
9
|
+
* file and what calls it, and it never learns what that function says.
|
|
10
|
+
*/
|
|
11
|
+
export interface GraphObject {
|
|
12
|
+
/** Unique within a project. A file is its own path; a symbol is `file::name`. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** file, function, method, class, component, model, table, route. */
|
|
15
|
+
kind: string;
|
|
16
|
+
name: string;
|
|
17
|
+
file: string;
|
|
18
|
+
line: number;
|
|
19
|
+
[extra: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface GraphLink {
|
|
22
|
+
from: string;
|
|
23
|
+
/** imports, defines, calls, renders, inherits, references, maps_to,
|
|
24
|
+
* handled_by, declares, calls_endpoint, browser_route. */
|
|
25
|
+
kind: string;
|
|
26
|
+
/** Set when the reader could resolve the far end itself. */
|
|
27
|
+
to?: string;
|
|
28
|
+
/** Set when it could not, and the service joins it up. */
|
|
29
|
+
to_name?: string;
|
|
30
|
+
[extra: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
export interface FactSet {
|
|
33
|
+
objects: GraphObject[];
|
|
34
|
+
links: GraphLink[];
|
|
35
|
+
/** A file that declares routers, and what each is mounted under. Python only:
|
|
36
|
+
* an endpoint's path is written relative to a router that is mounted with a
|
|
37
|
+
* prefix in a different file, and without both halves the front end and the
|
|
38
|
+
* back end never join up. */
|
|
39
|
+
routers?: {
|
|
40
|
+
prefixes: Record<string, string>;
|
|
41
|
+
mounts: {
|
|
42
|
+
target: string;
|
|
43
|
+
prefix: string;
|
|
44
|
+
line: number;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The one shape both readers emit, and the only thing that ever leaves the
|
|
4
|
+
* machine.
|
|
5
|
+
*
|
|
6
|
+
* An object and a link, each carrying a file and a line. Nothing else. No file
|
|
7
|
+
* contents, no source text, no string a person wrote. That is what makes
|
|
8
|
+
* sending the map to the service a different act from sending the code: the
|
|
9
|
+
* service learns that a function called `price_of` exists at line 40 of a named
|
|
10
|
+
* file and what calls it, and it never learns what that function says.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** How many files one project may have indexed. A ceiling, not a guess: a
|
|
2
|
+
* monorepo of forty thousand files would take minutes to read and produce a
|
|
3
|
+
* map too large to be worth sending, and half a map that claims to be whole is
|
|
4
|
+
* the failure this whole feature exists to avoid. Past this the map says
|
|
5
|
+
* plainly that it covers part of the project. */
|
|
6
|
+
export declare const MAX_FILES = 6000;
|
|
7
|
+
/** How large a single file may be before it is skipped. A generated bundle or
|
|
8
|
+
* a checked-in minified file is not code anybody navigates. */
|
|
9
|
+
export declare const MAX_FILE_BYTES = 1500000;
|
|
10
|
+
export declare const PY_EXT: Set<string>;
|
|
11
|
+
export declare const TS_EXT: Set<string>;
|
|
12
|
+
export interface IndexedFile {
|
|
13
|
+
/** Path relative to the project root, forward slashes, always. */
|
|
14
|
+
rel: string;
|
|
15
|
+
size: number;
|
|
16
|
+
mtimeMs: number;
|
|
17
|
+
lang: "python" | "typescript" | "translations";
|
|
18
|
+
}
|
|
19
|
+
/** Every file the map covers, in a stable order.
|
|
20
|
+
*
|
|
21
|
+
* Sorted, and that is not cosmetic: the fingerprint below is a hash over this
|
|
22
|
+
* list, so an unstable order would report the code had moved every time the
|
|
23
|
+
* filesystem felt like returning entries differently. */
|
|
24
|
+
export declare function indexedFiles(root: string): IndexedFile[];
|
|
25
|
+
/** One short string per file that changes whenever that file does.
|
|
26
|
+
*
|
|
27
|
+
* Size and modified time rather than a hash of the contents: reading six
|
|
28
|
+
* thousand files to find out whether any of them changed costs more than
|
|
29
|
+
* rebuilding the map. A file edited back to its original contents within the
|
|
30
|
+
* same millisecond would be missed, which is a case that does not occur in
|
|
31
|
+
* practice and is corrected by the next ordinary edit. */
|
|
32
|
+
export declare function stampOf(file: IndexedFile): string;
|
|
33
|
+
/** One short string for the whole project. Saying the code has not moved is a
|
|
34
|
+
* claim, and this is the evidence for it. */
|
|
35
|
+
export declare function fingerprint(files: IndexedFile[]): {
|
|
36
|
+
hash: string;
|
|
37
|
+
files: number;
|
|
38
|
+
};
|
|
39
|
+
/** The commit the project is standing on, when it is a repository at all.
|
|
40
|
+
*
|
|
41
|
+
* Read with a plain file read rather than by running git, because this is
|
|
42
|
+
* called at the start of every job and spawning a process there is a cost paid
|
|
43
|
+
* on work that has nothing to do with the map. A project with no git still
|
|
44
|
+
* gets a usable map; it simply cannot stamp an answer with a commit. */
|
|
45
|
+
export declare function headCommit(root: string): string;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TS_EXT = exports.PY_EXT = exports.MAX_FILE_BYTES = exports.MAX_FILES = void 0;
|
|
37
|
+
exports.indexedFiles = indexedFiles;
|
|
38
|
+
exports.stampOf = stampOf;
|
|
39
|
+
exports.fingerprint = fingerprint;
|
|
40
|
+
exports.headCommit = headCommit;
|
|
41
|
+
/**
|
|
42
|
+
* What the code map covers, and how to tell in milliseconds whether the code
|
|
43
|
+
* has moved since it was last read.
|
|
44
|
+
*
|
|
45
|
+
* Everything here runs on the person's own machine, because it is the one place
|
|
46
|
+
* their source files exist. Nothing in this folder decides, prices or
|
|
47
|
+
* classifies anything: it reads files and produces facts. That is the division
|
|
48
|
+
* the whole product rests on, and a function here that formed an opinion would
|
|
49
|
+
* be the first crack in it.
|
|
50
|
+
*/
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const read_locales_1 = require("./read-locales");
|
|
55
|
+
/** Folders never worth reading: somebody else's code, or build output. */
|
|
56
|
+
const SKIP_DIRS = new Set([
|
|
57
|
+
"node_modules", ".git", "__pycache__", ".venv", "venv", "env",
|
|
58
|
+
"site-packages", "dist", "build", ".pytest_cache", ".mypy_cache",
|
|
59
|
+
"htmlcov", ".ruff_cache", "coverage", ".vite", ".next", ".turbo",
|
|
60
|
+
"playwright-report", "test-results", "vendor", "target", ".cache",
|
|
61
|
+
"scratchpad", ".comprism", ".completionprism",
|
|
62
|
+
]);
|
|
63
|
+
/** Folder names that are build output only when they sit beside a package
|
|
64
|
+
* manifest. `out` is where TypeScript writes its JavaScript by convention, but
|
|
65
|
+
* it is also a perfectly ordinary name for a source folder, so skipping it
|
|
66
|
+
* everywhere would quietly remove real code from somebody's map. Next to a
|
|
67
|
+
* package.json it is almost always generated.
|
|
68
|
+
*
|
|
69
|
+
* Found by mapping this repository and getting 83 changed files from one edit:
|
|
70
|
+
* the terminal client's own compiled output was in the map, so every rebuild
|
|
71
|
+
* looked like eighty three source files moving. */
|
|
72
|
+
const BUILT_BESIDE_MANIFEST = new Set(["out", "output", "lib", "es", "esm"]);
|
|
73
|
+
/** How many files one project may have indexed. A ceiling, not a guess: a
|
|
74
|
+
* monorepo of forty thousand files would take minutes to read and produce a
|
|
75
|
+
* map too large to be worth sending, and half a map that claims to be whole is
|
|
76
|
+
* the failure this whole feature exists to avoid. Past this the map says
|
|
77
|
+
* plainly that it covers part of the project. */
|
|
78
|
+
exports.MAX_FILES = 6000;
|
|
79
|
+
/** How large a single file may be before it is skipped. A generated bundle or
|
|
80
|
+
* a checked-in minified file is not code anybody navigates. */
|
|
81
|
+
exports.MAX_FILE_BYTES = 1_500_000;
|
|
82
|
+
exports.PY_EXT = new Set([".py"]);
|
|
83
|
+
exports.TS_EXT = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
84
|
+
function isCode(rel, name) {
|
|
85
|
+
if (name.endsWith(".d.ts"))
|
|
86
|
+
return null;
|
|
87
|
+
if (name.endsWith(".min.js"))
|
|
88
|
+
return null;
|
|
89
|
+
const ext = path.extname(name);
|
|
90
|
+
if (exports.PY_EXT.has(ext))
|
|
91
|
+
return "python";
|
|
92
|
+
if (exports.TS_EXT.has(ext))
|
|
93
|
+
return "typescript";
|
|
94
|
+
// Translation files. Not code, and in the map for a reason no code parser can
|
|
95
|
+
// serve: the label a person sees is a key here and a sentence in ten language
|
|
96
|
+
// files, and "which languages are missing this one" is the question asked
|
|
97
|
+
// about it. See `read-locales.ts`.
|
|
98
|
+
if ((0, read_locales_1.looksLikeTranslations)(rel))
|
|
99
|
+
return "translations";
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/** Every file the map covers, in a stable order.
|
|
103
|
+
*
|
|
104
|
+
* Sorted, and that is not cosmetic: the fingerprint below is a hash over this
|
|
105
|
+
* list, so an unstable order would report the code had moved every time the
|
|
106
|
+
* filesystem felt like returning entries differently. */
|
|
107
|
+
function indexedFiles(root) {
|
|
108
|
+
const out = [];
|
|
109
|
+
const walk = (dir) => {
|
|
110
|
+
if (out.length >= exports.MAX_FILES)
|
|
111
|
+
return;
|
|
112
|
+
let entries;
|
|
113
|
+
try {
|
|
114
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (out.length >= exports.MAX_FILES)
|
|
121
|
+
return;
|
|
122
|
+
const full = path.join(dir, entry.name);
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
if (entry.name.startsWith(".") && entry.name !== ".github")
|
|
125
|
+
continue;
|
|
126
|
+
if (SKIP_DIRS.has(entry.name))
|
|
127
|
+
continue;
|
|
128
|
+
if (BUILT_BESIDE_MANIFEST.has(entry.name)
|
|
129
|
+
&& fs.existsSync(path.join(dir, "package.json")))
|
|
130
|
+
continue;
|
|
131
|
+
walk(full);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!entry.isFile())
|
|
135
|
+
continue;
|
|
136
|
+
const rel = path.relative(root, full).split(path.sep).join("/");
|
|
137
|
+
const lang = isCode(rel, entry.name);
|
|
138
|
+
if (!lang)
|
|
139
|
+
continue;
|
|
140
|
+
let stat;
|
|
141
|
+
try {
|
|
142
|
+
stat = fs.statSync(full);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (stat.size > exports.MAX_FILE_BYTES)
|
|
148
|
+
continue;
|
|
149
|
+
out.push({
|
|
150
|
+
rel,
|
|
151
|
+
size: stat.size,
|
|
152
|
+
mtimeMs: Math.round(stat.mtimeMs),
|
|
153
|
+
lang,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
walk(root);
|
|
158
|
+
out.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/** One short string per file that changes whenever that file does.
|
|
162
|
+
*
|
|
163
|
+
* Size and modified time rather than a hash of the contents: reading six
|
|
164
|
+
* thousand files to find out whether any of them changed costs more than
|
|
165
|
+
* rebuilding the map. A file edited back to its original contents within the
|
|
166
|
+
* same millisecond would be missed, which is a case that does not occur in
|
|
167
|
+
* practice and is corrected by the next ordinary edit. */
|
|
168
|
+
function stampOf(file) {
|
|
169
|
+
return `${file.size}:${file.mtimeMs}`;
|
|
170
|
+
}
|
|
171
|
+
/** One short string for the whole project. Saying the code has not moved is a
|
|
172
|
+
* claim, and this is the evidence for it. */
|
|
173
|
+
function fingerprint(files) {
|
|
174
|
+
const hash = crypto.createHash("sha1");
|
|
175
|
+
for (const file of files)
|
|
176
|
+
hash.update(`${file.rel}:${stampOf(file)}\n`);
|
|
177
|
+
return { hash: hash.digest("hex").slice(0, 16), files: files.length };
|
|
178
|
+
}
|
|
179
|
+
/** The commit the project is standing on, when it is a repository at all.
|
|
180
|
+
*
|
|
181
|
+
* Read with a plain file read rather than by running git, because this is
|
|
182
|
+
* called at the start of every job and spawning a process there is a cost paid
|
|
183
|
+
* on work that has nothing to do with the map. A project with no git still
|
|
184
|
+
* gets a usable map; it simply cannot stamp an answer with a commit. */
|
|
185
|
+
function headCommit(root) {
|
|
186
|
+
try {
|
|
187
|
+
const head = fs.readFileSync(path.join(root, ".git", "HEAD"), "utf8").trim();
|
|
188
|
+
if (!head.startsWith("ref:"))
|
|
189
|
+
return head.slice(0, 40);
|
|
190
|
+
const ref = head.slice(4).trim();
|
|
191
|
+
const direct = path.join(root, ".git", ref);
|
|
192
|
+
if (fs.existsSync(direct))
|
|
193
|
+
return fs.readFileSync(direct, "utf8").trim().slice(0, 40);
|
|
194
|
+
const packed = path.join(root, ".git", "packed-refs");
|
|
195
|
+
if (fs.existsSync(packed)) {
|
|
196
|
+
for (const line of fs.readFileSync(packed, "utf8").split("\n")) {
|
|
197
|
+
const [sha, name] = line.trim().split(" ");
|
|
198
|
+
if (name === ref && sha)
|
|
199
|
+
return sha.slice(0, 40);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
/* not a repository, or an unusual layout. Neither is an error. */
|
|
205
|
+
}
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FactSet } from "./facts";
|
|
2
|
+
/** Is this JSON file a translation file?
|
|
3
|
+
*
|
|
4
|
+
* Both conditions, not either: a `data/en.json` of sample records is not a
|
|
5
|
+
* translation file, and a `locales/schema.json` is not one either. Indexing
|
|
6
|
+
* the wrong JSON would fill the map with things nobody asked about. */
|
|
7
|
+
export declare function looksLikeTranslations(rel: string): boolean;
|
|
8
|
+
/** The language a translation file is for, from its own name. */
|
|
9
|
+
export declare function languageOf(rel: string): string;
|
|
10
|
+
interface KeyAt {
|
|
11
|
+
key: string;
|
|
12
|
+
line: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Every leaf key in a translation file, with the line it sits on.
|
|
16
|
+
*
|
|
17
|
+
* A single pass over the text, tracking strings, escapes, nesting and lines. It
|
|
18
|
+
* reads the structure rather than the values: what a translation SAYS never
|
|
19
|
+
* leaves the machine, only that the key exists and where.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scanKeys(text: string): KeyAt[];
|
|
22
|
+
export declare function readLocales(root: string, rels: string[]): {
|
|
23
|
+
facts: Record<string, FactSet>;
|
|
24
|
+
failures: {
|
|
25
|
+
file: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
export {};
|