@functional-systems/srcx 0.1.0-dev.4 → 0.1.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +179 -0
- package/CONTRIBUTING.md +4 -0
- package/DESIGN.md +378 -58
- package/README.md +628 -35
- package/RELEASING.md +15 -3
- package/VALIDATION.md +1120 -16
- package/dist/artifact.d.ts +31 -0
- package/dist/artifact.js +237 -0
- package/dist/artifact.js.map +1 -0
- package/dist/build.d.ts +86 -3
- package/dist/build.js +233 -71
- package/dist/build.js.map +1 -1
- package/dist/chunk.d.ts +9 -3
- package/dist/chunk.js +341 -14
- package/dist/chunk.js.map +1 -1
- package/dist/cli.js +130 -21
- package/dist/cli.js.map +1 -1
- package/dist/file-policy.d.ts +20 -0
- package/dist/file-policy.js +103 -0
- package/dist/file-policy.js.map +1 -0
- package/dist/implementation.d.ts +11 -0
- package/dist/implementation.js +191 -0
- package/dist/implementation.js.map +1 -0
- package/dist/publish.d.ts +18 -0
- package/dist/publish.js +224 -46
- package/dist/publish.js.map +1 -1
- package/dist/releases.js +21 -3
- package/dist/releases.js.map +1 -1
- package/dist/remote.d.ts +1 -0
- package/dist/remote.js +50 -9
- package/dist/remote.js.map +1 -1
- package/dist/repository.d.ts +4 -1
- package/dist/repository.js +26 -11
- package/dist/repository.js.map +1 -1
- package/dist/rerank.d.ts +16 -0
- package/dist/rerank.js +87 -0
- package/dist/rerank.js.map +1 -0
- package/dist/search.d.ts +18 -3
- package/dist/search.js +143 -23
- package/dist/search.js.map +1 -1
- package/dist/skills.d.ts +24 -0
- package/dist/skills.js +155 -0
- package/dist/skills.js.map +1 -0
- package/package.json +17 -6
- package/runtime/grammars/README.md +50 -0
- package/runtime/grammars/tree-sitter-bash.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-bash.wasm +0 -0
- package/runtime/grammars/tree-sitter-sql.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-sql.wasm +0 -0
- package/runtime/grammars/tree-sitter-wasm.LICENSE +21 -0
- package/runtime/qwen.py +132 -0
- package/runtime/requirements.txt +34 -0
- package/skills/srcx-search/SKILL.md +103 -0
- package/src/artifact.ts +303 -0
- package/src/build.ts +313 -84
- package/src/chunk.ts +360 -12
- package/src/cli.ts +263 -23
- package/src/file-policy.ts +166 -0
- package/src/implementation.ts +253 -0
- package/src/publish.ts +321 -47
- package/src/releases.ts +36 -3
- package/src/remote.ts +70 -13
- package/src/repository.ts +40 -11
- package/src/rerank.ts +131 -0
- package/src/search.ts +229 -28
- package/src/skills.ts +205 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { Node } from "web-tree-sitter";
|
|
2
|
+
import { CHUNKER, detectLanguage, tokens, withSyntaxTree } from "./chunk.js";
|
|
3
|
+
import { invariant, lineAt } from "./common.js";
|
|
4
|
+
|
|
5
|
+
export type Implementation = {
|
|
6
|
+
status:
|
|
7
|
+
"resolved" | "not-linked" | "unsupported" | "parse-error" | "too-large";
|
|
8
|
+
startLine?: number;
|
|
9
|
+
endLine?: number;
|
|
10
|
+
symbol?: string;
|
|
11
|
+
};
|
|
12
|
+
type Definition = {
|
|
13
|
+
node: Node;
|
|
14
|
+
name: string;
|
|
15
|
+
key: string;
|
|
16
|
+
declaration: boolean;
|
|
17
|
+
};
|
|
18
|
+
const children = (n: Node): Node[] =>
|
|
19
|
+
n.namedChildren.filter((c): c is Node => !!c);
|
|
20
|
+
|
|
21
|
+
/** Only imported typing overload aliases used exclusively as decorators are safe here. */
|
|
22
|
+
function overloadAliases(root: Node): Set<string> {
|
|
23
|
+
const aliases = new Set<string>(),
|
|
24
|
+
imports = new Set<number>();
|
|
25
|
+
for (const n of children(root)) {
|
|
26
|
+
if (
|
|
27
|
+
n.type !== "import_from_statement" ||
|
|
28
|
+
!["typing", "typing_extensions"].includes(
|
|
29
|
+
n.childForFieldName("module_name")?.text ?? "",
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
continue;
|
|
33
|
+
for (const name of n.childrenForFieldName("name")) {
|
|
34
|
+
if (!name) continue;
|
|
35
|
+
if (name.type === "dotted_name" && name.text === "overload") {
|
|
36
|
+
aliases.add("overload");
|
|
37
|
+
imports.add(name.id);
|
|
38
|
+
} else if (
|
|
39
|
+
name.type === "aliased_import" &&
|
|
40
|
+
name.childForFieldName("name")?.text === "overload"
|
|
41
|
+
) {
|
|
42
|
+
const alias = name.childForFieldName("alias");
|
|
43
|
+
if (alias) {
|
|
44
|
+
aliases.add(alias.text);
|
|
45
|
+
imports.add(name.id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function visit(n: Node): void {
|
|
51
|
+
if (imports.has(n.id)) return;
|
|
52
|
+
if (n.type === "wildcard_import") aliases.clear();
|
|
53
|
+
if (
|
|
54
|
+
n.type === "identifier" &&
|
|
55
|
+
aliases.has(n.text) &&
|
|
56
|
+
!(n.parent?.type === "decorator" && n.parent.text === `@${n.text}`)
|
|
57
|
+
)
|
|
58
|
+
aliases.delete(n.text);
|
|
59
|
+
for (const child of children(n)) visit(child);
|
|
60
|
+
}
|
|
61
|
+
visit(root);
|
|
62
|
+
return aliases;
|
|
63
|
+
}
|
|
64
|
+
function definition(
|
|
65
|
+
n: Node,
|
|
66
|
+
python: boolean,
|
|
67
|
+
aliases: Set<string>,
|
|
68
|
+
): Definition | undefined {
|
|
69
|
+
const d = n.childForFieldName(python ? "definition" : "declaration") ?? n;
|
|
70
|
+
const name = d.childForFieldName("name");
|
|
71
|
+
if (
|
|
72
|
+
!name ||
|
|
73
|
+
![
|
|
74
|
+
"identifier",
|
|
75
|
+
"property_identifier",
|
|
76
|
+
"private_property_identifier",
|
|
77
|
+
].includes(name.type)
|
|
78
|
+
)
|
|
79
|
+
return;
|
|
80
|
+
if (python) {
|
|
81
|
+
if (d.type !== "function_definition") return;
|
|
82
|
+
const decorators = children(n).filter((c) => c.type === "decorator");
|
|
83
|
+
const declaration = decorators.some((c) => aliases.has(c.text.slice(1)));
|
|
84
|
+
const body = d.childForFieldName("body");
|
|
85
|
+
// Stubs without an overload marker do not supply an implementation.
|
|
86
|
+
const executable =
|
|
87
|
+
body &&
|
|
88
|
+
children(body).some(
|
|
89
|
+
(c) =>
|
|
90
|
+
c.type !== "comment" &&
|
|
91
|
+
c.type !== "pass_statement" &&
|
|
92
|
+
!(
|
|
93
|
+
c.type === "expression_statement" &&
|
|
94
|
+
["ellipsis", "string"].includes(c.firstNamedChild?.type ?? "")
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
if (!declaration && !executable) return;
|
|
98
|
+
// Other decorators may change dispatch (property, singledispatch, etc.).
|
|
99
|
+
if (
|
|
100
|
+
decorators.some(
|
|
101
|
+
(c) =>
|
|
102
|
+
!aliases.has(c.text.slice(1)) &&
|
|
103
|
+
!["@staticmethod", "@classmethod"].includes(c.text),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
return;
|
|
107
|
+
const modifier = decorators
|
|
108
|
+
.filter((c) => ["@staticmethod", "@classmethod"].includes(c.text))
|
|
109
|
+
.map((c) => c.text)
|
|
110
|
+
.sort()
|
|
111
|
+
.join(":");
|
|
112
|
+
const async = d.children.some((c) => c?.type === "async");
|
|
113
|
+
return {
|
|
114
|
+
node: n,
|
|
115
|
+
name: name.text,
|
|
116
|
+
key: `${name.text}:${modifier}:${async}`,
|
|
117
|
+
declaration,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (n !== d && n.type !== "export_statement") return;
|
|
121
|
+
if (
|
|
122
|
+
![
|
|
123
|
+
"function_signature",
|
|
124
|
+
"function_declaration",
|
|
125
|
+
"method_signature",
|
|
126
|
+
"method_definition",
|
|
127
|
+
].includes(d.type)
|
|
128
|
+
)
|
|
129
|
+
return;
|
|
130
|
+
const prefix = d.text.slice(0, name.startIndex - d.startIndex);
|
|
131
|
+
if (/\b(abstract|get|set|declare)\b/.test(prefix)) return;
|
|
132
|
+
const method = d.type.startsWith("method");
|
|
133
|
+
const declaration = d.type.endsWith("signature");
|
|
134
|
+
if (!declaration && !d.childForFieldName("body")) return;
|
|
135
|
+
return {
|
|
136
|
+
node: n,
|
|
137
|
+
name: name.text,
|
|
138
|
+
key: `${method}:${/\bstatic\b/.test(prefix)}:${name.text}`,
|
|
139
|
+
declaration,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Follow only a contiguous, unambiguous overload group in the same AST container. */
|
|
144
|
+
export async function implementationSpan(
|
|
145
|
+
source: string,
|
|
146
|
+
path: string,
|
|
147
|
+
span: { startByte: number; endByte: number },
|
|
148
|
+
): Promise<Implementation> {
|
|
149
|
+
const raw = Buffer.from(source),
|
|
150
|
+
lang = detectLanguage(path);
|
|
151
|
+
invariant(
|
|
152
|
+
Number.isInteger(span.startByte) &&
|
|
153
|
+
Number.isInteger(span.endByte) &&
|
|
154
|
+
span.startByte >= 0 &&
|
|
155
|
+
span.endByte > span.startByte &&
|
|
156
|
+
span.endByte <= raw.length,
|
|
157
|
+
"Invalid implementation source range.",
|
|
158
|
+
);
|
|
159
|
+
if (
|
|
160
|
+
!["python", "typescript", "tsx"].includes(lang) ||
|
|
161
|
+
/\.(pyi|d\.ts|d\.tsx)$/i.test(path)
|
|
162
|
+
)
|
|
163
|
+
return { status: "unsupported" };
|
|
164
|
+
const start = raw.subarray(0, span.startByte).toString("utf8").length;
|
|
165
|
+
const end = raw.subarray(0, span.endByte).toString("utf8").length;
|
|
166
|
+
return withSyntaxTree(source, lang, (root) => {
|
|
167
|
+
if (root.hasError) return { status: "parse-error" };
|
|
168
|
+
const python = lang === "python",
|
|
169
|
+
aliases = python ? overloadAliases(root) : new Set<string>();
|
|
170
|
+
const targets: Definition[] = [];
|
|
171
|
+
function visit(container: Node): void {
|
|
172
|
+
const siblings = children(container).filter((n) => n.type !== "comment");
|
|
173
|
+
const definitions = siblings.map((n) => definition(n, python, aliases));
|
|
174
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
175
|
+
const d = definitions[i];
|
|
176
|
+
// A class/container hit is not a declaration hit. Require the selected
|
|
177
|
+
// span to end before the implementation, and contain a whole declaration.
|
|
178
|
+
if (
|
|
179
|
+
!d?.declaration ||
|
|
180
|
+
d.node.startIndex < start ||
|
|
181
|
+
d.node.endIndex > end
|
|
182
|
+
)
|
|
183
|
+
continue;
|
|
184
|
+
let first = i;
|
|
185
|
+
while (
|
|
186
|
+
first > 0 &&
|
|
187
|
+
definitions[first - 1]?.declaration &&
|
|
188
|
+
definitions[first - 1]?.key === d.key
|
|
189
|
+
)
|
|
190
|
+
first--;
|
|
191
|
+
let next = first;
|
|
192
|
+
while (
|
|
193
|
+
definitions[next]?.declaration &&
|
|
194
|
+
definitions[next]?.key === d.key
|
|
195
|
+
)
|
|
196
|
+
next++;
|
|
197
|
+
const target = definitions[next];
|
|
198
|
+
if (
|
|
199
|
+
!target ||
|
|
200
|
+
target.declaration ||
|
|
201
|
+
target.key !== d.key ||
|
|
202
|
+
target.node.startIndex < end
|
|
203
|
+
)
|
|
204
|
+
continue;
|
|
205
|
+
const matches = siblings.filter((n) => {
|
|
206
|
+
const node =
|
|
207
|
+
n.childForFieldName(python ? "definition" : "declaration") ?? n;
|
|
208
|
+
// TypeScript namespaces augment a function without adding another body.
|
|
209
|
+
// Keep other same-name declarations in the ambiguity check.
|
|
210
|
+
if (!python && node.type === "internal_module") return false;
|
|
211
|
+
return node.childForFieldName("name")?.text === d.name;
|
|
212
|
+
});
|
|
213
|
+
if (matches.length !== next - first + 1) continue;
|
|
214
|
+
// Reject unrelated code included in the selected chunk, but allow trivia
|
|
215
|
+
// between declarations (the chunker can attach comments/whitespace).
|
|
216
|
+
const selected = siblings.filter(
|
|
217
|
+
(n) => n.startIndex < end && n.endIndex > start,
|
|
218
|
+
);
|
|
219
|
+
if (
|
|
220
|
+
selected.some(
|
|
221
|
+
(n) =>
|
|
222
|
+
!definitions.slice(first, next).some((x) => x?.node.id === n.id),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
continue;
|
|
226
|
+
if (!targets.some((t) => t.node.id === target.node.id))
|
|
227
|
+
targets.push(target);
|
|
228
|
+
}
|
|
229
|
+
for (const child of siblings) visit(child);
|
|
230
|
+
}
|
|
231
|
+
visit(root);
|
|
232
|
+
if (targets.length !== 1) return { status: "not-linked" };
|
|
233
|
+
const target = targets[0]!;
|
|
234
|
+
const startLine = lineAt(
|
|
235
|
+
raw,
|
|
236
|
+
Buffer.byteLength(source.slice(0, target.node.startIndex)),
|
|
237
|
+
);
|
|
238
|
+
const endLine = lineAt(
|
|
239
|
+
raw,
|
|
240
|
+
Buffer.byteLength(source.slice(0, target.node.endIndex)) - 1,
|
|
241
|
+
);
|
|
242
|
+
const text = (source.match(/[^\n]*\n|[^\n]+$/g) ?? [])
|
|
243
|
+
.slice(startLine - 1, endLine)
|
|
244
|
+
.join("");
|
|
245
|
+
if (tokens(text) > CHUNKER.maxTokens) return { status: "too-large" };
|
|
246
|
+
return {
|
|
247
|
+
status: "resolved",
|
|
248
|
+
startLine,
|
|
249
|
+
endLine,
|
|
250
|
+
symbol: target.name,
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
}
|