@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.
Files changed (44) hide show
  1. package/manifests/documentation.yaml +77 -6
  2. package/manifests/hostCapabilities.yaml +49 -0
  3. package/manifests/instructionRules.yaml +7 -0
  4. package/package.json +1 -1
  5. package/scripts/bench/goldTasks.json +38 -0
  6. package/scripts/bench/runGold.mjs +220 -0
  7. package/scripts/release/verify-release.mjs +6 -0
  8. package/src/cli/commands/code.js +182 -0
  9. package/src/cli/commands/doctor.js +35 -3
  10. package/src/cli/commands/indexTools.js +102 -1
  11. package/src/cli/commands/memory.js +137 -0
  12. package/src/cli/index.js +7 -0
  13. package/src/core/codeintel/compiler.js +316 -0
  14. package/src/core/codeintel/diagnostics.js +114 -0
  15. package/src/core/codeintel/freshness.js +295 -0
  16. package/src/core/codeintel/impact.js +251 -0
  17. package/src/core/codeintel/invalidation.js +150 -0
  18. package/src/core/codeintel/manifest.js +176 -0
  19. package/src/core/codeintel/packet.js +146 -0
  20. package/src/core/codeintel/providers.js +201 -0
  21. package/src/core/codeintel/retriever.js +372 -0
  22. package/src/core/codeintel/router.js +149 -0
  23. package/src/core/codeintel/semanticProvider.js +235 -0
  24. package/src/core/docContracts.js +723 -0
  25. package/src/core/memory/migrate.js +324 -0
  26. package/src/core/memory/records.js +172 -0
  27. package/src/core/memory/retrieval.js +161 -11
  28. package/src/core/memory/store.js +398 -0
  29. package/src/core/memory/storeV2.js +171 -0
  30. package/src/core/memory/storeV2Loader.js +22 -0
  31. package/src/core/runtimeConfig.js +125 -0
  32. package/src/core/runtimePaths.js +3 -0
  33. package/src/index/taskRouting.js +39 -0
  34. package/templates/.claude/ukit/index/route-task.mjs +40 -0
  35. package/templates/AGENTS.md +46 -99
  36. package/templates/CLAUDE.md +46 -99
  37. package/templates/docs/AI_HANDOFF/tasks/_TEMPLATE.md +5 -0
  38. package/templates/docs/BUGFIX.md +2 -19
  39. package/templates/docs/BUG_INDEX.md +43 -0
  40. package/templates/docs/BUG_METRICS.md +1 -5
  41. package/templates/docs/BUG_TEMPLATE.md +1 -11
  42. package/templates/docs/UKIT_INTERNALS.md +4 -0
  43. package/templates/instructions/core.md +46 -99
  44. 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
+ }