@claudiu-ceia/astkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +393 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +3 -0
  5. package/dist/src/app.d.ts +1 -0
  6. package/dist/src/app.js +41 -0
  7. package/dist/src/cli.d.ts +2 -0
  8. package/dist/src/cli.js +4 -0
  9. package/dist/src/code-rank/code-rank.d.ts +9 -0
  10. package/dist/src/code-rank/code-rank.js +71 -0
  11. package/dist/src/code-rank/index.d.ts +1 -0
  12. package/dist/src/code-rank/index.js +1 -0
  13. package/dist/src/code-rank/rank.d.ts +29 -0
  14. package/dist/src/code-rank/rank.js +185 -0
  15. package/dist/src/common/input.d.ts +5 -0
  16. package/dist/src/common/input.js +25 -0
  17. package/dist/src/nav/declarations.d.ts +19 -0
  18. package/dist/src/nav/declarations.js +106 -0
  19. package/dist/src/nav/definition.d.ts +14 -0
  20. package/dist/src/nav/definition.js +62 -0
  21. package/dist/src/nav/location.d.ts +6 -0
  22. package/dist/src/nav/location.js +35 -0
  23. package/dist/src/nav/references.d.ts +19 -0
  24. package/dist/src/nav/references.js +75 -0
  25. package/dist/src/patch/patch.d.ts +27 -0
  26. package/dist/src/patch/patch.js +345 -0
  27. package/dist/src/pattern/balance.d.ts +1 -0
  28. package/dist/src/pattern/balance.js +25 -0
  29. package/dist/src/pattern/index.d.ts +5 -0
  30. package/dist/src/pattern/index.js +4 -0
  31. package/dist/src/pattern/match.d.ts +2 -0
  32. package/dist/src/pattern/match.js +141 -0
  33. package/dist/src/pattern/render.d.ts +1 -0
  34. package/dist/src/pattern/render.js +32 -0
  35. package/dist/src/pattern/syntax.d.ts +3 -0
  36. package/dist/src/pattern/syntax.js +87 -0
  37. package/dist/src/pattern/types.d.ts +27 -0
  38. package/dist/src/pattern/types.js +1 -0
  39. package/dist/src/search/search.d.ts +16 -0
  40. package/dist/src/search/search.js +207 -0
  41. package/dist/src/service.d.ts +16 -0
  42. package/dist/src/service.js +72 -0
  43. package/dist/src/sgrep/index.d.ts +5 -0
  44. package/dist/src/sgrep/index.js +3 -0
  45. package/dist/src/sgrep/isomorphisms/expand.d.ts +2 -0
  46. package/dist/src/sgrep/isomorphisms/expand.js +51 -0
  47. package/dist/src/sgrep/isomorphisms/index.d.ts +3 -0
  48. package/dist/src/sgrep/isomorphisms/index.js +2 -0
  49. package/dist/src/sgrep/isomorphisms/registry.d.ts +2 -0
  50. package/dist/src/sgrep/isomorphisms/registry.js +8 -0
  51. package/dist/src/sgrep/isomorphisms/rules/commutative-binary.d.ts +2 -0
  52. package/dist/src/sgrep/isomorphisms/rules/commutative-binary.js +51 -0
  53. package/dist/src/sgrep/isomorphisms/rules/object-literal-property-order.d.ts +2 -0
  54. package/dist/src/sgrep/isomorphisms/rules/object-literal-property-order.js +82 -0
  55. package/dist/src/sgrep/isomorphisms/rules/redundant-parentheses.d.ts +2 -0
  56. package/dist/src/sgrep/isomorphisms/rules/redundant-parentheses.js +43 -0
  57. package/dist/src/sgrep/isomorphisms/template-ast.d.ts +2 -0
  58. package/dist/src/sgrep/isomorphisms/template-ast.js +59 -0
  59. package/dist/src/sgrep/isomorphisms/types.d.ts +15 -0
  60. package/dist/src/sgrep/isomorphisms/types.js +0 -0
  61. package/dist/src/sgrep/phases/output.d.ts +9 -0
  62. package/dist/src/sgrep/phases/output.js +11 -0
  63. package/dist/src/sgrep/phases/parse.d.ts +10 -0
  64. package/dist/src/sgrep/phases/parse.js +11 -0
  65. package/dist/src/sgrep/phases/search.d.ts +11 -0
  66. package/dist/src/sgrep/phases/search.js +111 -0
  67. package/dist/src/sgrep/sgrep.d.ts +3 -0
  68. package/dist/src/sgrep/sgrep.js +17 -0
  69. package/dist/src/sgrep/types.d.ts +32 -0
  70. package/dist/src/sgrep/types.js +3 -0
  71. package/dist/src/spatch/files.d.ts +7 -0
  72. package/dist/src/spatch/files.js +51 -0
  73. package/dist/src/spatch/index.d.ts +3 -0
  74. package/dist/src/spatch/index.js +2 -0
  75. package/dist/src/spatch/patch-document.d.ts +9 -0
  76. package/dist/src/spatch/patch-document.js +64 -0
  77. package/dist/src/spatch/phases/output.d.ts +9 -0
  78. package/dist/src/spatch/phases/output.js +15 -0
  79. package/dist/src/spatch/phases/parse.d.ts +11 -0
  80. package/dist/src/spatch/phases/parse.js +16 -0
  81. package/dist/src/spatch/phases/rewrite.d.ts +14 -0
  82. package/dist/src/spatch/phases/rewrite.js +111 -0
  83. package/dist/src/spatch/spatch.d.ts +3 -0
  84. package/dist/src/spatch/spatch.js +17 -0
  85. package/dist/src/spatch/template.d.ts +2 -0
  86. package/dist/src/spatch/template.js +1 -0
  87. package/dist/src/spatch/text.d.ts +5 -0
  88. package/dist/src/spatch/text.js +35 -0
  89. package/dist/src/spatch/types.d.ts +40 -0
  90. package/dist/src/spatch/types.js +20 -0
  91. package/package.json +69 -0
  92. package/skills/astkit-tooling/SKILL.md +101 -0
  93. package/skills/astkit-tooling/agents/openai.yaml +4 -0
  94. package/skills/astkit-tooling/references/cognitive-model.md +61 -0
  95. package/skills/astkit-tooling/references/non-goals.md +11 -0
