@nimiplatform/nimi-coding 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +3 -1
  3. package/README.md +52 -25
  4. package/README.zh-CN.md +52 -25
  5. package/cli/commands/clear.mjs +3 -2
  6. package/cli/commands/code.mjs +225 -0
  7. package/cli/commands/start.mjs +3 -2
  8. package/cli/constants.mjs +2 -1
  9. package/cli/dev/run-cli-smoke.mjs +6 -0
  10. package/cli/help.mjs +8 -3
  11. package/cli/index.mjs +3 -2
  12. package/cli/lib/authority/diagnostics.mjs +3 -1
  13. package/cli/lib/code/authority-annotation-response.mjs +43 -0
  14. package/cli/lib/code/authority-annotation-scan.mjs +115 -0
  15. package/cli/lib/code/authority-annotations.mjs +293 -0
  16. package/cli/lib/code/context.mjs +710 -0
  17. package/cli/lib/code/response.mjs +165 -0
  18. package/cli/lib/doctor.mjs +3 -2
  19. package/cli/lib/entrypoints.mjs +12 -5
  20. package/cli/seeds/seed-policy.yaml +2 -2
  21. package/methodology/authority-authoring.yaml +70 -52
  22. package/methodology/core.yaml +7 -4
  23. package/package.json +5 -3
  24. package/spec/code-authority-annotations.yaml +47 -0
  25. package/spec/code-context.yaml +38 -0
  26. package/spec/product-scope.yaml +8 -3
  27. package/cli/commands/validate-ai-governance.mjs +0 -335
  28. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +0 -377
  29. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +0 -376
  30. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +0 -155
  31. package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +0 -173
  32. package/cli/lib/internal/governance/config.mjs +0 -114
  33. package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +0 -49
