@ngockhoale/ukit 2.6.7 → 2.6.8
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/manifests/documentation.yaml +77 -6
- package/manifests/hostCapabilities.yaml +49 -0
- package/manifests/instructionRules.yaml +7 -0
- package/package.json +1 -1
- package/scripts/bench/goldTasks.json +38 -0
- package/scripts/bench/runGold.mjs +220 -0
- package/scripts/release/verify-release.mjs +6 -0
- package/src/cli/commands/code.js +182 -0
- package/src/cli/commands/doctor.js +35 -3
- package/src/cli/commands/indexTools.js +102 -1
- package/src/cli/commands/memory.js +137 -0
- package/src/cli/index.js +7 -0
- package/src/core/codeintel/compiler.js +316 -0
- package/src/core/codeintel/diagnostics.js +114 -0
- package/src/core/codeintel/freshness.js +295 -0
- package/src/core/codeintel/impact.js +251 -0
- package/src/core/codeintel/invalidation.js +150 -0
- package/src/core/codeintel/manifest.js +176 -0
- package/src/core/codeintel/packet.js +146 -0
- package/src/core/codeintel/providers.js +201 -0
- package/src/core/codeintel/retriever.js +372 -0
- package/src/core/codeintel/router.js +149 -0
- package/src/core/codeintel/semanticProvider.js +235 -0
- package/src/core/docContracts.js +723 -0
- package/src/core/memory/migrate.js +324 -0
- package/src/core/memory/records.js +172 -0
- package/src/core/memory/retrieval.js +161 -11
- package/src/core/memory/store.js +398 -0
- package/src/core/memory/storeV2.js +171 -0
- package/src/core/memory/storeV2Loader.js +22 -0
- package/src/core/runtimeConfig.js +125 -0
- package/src/core/runtimePaths.js +3 -0
- package/src/index/taskRouting.js +39 -0
- package/templates/.claude/ukit/index/route-task.mjs +40 -0
- package/templates/AGENTS.md +46 -99
- package/templates/CLAUDE.md +46 -99
- package/templates/docs/AI_HANDOFF/tasks/_TEMPLATE.md +5 -0
- package/templates/docs/BUGFIX.md +2 -19
- package/templates/docs/BUG_INDEX.md +43 -0
- package/templates/docs/BUG_METRICS.md +1 -5
- package/templates/docs/BUG_TEMPLATE.md +1 -11
- package/templates/docs/UKIT_INTERNALS.md +4 -0
- package/templates/instructions/core.md +46 -99
- package/templates/ukit/storage/config.json +30 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { getArtifactPath, INDEX_ARTIFACTS } from '../../index/paths.js';
|
|
5
|
+
import { createEdge, NullSemanticProvider } from './providers.js';
|
|
6
|
+
|
|
7
|
+
const NO_CAPS = Object.freeze({ defs: false, refs: false, types: false });
|
|
8
|
+
const ALL_CAPS = Object.freeze({ defs: true, refs: true, types: true });
|
|
9
|
+
const DEFAULT_LIMIT = 50;
|
|
10
|
+
const MAX_FILES = 200;
|
|
11
|
+
const SEMANTIC_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts']);
|
|
12
|
+
|
|
13
|
+
function defaultLoader() {
|
|
14
|
+
return import('typescript');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isSemanticFile(filePath) {
|
|
18
|
+
return typeof filePath === 'string' && SEMANTIC_EXTS.has(path.extname(filePath));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* SemanticProvider backed by the TypeScript language service, loaded via an
|
|
23
|
+
* injectable async loader (default: dynamic `import('typescript')`). When the
|
|
24
|
+
* loader fails — package absent, throw, any reason — the provider degrades to
|
|
25
|
+
* `available()===false`, `resolve()→null`, all-false capabilities. Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export class TypeScriptSemanticProvider {
|
|
28
|
+
constructor({ projectRoot = process.cwd(), loader = defaultLoader } = {}) {
|
|
29
|
+
this.name = 'typescript-semantic';
|
|
30
|
+
this.projectRoot = projectRoot;
|
|
31
|
+
this._loader = loader;
|
|
32
|
+
this._probe = null;
|
|
33
|
+
this._caps = NO_CAPS;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#probeTs() {
|
|
37
|
+
if (!this._probe) {
|
|
38
|
+
this._probe = Promise.resolve()
|
|
39
|
+
.then(() => this._loader())
|
|
40
|
+
.then((ts) => {
|
|
41
|
+
this._caps = ts ? ALL_CAPS : NO_CAPS;
|
|
42
|
+
return ts ?? null;
|
|
43
|
+
})
|
|
44
|
+
.catch(() => {
|
|
45
|
+
this._caps = NO_CAPS;
|
|
46
|
+
return null;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return this._probe;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async available() {
|
|
53
|
+
return (await this.#probeTs()) !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
capabilities() {
|
|
57
|
+
return { ...this._caps };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ctx = { file?, mode?: 'definition'|'references'|'both', limit?, snapshot? }
|
|
62
|
+
* → edge[] (createEdge shape) | null when unavailable or on any failure.
|
|
63
|
+
*/
|
|
64
|
+
async resolve(symbol, ctx = {}) {
|
|
65
|
+
try {
|
|
66
|
+
const ts = await this.#probeTs();
|
|
67
|
+
if (!ts || typeof symbol !== 'string' || symbol.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const safeCtx = ctx && typeof ctx === 'object' ? ctx : {};
|
|
71
|
+
const mode = safeCtx.mode ?? 'both';
|
|
72
|
+
const limit = Number.isInteger(safeCtx.limit) && safeCtx.limit > 0 ? safeCtx.limit : DEFAULT_LIMIT;
|
|
73
|
+
const files = this.#boundedFiles(safeCtx);
|
|
74
|
+
if (files.length === 0) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return this.#resolveWith(ts, symbol, files, mode, limit, safeCtx.snapshot ?? null);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#boundedFiles(ctx) {
|
|
84
|
+
if (isSemanticFile(ctx.file)) {
|
|
85
|
+
return [path.resolve(this.projectRoot, ctx.file)];
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const filesPath = getArtifactPath(this.projectRoot, INDEX_ARTIFACTS.files);
|
|
89
|
+
const raw = JSON.parse(fs.readFileSync(filesPath, 'utf8'));
|
|
90
|
+
const list = Array.isArray(raw) ? raw : (raw.files ?? []);
|
|
91
|
+
return list
|
|
92
|
+
.map((entry) => (typeof entry === 'string' ? entry : entry?.filePath ?? entry?.path))
|
|
93
|
+
.filter(isSemanticFile)
|
|
94
|
+
.slice(0, MAX_FILES)
|
|
95
|
+
.map((file) => path.resolve(this.projectRoot, file));
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#resolveWith(ts, symbol, files, mode, limit, snapshot) {
|
|
102
|
+
const sources = new Map();
|
|
103
|
+
const readSource = (fileName) => {
|
|
104
|
+
if (!sources.has(fileName)) {
|
|
105
|
+
try {
|
|
106
|
+
sources.set(fileName, fs.readFileSync(fileName, 'utf8'));
|
|
107
|
+
} catch {
|
|
108
|
+
sources.set(fileName, null);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return sources.get(fileName);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const host = {
|
|
115
|
+
getScriptFileNames: () => files,
|
|
116
|
+
getScriptVersion: () => '1',
|
|
117
|
+
getScriptSnapshot: (fileName) => {
|
|
118
|
+
const text = readSource(fileName);
|
|
119
|
+
if (text === null) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return ts.ScriptSnapshot?.fromString ? ts.ScriptSnapshot.fromString(text) : text;
|
|
123
|
+
},
|
|
124
|
+
getCurrentDirectory: () => this.projectRoot,
|
|
125
|
+
getCompilationSettings: () => ({ allowJs: true, checkJs: false }),
|
|
126
|
+
getDefaultLibFileName: () => 'lib.d.ts',
|
|
127
|
+
readFile: (fileName) => readSource(fileName) ?? undefined,
|
|
128
|
+
fileExists: (fileName) => readSource(fileName) !== null,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let service = null;
|
|
132
|
+
const edges = [];
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
const pushEdge = (kind, fileName, start) => {
|
|
135
|
+
if (edges.length >= limit) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const source = readSource(fileName);
|
|
139
|
+
const line = source === null ? 0 : lineOf(source, start);
|
|
140
|
+
const key = `${kind}:${fileName}:${start}`;
|
|
141
|
+
if (seen.has(key)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
seen.add(key);
|
|
145
|
+
edges.push(createEdge({
|
|
146
|
+
from: symbol,
|
|
147
|
+
to: `${fileName}:${line}`,
|
|
148
|
+
kind,
|
|
149
|
+
confidence: 1,
|
|
150
|
+
provider: this.name,
|
|
151
|
+
evidence: 'typescript-ls',
|
|
152
|
+
snapshot,
|
|
153
|
+
}));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
service = ts.createLanguageService(host);
|
|
158
|
+
const wantsDefs = mode === 'definition' || mode === 'both';
|
|
159
|
+
const wantsRefs = mode === 'references' || mode === 'both';
|
|
160
|
+
for (const fileName of files) {
|
|
161
|
+
if (edges.length >= limit) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
const source = readSource(fileName);
|
|
165
|
+
if (source === null) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
for (const pos of occurrencesOf(source, symbol)) {
|
|
169
|
+
if (edges.length >= limit) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
if (wantsDefs && typeof service.getDefinitionAtPosition === 'function') {
|
|
173
|
+
for (const entry of service.getDefinitionAtPosition(fileName, pos) ?? []) {
|
|
174
|
+
if (entry?.fileName && entry?.textSpan) {
|
|
175
|
+
pushEdge('def', entry.fileName, entry.textSpan.start);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (wantsRefs && typeof service.findReferences === 'function') {
|
|
180
|
+
for (const group of service.findReferences(fileName, pos) ?? []) {
|
|
181
|
+
for (const entry of group?.references ?? []) {
|
|
182
|
+
if (entry?.fileName && entry?.textSpan) {
|
|
183
|
+
pushEdge('ref', entry.fileName, entry.textSpan.start);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} finally {
|
|
191
|
+
try {
|
|
192
|
+
service?.dispose?.();
|
|
193
|
+
} catch {
|
|
194
|
+
// dispose is best-effort
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return edges;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function occurrencesOf(source, symbol) {
|
|
202
|
+
const positions = [];
|
|
203
|
+
const pattern = new RegExp(`\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g');
|
|
204
|
+
let match;
|
|
205
|
+
while ((match = pattern.exec(source)) !== null && positions.length < 50) {
|
|
206
|
+
positions.push(match.index);
|
|
207
|
+
}
|
|
208
|
+
return positions;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function lineOf(source, offset) {
|
|
212
|
+
let line = 1;
|
|
213
|
+
for (let i = 0; i < offset && i < source.length; i += 1) {
|
|
214
|
+
if (source[i] === '\n') {
|
|
215
|
+
line += 1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return line;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Factory honoring `config.codeIntel.providers.semantic`:
|
|
223
|
+
* 'typescript' (or 'auto' when typescript resolves) → TypeScriptSemanticProvider
|
|
224
|
+
* anything else / missing ('null' default) → NullSemanticProvider
|
|
225
|
+
*/
|
|
226
|
+
export function createSemanticProvider({ projectRoot = process.cwd(), config = {}, loader } = {}) {
|
|
227
|
+
const setting = config?.codeIntel?.providers?.semantic ?? 'null';
|
|
228
|
+
if (setting === 'typescript' || setting === 'auto') {
|
|
229
|
+
return new TypeScriptSemanticProvider({
|
|
230
|
+
projectRoot,
|
|
231
|
+
loader: loader ?? defaultLoader,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return new NullSemanticProvider();
|
|
235
|
+
}
|