@@ -0,0 +1,185 @@
1
+ import path from "node:path";
2
+ import ts from "typescript";
3
+ import { createService, fromPosition, relativePath } from "../service.js";
4
+ import { collectPatchableFiles } from "../spatch/files.js";
5
+ import { DEFAULT_EXCLUDED_DIRECTORIES, DEFAULT_PATCHABLE_EXTENSIONS, } from "../spatch/types.js";
6
+ export async function rankCode(options = {}) {
7
+ const cwd = path.resolve(options.cwd ?? process.cwd());
8
+ const scope = options.scope ?? ".";
9
+ const resolvedScope = path.resolve(cwd, scope);
10
+ const files = await collectPatchableFiles({
11
+ cwd,
12
+ scope,
13
+ extensions: options.extensions ?? DEFAULT_PATCHABLE_EXTENSIONS,
14
+ excludedDirectories: options.excludedDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES,
15
+ });
16
+ if (files.length === 0) {
17
+ return {
18
+ cwd,
19
+ scope: resolvedScope,
20
+ filesScanned: 0,
21
+ symbolsScanned: 0,
22
+ symbolsRanked: 0,
23
+ symbols: [],
24
+ };
25
+ }
26
+ const { service, program, projectRoot } = createService(cwd, files);
27
+ const checker = program.getTypeChecker();
28
+ const symbols = [];
29
+ const seenDeclarations = new Set();
30
+ for (const filePath of files) {
31
+ const sourceFile = program.getSourceFile(filePath);
32
+ if (!sourceFile) {
33
+ continue;
34
+ }
35
+ const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
36
+ if (!moduleSymbol) {
37
+ continue;
38
+ }
39
+ const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
40
+ for (const exportedSymbol of exportedSymbols) {
41
+ const declaration = pickDeclarationInFile(exportedSymbol, filePath);
42
+ if (!declaration) {
43
+ continue;
44
+ }
45
+ const declarationSourceFile = declaration.getSourceFile();
46
+ const declarationStart = declaration.getStart(declarationSourceFile);
47
+ const declarationKey = `${declarationSourceFile.fileName}:${declarationStart}:${exportedSymbol.getName()}`;
48
+ if (seenDeclarations.has(declarationKey)) {
49
+ continue;
50
+ }
51
+ seenDeclarations.add(declarationKey);
52
+ const stats = collectReferenceStats(service.findReferences(declarationSourceFile.fileName, declarationStart), declarationSourceFile.fileName, projectRoot);
53
+ const pos = fromPosition(declarationSourceFile, declarationStart);
54
+ symbols.push({
55
+ symbol: exportedSymbol.getName(),
56
+ kind: getDeclarationKind(declaration),
57
+ file: relativePath(projectRoot, declarationSourceFile.fileName) ||
58
+ path.basename(declarationSourceFile.fileName),
59
+ line: pos.line,
60
+ character: pos.character,
61
+ score: computeRankScore(stats),
62
+ referenceCount: stats.referenceCount,
63
+ internalReferenceCount: stats.internalReferenceCount,
64
+ externalReferenceCount: stats.externalReferenceCount,
65
+ referencingFileCount: stats.referencingFiles.length,
66
+ referencingFiles: stats.referencingFiles,
67
+ });
68
+ }
69
+ }
70
+ symbols.sort(compareRankedSymbols);
71
+ const limit = normalizeLimit(options.limit);
72
+ const rankedSymbols = limit === null ? symbols : symbols.slice(0, limit);
73
+ return {
74
+ cwd,
75
+ scope: resolvedScope,
76
+ filesScanned: files.length,
77
+ symbolsScanned: symbols.length,
78
+ symbolsRanked: rankedSymbols.length,
79
+ symbols: rankedSymbols,
80
+ };
81
+ }
82
+ function collectReferenceStats(references, declarationFile, projectRoot) {
83
+ if (!references || references.length === 0) {
84
+ return {
85
+ referenceCount: 0,
86
+ internalReferenceCount: 0,
87
+ externalReferenceCount: 0,
88
+ referencingFiles: [],
89
+ };
90
+ }
91
+ const seenReferences = new Set();
92
+ const referencingFiles = new Set();
93
+ const normalizedDeclarationFile = path.resolve(declarationFile);
94
+ let referenceCount = 0;
95
+ let internalReferenceCount = 0;
96
+ let externalReferenceCount = 0;
97
+ for (const group of references) {
98
+ for (const reference of group.references) {
99
+ const key = `${reference.fileName}:${reference.textSpan.start}:${reference.textSpan.length}`;
100
+ if (seenReferences.has(key)) {
101
+ continue;
102
+ }
103
+ seenReferences.add(key);
104
+ if (reference.isDefinition) {
105
+ continue;
106
+ }
107
+ referenceCount += 1;
108
+ const normalizedReferenceFile = path.resolve(reference.fileName);
109
+ if (normalizedReferenceFile === normalizedDeclarationFile) {
110
+ internalReferenceCount += 1;
111
+ }
112
+ else {
113
+ externalReferenceCount += 1;
114
+ }
115
+ referencingFiles.add(relativePath(projectRoot, reference.fileName) ||
116
+ path.basename(reference.fileName));
117
+ }
118
+ }
119
+ return {
120
+ referenceCount,
121
+ internalReferenceCount,
122
+ externalReferenceCount,
123
+ referencingFiles: [...referencingFiles].sort((left, right) => left.localeCompare(right)),
124
+ };
125
+ }
126
+ function computeRankScore(stats) {
127
+ return (stats.externalReferenceCount * 4 +
128
+ stats.referenceCount +
129
+ stats.referencingFiles.length * 2);
130
+ }
131
+ function compareRankedSymbols(left, right) {
132
+ return (right.score - left.score ||
133
+ right.externalReferenceCount - left.externalReferenceCount ||
134
+ right.referenceCount - left.referenceCount ||
135
+ left.file.localeCompare(right.file) ||
136
+ left.line - right.line ||
137
+ left.symbol.localeCompare(right.symbol));
138
+ }
139
+ function pickDeclarationInFile(symbol, filePath) {
140
+ const normalizedFilePath = path.resolve(filePath);
141
+ const declarations = symbol.getDeclarations();
142
+ if (!declarations || declarations.length === 0) {
143
+ return null;
144
+ }
145
+ for (const declaration of declarations) {
146
+ const declarationFile = path.resolve(declaration.getSourceFile().fileName);
147
+ if (declarationFile === normalizedFilePath &&
148
+ isRankableDeclaration(declaration)) {
149
+ return declaration;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ function getDeclarationKind(declaration) {
155
+ if (ts.isFunctionDeclaration(declaration))
156
+ return "function";
157
+ if (ts.isClassDeclaration(declaration))
158
+ return "class";
159
+ if (ts.isInterfaceDeclaration(declaration))
160
+ return "interface";
161
+ if (ts.isTypeAliasDeclaration(declaration))
162
+ return "type";
163
+ if (ts.isEnumDeclaration(declaration))
164
+ return "enum";
165
+ if (ts.isVariableDeclaration(declaration))
166
+ return "const";
167
+ if (ts.isModuleDeclaration(declaration))
168
+ return "module";
169
+ return "unknown";
170
+ }
171
+ function isRankableDeclaration(declaration) {
172
+ return (ts.isFunctionDeclaration(declaration) ||
173
+ ts.isClassDeclaration(declaration) ||
174
+ ts.isInterfaceDeclaration(declaration) ||
175
+ ts.isTypeAliasDeclaration(declaration) ||
176
+ ts.isEnumDeclaration(declaration) ||
177
+ ts.isVariableDeclaration(declaration) ||
178
+ ts.isModuleDeclaration(declaration));
179
+ }
180
+ function normalizeLimit(limit) {
181
+ if (limit === undefined) {
182
+ return null;
183
+ }
184
+ return Math.max(0, Math.floor(limit));
185
+ }
@@ -0,0 +1,5 @@
1
+ export type ResolveTextInputOptions = {
2
+ cwd?: string;
3
+ encoding?: BufferEncoding;
4
+ };
5
+ export declare function resolveTextInput(input: string, options?: ResolveTextInputOptions): Promise<string>;
@@ -0,0 +1,25 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function resolveTextInput(input, options = {}) {
4
+ if (input.includes("\n") || input.includes("\r")) {
5
+ return input;
6
+ }
7
+ const cwd = path.resolve(options.cwd ?? process.cwd());
8
+ const inputPath = path.resolve(cwd, input);
9
+ try {
10
+ const inputStats = await stat(inputPath);
11
+ if (!inputStats.isFile()) {
12
+ throw new Error(`Input path is not a file: ${inputPath}`);
13
+ }
14
+ return await readFile(inputPath, options.encoding ?? "utf8");
15
+ }
16
+ catch (error) {
17
+ if (isErrorWithCode(error) && error.code === "ENOENT") {
18
+ return input;
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+ function isErrorWithCode(error) {
24
+ return typeof error === "object" && error !== null && "code" in error;
25
+ }
@@ -0,0 +1,19 @@
1
+ interface MemberInfo {
2
+ name: string;
3
+ signature: string;
4
+ line: number;
5
+ }
6
+ interface DeclarationInfo {
7
+ name: string;
8
+ kind: string;
9
+ signature: string;
10
+ line: number;
11
+ members?: MemberInfo[];
12
+ }
13
+ interface DeclarationsOutput {
14
+ file: string;
15
+ declarations: DeclarationInfo[];
16
+ }
17
+ export declare function getDeclarations(filePath: string): DeclarationsOutput;
18
+ export declare const declarationsCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
19
+ export {};
@@ -0,0 +1,106 @@
1
+ import ts from "typescript";
2
+ import path from "node:path";
3
+ import { buildCommand } from "@stricli/core";
4
+ import { createService, fromPosition, relativePath } from "../service.js";
5
+ function getDeclarationKind(declaration) {
6
+ if (ts.isFunctionDeclaration(declaration))
7
+ return "function";
8
+ if (ts.isClassDeclaration(declaration))
9
+ return "class";
10
+ if (ts.isInterfaceDeclaration(declaration))
11
+ return "interface";
12
+ if (ts.isTypeAliasDeclaration(declaration))
13
+ return "type";
14
+ if (ts.isEnumDeclaration(declaration))
15
+ return "enum";
16
+ if (ts.isVariableDeclaration(declaration))
17
+ return "const";
18
+ if (ts.isModuleDeclaration(declaration))
19
+ return "module";
20
+ return "unknown";
21
+ }
22
+ export function getDeclarations(filePath) {
23
+ const resolved = path.resolve(filePath);
24
+ const { program, projectRoot } = createService(process.cwd(), resolved);
25
+ const sourceFile = program.getSourceFile(resolved);
26
+ if (!sourceFile) {
27
+ throw new Error(`File not found: ${filePath}`);
28
+ }
29
+ const typeChecker = program.getTypeChecker();
30
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
31
+ if (!moduleSymbol) {
32
+ return { file: relativePath(projectRoot, resolved), declarations: [] };
33
+ }
34
+ const exports = typeChecker.getExportsOfModule(moduleSymbol);
35
+ const declarations = [];
36
+ for (const exp of exports) {
37
+ const decls = exp.getDeclarations();
38
+ if (!decls || decls.length === 0)
39
+ continue;
40
+ const declaration = decls[0];
41
+ // Only include declarations from the target file
42
+ if (declaration.getSourceFile().fileName !== resolved)
43
+ continue;
44
+ const kind = getDeclarationKind(declaration);
45
+ const type = (kind === "interface" || kind === "class" || kind === "enum" || kind === "type")
46
+ ? typeChecker.getDeclaredTypeOfSymbol(exp)
47
+ : typeChecker.getTypeOfSymbol(exp);
48
+ const signature = typeChecker.typeToString(type, declaration, ts.TypeFormatFlags.NoTruncation);
49
+ const pos = fromPosition(sourceFile, declaration.getStart(sourceFile));
50
+ const info = {
51
+ name: exp.getName(),
52
+ kind,
53
+ signature,
54
+ line: pos.line,
55
+ };
56
+ // For classes and interfaces, enumerate members
57
+ if (kind === "class" || kind === "interface") {
58
+ const declType = typeChecker.getDeclaredTypeOfSymbol(exp);
59
+ const properties = typeChecker.getPropertiesOfType(declType);
60
+ const members = [];
61
+ for (const prop of properties) {
62
+ const propDecls = prop.getDeclarations();
63
+ if (!propDecls || propDecls.length === 0)
64
+ continue;
65
+ const propDecl = propDecls[0];
66
+ // Only include members from this file
67
+ if (propDecl.getSourceFile().fileName !== resolved)
68
+ continue;
69
+ const propType = typeChecker.getTypeOfSymbol(prop);
70
+ const propSignature = typeChecker.typeToString(propType, propDecl, ts.TypeFormatFlags.NoTruncation);
71
+ const propPos = fromPosition(sourceFile, propDecl.getStart(sourceFile));
72
+ members.push({
73
+ name: prop.getName(),
74
+ signature: propSignature,
75
+ line: propPos.line,
76
+ });
77
+ }
78
+ if (members.length > 0) {
79
+ info.members = members;
80
+ }
81
+ }
82
+ declarations.push(info);
83
+ }
84
+ return { file: relativePath(projectRoot, resolved), declarations };
85
+ }
86
+ export const declarationsCommand = buildCommand({
87
+ func(_flags, file) {
88
+ const result = getDeclarations(file);
89
+ this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
90
+ },
91
+ parameters: {
92
+ positional: {
93
+ kind: "tuple",
94
+ parameters: [
95
+ {
96
+ brief: "File to list declarations from",
97
+ placeholder: "file",
98
+ parse: (input) => input,
99
+ },
100
+ ],
101
+ },
102
+ },
103
+ docs: {
104
+ brief: "List exported declarations and type signatures",
105
+ },
106
+ });
@@ -0,0 +1,14 @@
1
+ interface DefinitionLocation {
2
+ file: string;
3
+ line: number;
4
+ character: number;
5
+ kind: string;
6
+ containerName: string;
7
+ }
8
+ interface DefinitionOutput {
9
+ symbol: string;
10
+ definitions: DefinitionLocation[];
11
+ }
12
+ export declare function getDefinition(filePath: string, line: number, character: number): DefinitionOutput;
13
+ export declare const definitionCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
14
+ export {};
@@ -0,0 +1,62 @@
1
+ import path from "node:path";
2
+ import { buildCommand } from "@stricli/core";
3
+ import { parseFilePosition } from "./location.js";
4
+ import { createService, toPosition, fromPosition, relativePath } from "../service.js";
5
+ export function getDefinition(filePath, line, character) {
6
+ const resolved = path.resolve(filePath);
7
+ const { service, program, projectRoot } = createService(process.cwd(), resolved);
8
+ const sourceFile = program.getSourceFile(resolved);
9
+ if (!sourceFile) {
10
+ throw new Error(`File not found: ${filePath}`);
11
+ }
12
+ const pos = toPosition(sourceFile, line, character);
13
+ const defs = service.getDefinitionAtPosition(resolved, pos);
14
+ if (!defs || defs.length === 0) {
15
+ // Get the word at position for the symbol name
16
+ const wordRange = service.getSmartSelectionRange(resolved, pos);
17
+ const symbolName = wordRange
18
+ ? sourceFile.text.slice(wordRange.textSpan.start, wordRange.textSpan.start + wordRange.textSpan.length)
19
+ : "<unknown>";
20
+ return { symbol: symbolName, definitions: [] };
21
+ }
22
+ const symbol = defs[0].name || "<unknown>";
23
+ const definitions = defs.map((def) => {
24
+ const defSourceFile = program.getSourceFile(def.fileName);
25
+ let defLine = 1;
26
+ let defChar = 1;
27
+ if (defSourceFile) {
28
+ const lc = fromPosition(defSourceFile, def.textSpan.start);
29
+ defLine = lc.line;
30
+ defChar = lc.character;
31
+ }
32
+ return {
33
+ file: relativePath(projectRoot, def.fileName),
34
+ line: defLine,
35
+ character: defChar,
36
+ kind: def.kind,
37
+ containerName: def.containerName || "",
38
+ };
39
+ });
40
+ return { symbol, definitions };
41
+ }
42
+ export const definitionCommand = buildCommand({
43
+ func(_flags, location) {
44
+ const result = getDefinition(location.file, location.line, location.character);
45
+ this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
46
+ },
47
+ parameters: {
48
+ positional: {
49
+ kind: "tuple",
50
+ parameters: [
51
+ {
52
+ brief: "Location (<file>:<line>:<character>)",
53
+ placeholder: "location",
54
+ parse: parseFilePosition,
55
+ },
56
+ ],
57
+ },
58
+ },
59
+ docs: {
60
+ brief: "Go to definition at position",
61
+ },
62
+ });
@@ -0,0 +1,6 @@
1
+ export type FilePosition = {
2
+ file: string;
3
+ line: number;
4
+ character: number;
5
+ };
6
+ export declare function parseFilePosition(input: string): FilePosition;
@@ -0,0 +1,35 @@
1
+ import { eof, map, regex as parseRegex, seq, str, } from "@claudiu-ceia/combine";
2
+ const LOCATION_ERROR_MESSAGE = "Invalid location: expected <file>:<line>:<character>.";
3
+ const positiveIntegerParser = parseRegex(/[1-9][0-9]*/, "positive integer");
4
+ const fileForColonSyntaxParser = parseRegex(/[^\n]+(?=:[1-9][0-9]*:[1-9][0-9]*$)/, "file path");
5
+ const colonFilePositionParser = map(seq(fileForColonSyntaxParser, str(":"), positiveIntegerParser, str(":"), positiveIntegerParser, eof()), ([file, , line, , character]) => ({
6
+ file: file.trim(),
7
+ line: parsePositiveInteger(line, "line"),
8
+ character: parsePositiveInteger(character, "character"),
9
+ }));
10
+ export function parseFilePosition(input) {
11
+ const raw = input.trim();
12
+ if (raw.length === 0) {
13
+ throw new Error(LOCATION_ERROR_MESSAGE);
14
+ }
15
+ const parsed = colonFilePositionParser({ text: raw, index: 0 });
16
+ if (!parsed.success) {
17
+ throw new Error(LOCATION_ERROR_MESSAGE);
18
+ }
19
+ const file = parsed.value.file.trim();
20
+ if (file.length === 0) {
21
+ throw new Error("Invalid file: file path cannot be empty.");
22
+ }
23
+ return {
24
+ file,
25
+ line: parsed.value.line,
26
+ character: parsed.value.character,
27
+ };
28
+ }
29
+ function parsePositiveInteger(raw, label) {
30
+ const parsed = Number.parseInt(raw, 10);
31
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
32
+ throw new Error(`Invalid ${label}: expected a positive integer.`);
33
+ }
34
+ return parsed;
35
+ }
@@ -0,0 +1,19 @@
1
+ interface ReferenceLocation {
2
+ file: string;
3
+ line: number;
4
+ character: number;
5
+ isDefinition: boolean;
6
+ isWriteAccess: boolean;
7
+ }
8
+ interface ReferencesOutput {
9
+ symbol: string;
10
+ definition: {
11
+ file: string;
12
+ line: number;
13
+ character: number;
14
+ } | null;
15
+ references: ReferenceLocation[];
16
+ }
17
+ export declare function getReferences(filePath: string, line: number, character: number): ReferencesOutput;
18
+ export declare const referencesCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
19
+ export {};
@@ -0,0 +1,75 @@
1
+ import path from "node:path";
2
+ import { buildCommand } from "@stricli/core";
3
+ import { parseFilePosition } from "./location.js";
4
+ import { createService, toPosition, fromPosition, relativePath } from "../service.js";
5
+ export function getReferences(filePath, line, character) {
6
+ const resolved = path.resolve(filePath);
7
+ const { service, program, projectRoot } = createService(process.cwd(), resolved);
8
+ const sourceFile = program.getSourceFile(resolved);
9
+ if (!sourceFile) {
10
+ throw new Error(`File not found: ${filePath}`);
11
+ }
12
+ const pos = toPosition(sourceFile, line, character);
13
+ const refSymbols = service.findReferences(resolved, pos);
14
+ if (!refSymbols || refSymbols.length === 0) {
15
+ const wordRange = service.getSmartSelectionRange(resolved, pos);
16
+ const symbolName = wordRange
17
+ ? sourceFile.text.slice(wordRange.textSpan.start, wordRange.textSpan.start + wordRange.textSpan.length)
18
+ : "<unknown>";
19
+ return { symbol: symbolName, definition: null, references: [] };
20
+ }
21
+ const firstGroup = refSymbols[0];
22
+ const symbol = firstGroup.definition.name || "<unknown>";
23
+ // Extract definition location
24
+ const defSpan = firstGroup.definition.textSpan;
25
+ const defFile = firstGroup.definition.fileName;
26
+ const defSourceFile = program.getSourceFile(defFile);
27
+ let definition = null;
28
+ if (defSourceFile) {
29
+ const defPos = fromPosition(defSourceFile, defSpan.start);
30
+ definition = {
31
+ file: relativePath(projectRoot, defFile),
32
+ line: defPos.line,
33
+ character: defPos.character,
34
+ };
35
+ }
36
+ // Extract all references
37
+ const references = [];
38
+ for (const group of refSymbols) {
39
+ for (const ref of group.references) {
40
+ const refSourceFile = program.getSourceFile(ref.fileName);
41
+ if (!refSourceFile)
42
+ continue;
43
+ const refPos = fromPosition(refSourceFile, ref.textSpan.start);
44
+ references.push({
45
+ file: relativePath(projectRoot, ref.fileName),
46
+ line: refPos.line,
47
+ character: refPos.character,
48
+ isDefinition: ref.isDefinition ?? false,
49
+ isWriteAccess: ref.isWriteAccess ?? false,
50
+ });
51
+ }
52
+ }
53
+ return { symbol, definition, references };
54
+ }
55
+ export const referencesCommand = buildCommand({
56
+ func(_flags, location) {
57
+ const result = getReferences(location.file, location.line, location.character);
58
+ this.process.stdout.write(JSON.stringify(result, null, 2) + "\n");
59
+ },
60
+ parameters: {
61
+ positional: {
62
+ kind: "tuple",
63
+ parameters: [
64
+ {
65
+ brief: "Location (<file>:<line>:<character>)",
66
+ placeholder: "location",
67
+ parse: parseFilePosition,
68
+ },
69
+ ],
70
+ },
71
+ },
72
+ docs: {
73
+ brief: "Find all references at position",
74
+ },
75
+ });
@@ -0,0 +1,27 @@
1
+ import { type ChalkInstance } from "chalk";
2
+ import type { SpatchOccurrence, SpatchResult } from "../spatch/types.ts";
3
+ export type PatchCommandFlags = {
4
+ "dry-run"?: boolean;
5
+ interactive?: boolean;
6
+ json?: boolean;
7
+ "no-color"?: boolean;
8
+ cwd?: string;
9
+ };
10
+ type InteractiveChoice = "yes" | "no" | "all" | "quit";
11
+ export type InteractiveContext = {
12
+ file: string;
13
+ occurrence: SpatchOccurrence;
14
+ changeNumber: number;
15
+ totalChanges: number;
16
+ };
17
+ export type RunPatchCommandOptions = {
18
+ interactiveDecider?: (ctx: InteractiveContext) => Promise<InteractiveChoice>;
19
+ };
20
+ export declare function runPatchCommand(patchInput: string, scope: string | undefined, flags: PatchCommandFlags, options?: RunPatchCommandOptions): Promise<SpatchResult>;
21
+ type FormatPatchOutputOptions = {
22
+ color?: boolean;
23
+ chalkInstance?: ChalkInstance;
24
+ };
25
+ export declare function formatPatchOutput(result: SpatchResult, options?: FormatPatchOutputOptions): string;
26
+ export declare const patchCommand: import("@stricli/core").Command<import("@stricli/core").CommandContext>;
27
+ export {};