@@ -0,0 +1,710 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { lstat, readFile, realpath, stat } from "node:fs/promises";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ const TYPESCRIPT_VERSION = "5.9.3";
10
+
11
+ export class CodeContextFailure extends Error {
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.name = "CodeContextFailure";
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ function fail(code, message) {
20
+ throw new CodeContextFailure(code, message);
21
+ }
22
+
23
+ function pathWithin(root, candidate) {
24
+ const relative = path.relative(root, candidate);
25
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
26
+ }
27
+
28
+ function posixPath(value) {
29
+ return value.split(path.sep).join("/");
30
+ }
31
+
32
+ function cleanGitEnvironment() {
33
+ const environment = { ...process.env };
34
+ for (const key of Object.keys(environment)) {
35
+ if (key === "GIT_DIR" || key === "GIT_WORK_TREE" || key === "GIT_INDEX_FILE" || key.startsWith("GIT_CONFIG_")) {
36
+ delete environment[key];
37
+ }
38
+ }
39
+ return {
40
+ ...environment,
41
+ GIT_CONFIG_NOSYSTEM: "1",
42
+ GIT_OPTIONAL_LOCKS: "0",
43
+ GIT_TERMINAL_PROMPT: "0",
44
+ LC_ALL: "C",
45
+ };
46
+ }
47
+
48
+ async function gitLine(repository, args) {
49
+ try {
50
+ const result = await execFileAsync("git", args, {
51
+ cwd: repository,
52
+ env: cleanGitEnvironment(),
53
+ encoding: "utf8",
54
+ maxBuffer: 64 * 1024,
55
+ windowsHide: true,
56
+ });
57
+ const text = result.stdout.endsWith("\n") ? result.stdout.slice(0, -1) : result.stdout;
58
+ if (text.length === 0 || text.includes("\n") || text.includes("\r")) throw new Error("Git returned no single line");
59
+ return text;
60
+ } catch {
61
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
62
+ }
63
+ }
64
+
65
+ async function resolveExactRepository(repositoryInput) {
66
+ const requested = path.resolve(process.cwd(), repositoryInput);
67
+ let info;
68
+ try {
69
+ info = await lstat(requested);
70
+ } catch {
71
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
72
+ }
73
+ if (!info.isDirectory() || info.isSymbolicLink()) {
74
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
75
+ }
76
+ const repository = await realpath(requested);
77
+ if (await gitLine(repository, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
78
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
79
+ }
80
+ let top;
81
+ try {
82
+ top = await realpath(await gitLine(repository, ["rev-parse", "--path-format=absolute", "--show-toplevel"]));
83
+ } catch {
84
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
85
+ }
86
+ if (path.relative(repository, top) !== "") {
87
+ fail("CODE_REPOSITORY_INVALID", "repository must be one exact readable Git worktree root");
88
+ }
89
+ return repository;
90
+ }
91
+
92
+ async function resolveContainedFile(repository, input, kind) {
93
+ if (typeof input !== "string" || input.length === 0 || input.includes("\0") || path.isAbsolute(input)) {
94
+ fail("CODE_PATH_INVALID", `${kind} must be one contained repository-relative file`);
95
+ }
96
+ const requested = path.resolve(repository, input);
97
+ if (!pathWithin(repository, requested)) fail("CODE_PATH_INVALID", `${kind} must be one contained repository-relative file`);
98
+ let resolved;
99
+ let info;
100
+ try {
101
+ resolved = await realpath(requested);
102
+ info = await stat(resolved);
103
+ } catch {
104
+ fail("CODE_PATH_INVALID", `${kind} must be one contained repository-relative file`);
105
+ }
106
+ if (!pathWithin(repository, resolved) || !info.isFile()) {
107
+ fail("CODE_PATH_INVALID", `${kind} must be one contained repository-relative file`);
108
+ }
109
+ return { absolute: resolved, relative: posixPath(path.relative(repository, resolved)) };
110
+ }
111
+
112
+ async function nearestPackageManifest(repository, startDirectory) {
113
+ let current = startDirectory;
114
+ while (pathWithin(repository, current)) {
115
+ const manifest = path.join(current, "package.json");
116
+ try {
117
+ const resolved = await realpath(manifest);
118
+ if (!pathWithin(repository, resolved)) {
119
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "tsconfig owner package manifest must remain inside the repository");
120
+ }
121
+ if ((await stat(resolved)).isFile()) return resolved;
122
+ } catch (error) {
123
+ if (error instanceof CodeContextFailure) throw error;
124
+ // Continue to the next repository-contained parent.
125
+ }
126
+ if (current === repository) break;
127
+ current = path.dirname(current);
128
+ }
129
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "tsconfig owner has no repository-contained package manifest");
130
+ }
131
+
132
+ async function loadProjectTypeScript(repository, tsconfigPath) {
133
+ const manifest = await nearestPackageManifest(repository, path.dirname(tsconfigPath));
134
+ const requireFromOwner = createRequire(manifest);
135
+ let packagePath;
136
+ let compiler;
137
+ try {
138
+ packagePath = requireFromOwner.resolve("typescript/package.json");
139
+ compiler = requireFromOwner("typescript");
140
+ } catch {
141
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "tsconfig owner cannot resolve project-local TypeScript 5.9.3");
142
+ }
143
+ let packageManifest;
144
+ try {
145
+ packageManifest = JSON.parse(await readFile(packagePath, "utf8"));
146
+ } catch {
147
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "project-local TypeScript package metadata is unreadable");
148
+ }
149
+ if (packageManifest.version !== TYPESCRIPT_VERSION || compiler.version !== TYPESCRIPT_VERSION) {
150
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "project-local TypeScript version must be exactly 5.9.3");
151
+ }
152
+ for (const capability of ["readConfigFile", "parseJsonConfigFileContent", "createProgram", "isIdentifier"]) {
153
+ if (typeof compiler[capability] !== "function") {
154
+ fail("CODE_TYPESCRIPT_UNSUPPORTED", "project-local TypeScript lacks a required public capability");
155
+ }
156
+ }
157
+ return {
158
+ compiler,
159
+ compilerRoot: await realpath(path.dirname(packagePath)),
160
+ compilerVersion: TYPESCRIPT_VERSION,
161
+ };
162
+ }
163
+
164
+ function diagnosticHasError(compiler, diagnostics) {
165
+ return diagnostics.some((diagnostic) => diagnostic.category === compiler.DiagnosticCategory.Error);
166
+ }
167
+
168
+ function canonicalCompilerPath(compiler, value) {
169
+ const normalized = path.normalize(path.resolve(value));
170
+ return compiler.sys.useCaseSensitiveFileNames ? normalized : normalized.toLowerCase();
171
+ }
172
+
173
+ function sourceFileForPath(compiler, program, sourcePath) {
174
+ const direct = program.getSourceFile(sourcePath) ?? program.getSourceFile(compiler.sys.resolvePath(sourcePath));
175
+ if (direct !== undefined) return direct;
176
+ const expected = canonicalCompilerPath(compiler, sourcePath);
177
+ return program.getSourceFiles().find((sourceFile) => {
178
+ let candidate = sourceFile.fileName;
179
+ try {
180
+ candidate = compiler.sys.realpath?.(candidate) ?? candidate;
181
+ } catch {
182
+ // A compiler-resolved virtual path cannot prove membership in the requested real file.
183
+ }
184
+ return canonicalCompilerPath(compiler, candidate) === expected;
185
+ });
186
+ }
187
+
188
+ function createSelectedProgram(compiler, tsconfigPath, sourcePath) {
189
+ const config = compiler.readConfigFile(tsconfigPath, compiler.sys.readFile);
190
+ if (config.error !== undefined) fail("CODE_CONFIG_INVALID", "tsconfig could not be read with TypeScript 5.9.3");
191
+ const parsed = compiler.parseJsonConfigFileContent(
192
+ config.config,
193
+ compiler.sys,
194
+ path.dirname(tsconfigPath),
195
+ undefined,
196
+ tsconfigPath,
197
+ );
198
+ if (diagnosticHasError(compiler, parsed.errors)) {
199
+ fail("CODE_CONFIG_INVALID", "tsconfig could not produce one complete TypeScript Program");
200
+ }
201
+ if ((parsed.projectReferences?.length ?? 0) > 0) {
202
+ fail("CODE_PROJECT_REFERENCES_UNSUPPORTED", "TypeScript project references are not supported by code context v1");
203
+ }
204
+ let program;
205
+ try {
206
+ program = compiler.createProgram({ rootNames: parsed.fileNames, options: parsed.options });
207
+ } catch {
208
+ fail("CODE_CONFIG_INVALID", "tsconfig could not produce one complete TypeScript Program");
209
+ }
210
+ const sourceFile = sourceFileForPath(compiler, program, sourcePath);
211
+ if (sourceFile === undefined) fail("CODE_SOURCE_NOT_IN_PROGRAM", "source is not a member of the selected TypeScript Program");
212
+ if (sourceFile.isDeclarationFile || !/[.]tsx?$/u.test(sourceFile.fileName) || /[.]d[.]ts$/u.test(sourceFile.fileName)) {
213
+ fail("CODE_SOURCE_UNSUPPORTED", "root source must be one non-declaration .ts or .tsx file");
214
+ }
215
+ return { program, sourceFile, checker: program.getTypeChecker() };
216
+ }
217
+
218
+ function admittedRoot(compiler, statement, symbolName) {
219
+ if (
220
+ (compiler.isFunctionDeclaration(statement)
221
+ || compiler.isClassDeclaration(statement)
222
+ || compiler.isInterfaceDeclaration(statement)
223
+ || compiler.isTypeAliasDeclaration(statement))
224
+ && statement.name !== undefined
225
+ && compiler.isIdentifier(statement.name)
226
+ && statement.name.text === symbolName
227
+ ) {
228
+ return { declaration: statement, sourceNode: statement, name: statement.name };
229
+ }
230
+ if (!compiler.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return null;
231
+ const declaration = statement.declarationList.declarations[0];
232
+ if (!compiler.isIdentifier(declaration.name) || declaration.name.text !== symbolName) return null;
233
+ return { declaration, sourceNode: statement, name: declaration.name };
234
+ }
235
+
236
+ function rootKind(compiler, declaration) {
237
+ if (compiler.isFunctionDeclaration(declaration)) return "function";
238
+ if (compiler.isClassDeclaration(declaration)) return "class";
239
+ if (compiler.isInterfaceDeclaration(declaration)) return "interface";
240
+ if (compiler.isTypeAliasDeclaration(declaration)) return "type_alias";
241
+ return "variable";
242
+ }
243
+
244
+ function selectRoot(compiler, checker, sourceFile, symbolName) {
245
+ const matches = sourceFile.statements
246
+ .map((statement) => admittedRoot(compiler, statement, symbolName))
247
+ .filter((entry) => entry !== null);
248
+ if (matches.length !== 1) fail("CODE_SYMBOL_NOT_FOUND", "symbol must match one supported top-level declaration");
249
+ const selected = matches[0];
250
+ const symbol = checker.getSymbolAtLocation(selected.name);
251
+ const declarations = symbol?.getDeclarations?.() ?? symbol?.declarations;
252
+ if (symbol === undefined || declarations === undefined || declarations.length !== 1 || declarations[0] !== selected.declaration) {
253
+ fail("CODE_SYMBOL_UNSUPPORTED", "root symbol must have one unmerged supported declaration");
254
+ }
255
+ return { ...selected, symbol, kind: rootKind(compiler, selected.declaration) };
256
+ }
257
+
258
+ function nodeRange(compiler, sourceFile, node) {
259
+ const start = node.getStart(sourceFile, false);
260
+ const end = node.getEnd();
261
+ const startPosition = sourceFile.getLineAndCharacterOfPosition(start);
262
+ const endPosition = sourceFile.getLineAndCharacterOfPosition(end);
263
+ return {
264
+ start,
265
+ end,
266
+ portable: {
267
+ start_line: startPosition.line + 1,
268
+ start_column: startPosition.character + 1,
269
+ end_line: endPosition.line + 1,
270
+ end_column: endPosition.character + 1,
271
+ },
272
+ };
273
+ }
274
+
275
+ function owningDependencyPackage(filePath) {
276
+ let current = path.dirname(filePath);
277
+ while (true) {
278
+ const manifestPath = path.join(current, "package.json");
279
+ if (existsSync(manifestPath)) {
280
+ try {
281
+ if (statSync(manifestPath).isFile()) {
282
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
283
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
284
+ return {
285
+ root: realpathSync(current),
286
+ name: manifest.name,
287
+ version: typeof manifest.version === "string" ? manifest.version : null,
288
+ };
289
+ }
290
+ }
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ const parent = path.dirname(current);
296
+ if (parent === current) return null;
297
+ current = parent;
298
+ }
299
+ }
300
+
301
+ function sourceIdentity(repository, compilerRoot, compilerVersion, sourceFile) {
302
+ let filePath;
303
+ try {
304
+ filePath = realpathSync(sourceFile.fileName);
305
+ } catch {
306
+ return null;
307
+ }
308
+ if (pathWithin(compilerRoot, filePath)) {
309
+ return {
310
+ origin: "typescript_lib",
311
+ path: posixPath(path.relative(compilerRoot, filePath)),
312
+ package: { name: "typescript", version: compilerVersion },
313
+ };
314
+ }
315
+ const repositoryRelative = path.relative(repository, filePath);
316
+ const repositorySegments = repositoryRelative.split(path.sep);
317
+ if (pathWithin(repository, filePath) && !repositorySegments.includes("node_modules")) {
318
+ return { origin: "repository", path: posixPath(repositoryRelative), package: null };
319
+ }
320
+ const dependency = owningDependencyPackage(filePath);
321
+ if (dependency === null || !pathWithin(dependency.root, filePath)) return null;
322
+ return {
323
+ origin: "dependency",
324
+ path: posixPath(path.relative(dependency.root, filePath)),
325
+ package: { name: dependency.name, version: dependency.version },
326
+ };
327
+ }
328
+
329
+ function portableLocus(compiler, repository, compilerRoot, compilerVersion, node) {
330
+ const sourceFile = node.getSourceFile?.();
331
+ if (sourceFile === undefined) return null;
332
+ const identity = sourceIdentity(repository, compilerRoot, compilerVersion, sourceFile);
333
+ if (identity === null) return null;
334
+ try {
335
+ return { ...identity, range: nodeRange(compiler, sourceFile, node).portable };
336
+ } catch {
337
+ return null;
338
+ }
339
+ }
340
+
341
+ function compareLoci(left, right) {
342
+ return left.origin.localeCompare(right.origin)
343
+ || (left.package?.name ?? "").localeCompare(right.package?.name ?? "")
344
+ || (left.package?.version ?? "").localeCompare(right.package?.version ?? "")
345
+ || left.path.localeCompare(right.path)
346
+ || left.range.start_line - right.range.start_line
347
+ || left.range.start_column - right.range.start_column
348
+ || left.range.end_line - right.range.end_line
349
+ || left.range.end_column - right.range.end_column;
350
+ }
351
+
352
+ function symbolKind(compiler, symbol) {
353
+ const flags = symbol.flags;
354
+ for (const [flag, label] of [
355
+ [compiler.SymbolFlags.Class, "class"],
356
+ [compiler.SymbolFlags.Interface, "interface"],
357
+ [compiler.SymbolFlags.TypeAlias, "type_alias"],
358
+ [compiler.SymbolFlags.Enum, "enum"],
359
+ [compiler.SymbolFlags.Function, "function"],
360
+ [compiler.SymbolFlags.Method, "method"],
361
+ [compiler.SymbolFlags.Property, "property"],
362
+ [compiler.SymbolFlags.Variable, "variable"],
363
+ [compiler.SymbolFlags.Module, "module"],
364
+ [compiler.SymbolFlags.TypeParameter, "type_parameter"],
365
+ ]) {
366
+ if ((flags & flag) !== 0) return label;
367
+ }
368
+ return "other";
369
+ }
370
+
371
+ function terminalJsxName(compiler, tagName) {
372
+ if (compiler.isIdentifier(tagName)) return tagName;
373
+ if (compiler.isPropertyAccessExpression(tagName)) return tagName.name;
374
+ if (compiler.isJsxNamespacedName?.(tagName)) return tagName.name;
375
+ return null;
376
+ }
377
+
378
+ function jsxTagForNode(compiler, node) {
379
+ let current = node;
380
+ let parent = node.parent;
381
+ while (parent !== undefined && (compiler.isPropertyAccessExpression(parent) || compiler.isJsxNamespacedName?.(parent))) {
382
+ current = parent;
383
+ parent = parent.parent;
384
+ }
385
+ if (
386
+ parent !== undefined
387
+ && (compiler.isJsxOpeningElement(parent) || compiler.isJsxClosingElement(parent) || compiler.isJsxSelfClosingElement(parent))
388
+ && parent.tagName === current
389
+ ) {
390
+ return parent.tagName;
391
+ }
392
+ return null;
393
+ }
394
+
395
+ function isLiteralModuleSite(compiler, node) {
396
+ if (!compiler.isStringLiteral(node)) return false;
397
+ const parent = node.parent;
398
+ if (
399
+ compiler.isCallExpression(parent)
400
+ && parent.expression.kind === compiler.SyntaxKind.ImportKeyword
401
+ && parent.arguments[0] === node
402
+ ) return true;
403
+ return compiler.isLiteralTypeNode(parent)
404
+ && parent.literal === node
405
+ && compiler.isImportTypeNode(parent.parent)
406
+ && parent.parent.argument === parent;
407
+ }
408
+
409
+ function siteRole(compiler, node) {
410
+ if (isLiteralModuleSite(compiler, node)) return "module";
411
+ if (!compiler.isIdentifier(node)) return null;
412
+ const tagName = jsxTagForNode(compiler, node);
413
+ if (tagName !== null) return terminalJsxName(compiler, tagName) === node ? "jsx" : null;
414
+ const parent = node.parent;
415
+ if (compiler.isPropertyAccessExpression(parent) && parent.name === node) return "member";
416
+ if (compiler.isQualifiedName(parent) && parent.right === node) return "member";
417
+ if (compiler.isShorthandPropertyAssignment(parent) && parent.name === node) return "identifier";
418
+ if (parent?.name === node || parent?.propertyName === node || parent?.label === node) return null;
419
+ return "identifier";
420
+ }
421
+
422
+ function collectSites(compiler, rootNode) {
423
+ const sites = [];
424
+ const seen = new Set();
425
+ function visit(node) {
426
+ const role = siteRole(compiler, node);
427
+ if (role !== null) {
428
+ const key = `${node.pos}:${node.end}:${role}`;
429
+ if (!seen.has(key)) {
430
+ seen.add(key);
431
+ sites.push({ node, role });
432
+ }
433
+ }
434
+ compiler.forEachChild(node, visit);
435
+ }
436
+ compiler.forEachChild(rootNode, visit);
437
+ return sites.sort((left, right) => (
438
+ left.node.getStart() - right.node.getStart()
439
+ || left.node.getEnd() - right.node.getEnd()
440
+ || left.role.localeCompare(right.role)
441
+ ));
442
+ }
443
+
444
+ function isTypePosition(compiler, node, rootNode) {
445
+ let current = node;
446
+ while (current !== rootNode && current.parent !== undefined) {
447
+ current = current.parent;
448
+ if (compiler.isTypeNode(current)) return true;
449
+ if (compiler.isExpression(current) && current !== node) return false;
450
+ }
451
+ return false;
452
+ }
453
+
454
+ function isDirectCalleeTerminal(compiler, node, expression) {
455
+ if (expression === node) return true;
456
+ if (compiler.isPropertyAccessExpression(expression)) return expression.name === node;
457
+ return false;
458
+ }
459
+
460
+ function bindingKind(compiler, site, rootNode) {
461
+ if (site.role === "jsx") return "resolved_jsx_binding";
462
+ let ancestor = site.node;
463
+ while (ancestor.parent !== undefined && (compiler.isPropertyAccessExpression(ancestor.parent) || compiler.isElementAccessExpression(ancestor.parent))) {
464
+ ancestor = ancestor.parent;
465
+ }
466
+ if (
467
+ compiler.isNewExpression(ancestor.parent)
468
+ && ancestor.parent.expression === ancestor
469
+ && isDirectCalleeTerminal(compiler, site.node, ancestor)
470
+ ) return "resolved_constructor_binding";
471
+ if (
472
+ (site.role === "module" && compiler.isLiteralTypeNode(site.node.parent))
473
+ || isTypePosition(compiler, site.node, rootNode)
474
+ ) return "resolved_type_binding";
475
+ return "resolved_value_binding";
476
+ }
477
+
478
+ function moduleSpecifierForAlias(compiler, declaration) {
479
+ let current = declaration;
480
+ while (current !== undefined) {
481
+ if (compiler.isImportDeclaration(current) || compiler.isExportDeclaration(current)) {
482
+ return compiler.isStringLiteral(current.moduleSpecifier) ? current.moduleSpecifier.text : null;
483
+ }
484
+ if (compiler.isImportEqualsDeclaration(current)) {
485
+ const reference = current.moduleReference;
486
+ return compiler.isExternalModuleReference(reference) && compiler.isStringLiteral(reference.expression)
487
+ ? reference.expression.text
488
+ : null;
489
+ }
490
+ current = current.parent;
491
+ }
492
+ return null;
493
+ }
494
+
495
+ function aliasNames(compiler, declaration) {
496
+ if (compiler.isImportSpecifier(declaration) || compiler.isExportSpecifier(declaration)) {
497
+ return {
498
+ imported_name: (declaration.propertyName ?? declaration.name).text,
499
+ local_name: declaration.name.text,
500
+ };
501
+ }
502
+ if (compiler.isImportClause(declaration) && declaration.name !== undefined) {
503
+ return { imported_name: "default", local_name: declaration.name.text };
504
+ }
505
+ if (compiler.isNamespaceImport(declaration)) return { imported_name: "*", local_name: declaration.name.text };
506
+ if (compiler.isImportEqualsDeclaration(declaration)) return { imported_name: "export=", local_name: declaration.name.text };
507
+ return { imported_name: null, local_name: declaration.name?.text ?? null };
508
+ }
509
+
510
+ function declarationSet(symbol) {
511
+ const declarations = symbol.getDeclarations?.() ?? symbol.declarations;
512
+ return Array.isArray(declarations) && declarations.length > 0 ? declarations : null;
513
+ }
514
+
515
+ function declarationsAreRootLocal(declarations, sourceFile, rootStart, rootEnd) {
516
+ return declarations.every((declaration) => {
517
+ if (declaration.getSourceFile() !== sourceFile) return false;
518
+ const start = declaration.getStart(sourceFile, false);
519
+ const end = declaration.getEnd();
520
+ return start >= rootStart && end <= rootEnd;
521
+ });
522
+ }
523
+
524
+ function resolveSiteSymbol(compiler, checker, site) {
525
+ if (compiler.isShorthandPropertyAssignment(site.node.parent)) {
526
+ return checker.getShorthandAssignmentValueSymbol(site.node.parent);
527
+ }
528
+ return checker.getSymbolAtLocation(site.node);
529
+ }
530
+
531
+ function aliasChain({ compiler, checker, repository, compilerRoot, compilerVersion, initial }) {
532
+ const steps = [];
533
+ const visited = new Set();
534
+ let symbol = initial;
535
+ while ((symbol.flags & compiler.SymbolFlags.Alias) !== 0) {
536
+ if (visited.has(symbol)) return { ok: false };
537
+ visited.add(symbol);
538
+ const declarations = declarationSet(symbol);
539
+ if (declarations === null || declarations.length !== 1) return { ok: false };
540
+ const declaration = declarations[0];
541
+ const locus = portableLocus(compiler, repository, compilerRoot, compilerVersion, declaration);
542
+ const moduleSpecifier = moduleSpecifierForAlias(compiler, declaration);
543
+ if (locus === null || moduleSpecifier === null) return { ok: false };
544
+ const next = checker.getImmediateAliasedSymbol(symbol);
545
+ if (next === undefined) return { ok: false };
546
+ steps.push({ locus, module_specifier: moduleSpecifier, ...aliasNames(compiler, declaration) });
547
+ symbol = next;
548
+ }
549
+ return { ok: true, symbol, steps };
550
+ }
551
+
552
+ function targetDescriptor({ compiler, checker, repository, compilerRoot, compilerVersion, symbol }) {
553
+ const exactSymbol = checker.getMergedSymbol(symbol);
554
+ const declarations = declarationSet(exactSymbol);
555
+ if (declarations === null) return { ok: false, reason: "portable_target_unavailable" };
556
+ const loci = declarations.map((declaration) => (
557
+ portableLocus(compiler, repository, compilerRoot, compilerVersion, declaration)
558
+ ));
559
+ if (loci.some((locus) => locus === null)) return { ok: false, reason: "portable_target_unavailable" };
560
+ return {
561
+ ok: true,
562
+ symbol: exactSymbol,
563
+ declarations,
564
+ descriptor: {
565
+ id: null,
566
+ name: exactSymbol.getName(),
567
+ kind: symbolKind(compiler, exactSymbol),
568
+ declaration_loci: loci.sort(compareLoci),
569
+ },
570
+ };
571
+ }
572
+
573
+ function sourceNodeForDeclaration(compiler, declaration) {
574
+ if (compiler.isSourceFile(declaration)) return null;
575
+ if (!compiler.isVariableDeclaration(declaration)) return declaration;
576
+ const declarationList = declaration.parent;
577
+ const statement = declarationList?.parent;
578
+ if (
579
+ !compiler.isVariableDeclarationList(declarationList)
580
+ || declarationList.declarations.length !== 1
581
+ || !compiler.isVariableStatement(statement)
582
+ ) return null;
583
+ return statement;
584
+ }
585
+
586
+ function sourceUnitForTarget(compiler, repository, compilerRoot, compilerVersion, targetId, declarations, rootSourceFile, rootStart, rootEnd) {
587
+ if (declarations.length !== 1) return null;
588
+ const declaration = declarations[0];
589
+ if (declaration.getSourceFile() === rootSourceFile) {
590
+ const start = declaration.getStart(rootSourceFile, false);
591
+ const end = declaration.getEnd();
592
+ if (start >= rootStart && end <= rootEnd) return null;
593
+ }
594
+ const sourceNode = sourceNodeForDeclaration(compiler, declaration);
595
+ if (sourceNode === null) return null;
596
+ const locus = portableLocus(compiler, repository, compilerRoot, compilerVersion, sourceNode);
597
+ if (locus?.origin !== "repository") return null;
598
+ const sourceFile = sourceNode.getSourceFile();
599
+ const range = nodeRange(compiler, sourceFile, sourceNode);
600
+ return { target_id: targetId, locus, source: sourceFile.text.slice(range.start, range.end) };
601
+ }
602
+
603
+ function projectRoot({ compiler, compilerRoot, compilerVersion, repository, sourceFile, checker, root }) {
604
+ const rootRange = nodeRange(compiler, sourceFile, root.sourceNode);
605
+ const rootLocus = portableLocus(compiler, repository, compilerRoot, compilerVersion, root.sourceNode);
606
+ if (rootLocus === null || rootLocus.origin !== "repository") {
607
+ fail("CODE_SOURCE_UNSUPPORTED", "root source could not produce one portable repository locus");
608
+ }
609
+ const targets = [];
610
+ const semanticItems = [];
611
+ const sourceUnits = [];
612
+ const targetBySymbol = new Map();
613
+
614
+ for (const site of collectSites(compiler, root.declaration)) {
615
+ const siteLocus = portableLocus(compiler, repository, compilerRoot, compilerVersion, site.node);
616
+ if (siteLocus === null || siteLocus.origin !== "repository") {
617
+ semanticItems.push({ kind: "unresolved", site: rootLocus, reason: "compiler_resolution_failed" });
618
+ continue;
619
+ }
620
+ const initial = resolveSiteSymbol(compiler, checker, site);
621
+ if (initial === undefined) {
622
+ semanticItems.push({ kind: "unresolved", site: siteLocus, reason: "compiler_resolution_failed" });
623
+ continue;
624
+ }
625
+ const chain = aliasChain({ compiler, checker, repository, compilerRoot, compilerVersion, initial });
626
+ if (!chain.ok) {
627
+ semanticItems.push({ kind: "unresolved", site: siteLocus, reason: "alias_resolution_not_unique" });
628
+ continue;
629
+ }
630
+ const target = targetDescriptor({
631
+ compiler,
632
+ checker,
633
+ repository,
634
+ compilerRoot,
635
+ compilerVersion,
636
+ symbol: chain.symbol,
637
+ });
638
+ if (!target.ok) {
639
+ semanticItems.push({ kind: "unresolved", site: siteLocus, reason: target.reason });
640
+ continue;
641
+ }
642
+ if (declarationsAreRootLocal(target.declarations, sourceFile, rootRange.start, rootRange.end)) continue;
643
+ let descriptor = targetBySymbol.get(target.symbol);
644
+ if (descriptor === undefined) {
645
+ descriptor = { ...target.descriptor, id: `t${targets.length + 1}` };
646
+ targetBySymbol.set(target.symbol, descriptor);
647
+ targets.push(descriptor);
648
+ const sourceUnit = sourceUnitForTarget(
649
+ compiler,
650
+ repository,
651
+ compilerRoot,
652
+ compilerVersion,
653
+ descriptor.id,
654
+ target.declarations,
655
+ sourceFile,
656
+ rootRange.start,
657
+ rootRange.end,
658
+ );
659
+ if (sourceUnit !== null) sourceUnits.push(sourceUnit);
660
+ }
661
+ semanticItems.push({
662
+ kind: bindingKind(compiler, site, root.declaration),
663
+ site: siteLocus,
664
+ alias_steps: chain.steps,
665
+ target_id: descriptor.id,
666
+ });
667
+ }
668
+
669
+ return {
670
+ root: {
671
+ symbol: root.symbol.getName(),
672
+ kind: root.kind,
673
+ locus: rootLocus,
674
+ source: sourceFile.text.slice(rootRange.start, rootRange.end),
675
+ },
676
+ targets,
677
+ semanticItems,
678
+ sourceUnits,
679
+ };
680
+ }
681
+
682
+ export async function contextForTypeScript({ repository: repositoryInput, source: sourceInput, symbol, tsconfig: tsconfigInput }) {
683
+ const repository = await resolveExactRepository(repositoryInput);
684
+ const source = await resolveContainedFile(repository, sourceInput, "source");
685
+ const tsconfig = await resolveContainedFile(repository, tsconfigInput, "tsconfig");
686
+ if (!/[.]tsx?$/u.test(source.relative) || /[.]d[.]ts$/u.test(source.relative)) {
687
+ fail("CODE_SOURCE_UNSUPPORTED", "root source must be one non-declaration .ts or .tsx file");
688
+ }
689
+ const { compiler, compilerRoot, compilerVersion } = await loadProjectTypeScript(repository, tsconfig.absolute);
690
+ const { sourceFile, checker } = createSelectedProgram(compiler, tsconfig.absolute, source.absolute);
691
+ const root = selectRoot(compiler, checker, sourceFile, symbol);
692
+ const projection = projectRoot({
693
+ compiler,
694
+ compilerRoot,
695
+ compilerVersion,
696
+ repository,
697
+ sourceFile,
698
+ checker,
699
+ root,
700
+ });
701
+ return {
702
+ scope: {
703
+ source_path: source.relative,
704
+ symbol,
705
+ tsconfig_path: tsconfig.relative,
706
+ typescript_version: compilerVersion,
707
+ },
708
+ projection,
709
+ };
710
+ }