@ngockhoale/ukit 2.6.7 → 2.6.9

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 (55) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/manifests/documentation.yaml +77 -6
  3. package/manifests/hostCapabilities.yaml +49 -0
  4. package/manifests/instructionRules.yaml +7 -0
  5. package/package.json +1 -1
  6. package/scripts/bench/goldTasks.json +38 -0
  7. package/scripts/bench/runGold.mjs +220 -0
  8. package/scripts/index/build-index.mjs +2 -1
  9. package/scripts/index/query-index.mjs +2 -0
  10. package/scripts/release/verify-release.mjs +6 -0
  11. package/src/cli/commands/code.js +182 -0
  12. package/src/cli/commands/doctor.js +35 -3
  13. package/src/cli/commands/indexTools.js +107 -1
  14. package/src/cli/commands/install.js +2 -1
  15. package/src/cli/commands/memory.js +137 -0
  16. package/src/cli/index.js +7 -0
  17. package/src/core/codeintel/analogy.js +197 -0
  18. package/src/core/codeintel/cochange.js +205 -0
  19. package/src/core/codeintel/compiler.js +389 -0
  20. package/src/core/codeintel/diagnostics.js +114 -0
  21. package/src/core/codeintel/freshness.js +295 -0
  22. package/src/core/codeintel/graph.js +291 -0
  23. package/src/core/codeintel/impact.js +274 -0
  24. package/src/core/codeintel/invalidation.js +150 -0
  25. package/src/core/codeintel/manifest.js +176 -0
  26. package/src/core/codeintel/packet.js +147 -0
  27. package/src/core/codeintel/providers.js +201 -0
  28. package/src/core/codeintel/retriever.js +418 -0
  29. package/src/core/codeintel/router.js +149 -0
  30. package/src/core/codeintel/semanticProvider.js +235 -0
  31. package/src/core/codeintel/summaries.js +194 -0
  32. package/src/core/codeintel/vectorProvider.js +213 -0
  33. package/src/core/docContracts.js +723 -0
  34. package/src/core/memory/migrate.js +324 -0
  35. package/src/core/memory/records.js +172 -0
  36. package/src/core/memory/retrieval.js +161 -11
  37. package/src/core/memory/store.js +398 -0
  38. package/src/core/memory/storeV2.js +171 -0
  39. package/src/core/memory/storeV2Loader.js +22 -0
  40. package/src/core/runtimeConfig.js +173 -0
  41. package/src/core/runtimePaths.js +3 -0
  42. package/src/index/buildIndex.js +29 -0
  43. package/src/index/paths.js +2 -0
  44. package/src/index/taskRouting.js +39 -0
  45. package/templates/.claude/ukit/index/route-task.mjs +40 -0
  46. package/templates/AGENTS.md +46 -99
  47. package/templates/CLAUDE.md +46 -99
  48. package/templates/docs/AI_HANDOFF/tasks/_TEMPLATE.md +5 -0
  49. package/templates/docs/BUGFIX.md +2 -19
  50. package/templates/docs/BUG_INDEX.md +43 -0
  51. package/templates/docs/BUG_METRICS.md +1 -5
  52. package/templates/docs/BUG_TEMPLATE.md +1 -11
  53. package/templates/docs/UKIT_INTERNALS.md +4 -0
  54. package/templates/instructions/core.md +46 -99
  55. package/templates/ukit/storage/config.json +35 -0
@@ -0,0 +1,147 @@
1
+ // Context Packet v1 — shared envelope for every `ukit code` result (SPEC §5).
2
+ // JSON-serializable contract; no clock/random in static fields (CTX-04).
3
+
4
+ export const PACKET_VERSION = 1;
5
+
6
+ const EVIDENCE_LEVELS = new Set(['L0', 'L1', 'L2', 'L3', 'L4']);
7
+ const EVIDENCE_SOURCES = new Set(['index', 'file', 'git', 'memory']);
8
+
9
+ /**
10
+ * createPacket({ repo, snapshot, taskType, budget }) → packet
11
+ */
12
+ export function createPacket({ repo, snapshot, taskType, budget } = {}) {
13
+ const requested = budget && typeof budget.requested === 'number' ? budget.requested : 0;
14
+ return {
15
+ version: PACKET_VERSION,
16
+ repo: repo ?? '',
17
+ snapshot: snapshot ?? '',
18
+ task_type: taskType ?? '',
19
+ freshness: { level: 'L0', headSha: '', overlayHash: '', configHash: '', stale: false },
20
+ budget: { requested, used: 0 },
21
+ anchors: [],
22
+ evidence: [],
23
+ relations: [],
24
+ tests: [],
25
+ memory: [],
26
+ omitted: [],
27
+ next_actions: [],
28
+ };
29
+ }
30
+
31
+ function validateEvidenceItem(item, i, errors) {
32
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
33
+ errors.push(`evidence[${i}] must be an object`);
34
+ return;
35
+ }
36
+ if (!EVIDENCE_LEVELS.has(item.level)) errors.push(`evidence[${i}].level must be one of L0..L4`);
37
+ if (!EVIDENCE_SOURCES.has(item.source)) errors.push(`evidence[${i}].source must be one of index|file|git|memory`);
38
+ if (typeof item.why !== 'string' || item.why.length === 0) errors.push(`evidence[${i}].why is required`);
39
+ }
40
+
41
+ /**
42
+ * validatePacket(packet) → { ok, errors }
43
+ */
44
+ export function validatePacket(packet) {
45
+ const errors = [];
46
+ if (!packet || typeof packet !== 'object' || Array.isArray(packet)) {
47
+ return { ok: false, errors: ['packet must be an object'] };
48
+ }
49
+ if (packet.version !== PACKET_VERSION) errors.push(`version must be ${PACKET_VERSION}`);
50
+ if (typeof packet.repo !== 'string' || packet.repo.length === 0) errors.push('repo is required');
51
+ if (typeof packet.snapshot !== 'string' || packet.snapshot.length === 0) errors.push('snapshot is required');
52
+ if (typeof packet.task_type !== 'string') errors.push('task_type must be a string');
53
+ if (!packet.freshness || typeof packet.freshness !== 'object') {
54
+ errors.push('freshness object is required');
55
+ } else if (typeof packet.freshness.stale !== 'boolean') {
56
+ errors.push('freshness.stale must be a boolean');
57
+ }
58
+ if (!packet.budget || typeof packet.budget !== 'object') {
59
+ errors.push('budget object is required');
60
+ } else {
61
+ if (typeof packet.budget.requested !== 'number') errors.push('budget.requested must be a number');
62
+ if (typeof packet.budget.used !== 'number') errors.push('budget.used must be a number');
63
+ }
64
+ for (const key of ['anchors', 'evidence', 'relations', 'tests', 'memory', 'omitted', 'next_actions']) {
65
+ if (!Array.isArray(packet[key])) errors.push(`${key} must be an array`);
66
+ }
67
+ if (Array.isArray(packet.evidence)) {
68
+ packet.evidence.forEach((item, i) => validateEvidenceItem(item, i, errors));
69
+ }
70
+ return { ok: errors.length === 0, errors };
71
+ }
72
+
73
+ /**
74
+ * addEvidence(packet, evidence) → packet — enforces shape, then appends.
75
+ * Throws on invalid evidence; packet left unchanged.
76
+ */
77
+ export function addEvidence(packet, evidence) {
78
+ const errors = [];
79
+ validateEvidenceItem(evidence, 0, errors);
80
+ if (errors.length > 0) throw new Error(`invalid evidence: ${errors.join('; ')}`);
81
+ if (!Array.isArray(packet.evidence)) packet.evidence = [];
82
+ packet.evidence.push({ ...evidence });
83
+ return packet;
84
+ }
85
+
86
+ /**
87
+ * packetToText(packet) → string — deterministic human/LLM-facing render.
88
+ */
89
+ export function packetToText(packet) {
90
+ const lines = [];
91
+ lines.push('## Context Packet');
92
+ lines.push(`version: ${packet.version}`);
93
+ lines.push(`repo: ${packet.repo}`);
94
+ lines.push(`snapshot: ${packet.snapshot}`);
95
+ lines.push(`task_type: ${packet.task_type}`);
96
+ const f = packet.freshness || {};
97
+ lines.push(`freshness: level=${f.level ?? ''} stale: ${f.stale === true}`);
98
+ lines.push(`budget: ${packet.budget?.used ?? 0}/${packet.budget?.requested ?? 0}`);
99
+
100
+ lines.push('');
101
+ lines.push('## Anchors');
102
+ for (const a of packet.anchors ?? []) {
103
+ lines.push(`- ${a.path ?? ''}${typeof a.line === 'number' ? `:${a.line}` : ''}${a.symbol ? ` (${a.symbol})` : ''}`);
104
+ }
105
+ if ((packet.anchors ?? []).length === 0) lines.push('(none)');
106
+
107
+ lines.push('');
108
+ lines.push('## Evidence');
109
+ for (const e of packet.evidence ?? []) {
110
+ lines.push(`- [${e.level}/${e.source}] ${e.path ?? ''} — ${e.why ?? ''}`);
111
+ if (e.summary) lines.push(` summary: ${e.summary}`);
112
+ if (e.excerpt) lines.push(` ${e.excerpt}`);
113
+ }
114
+ if ((packet.evidence ?? []).length === 0) lines.push('(none)');
115
+
116
+ lines.push('');
117
+ lines.push('## Relations');
118
+ for (const r of packet.relations ?? []) {
119
+ lines.push(`- ${r.from ?? ''} -[${r.kind ?? ''}]-> ${r.to ?? ''} (confidence: ${r.confidence ?? 0}${r.provider ? `, ${r.provider}` : ''})`);
120
+ }
121
+ if ((packet.relations ?? []).length === 0) lines.push('(none)');
122
+
123
+ lines.push('');
124
+ lines.push('## Tests');
125
+ for (const t of packet.tests ?? []) lines.push(`- ${t}`);
126
+ if ((packet.tests ?? []).length === 0) lines.push('(none)');
127
+
128
+ lines.push('');
129
+ lines.push('## Omitted');
130
+ for (const o of packet.omitted ?? []) lines.push(`- ${o.what ?? ''} — ${o.why ?? ''}`);
131
+ if ((packet.omitted ?? []).length === 0) lines.push('(none)');
132
+
133
+ lines.push('');
134
+ lines.push('## Next Actions');
135
+ for (const n of packet.next_actions ?? []) {
136
+ if (n && typeof n === 'object') {
137
+ // Object form (SPEC §8): - [<kind>] <file(s)> — <message>
138
+ const target = Array.isArray(n.files) ? n.files.join(', ') : (n.file ?? '');
139
+ lines.push(`- [${n.kind ?? 'action'}] ${target} — ${n.message ?? n.why ?? ''}`);
140
+ } else {
141
+ lines.push(`- ${n}`);
142
+ }
143
+ }
144
+ if ((packet.next_actions ?? []).length === 0) lines.push('(none)');
145
+
146
+ return lines.join('\n');
147
+ }
@@ -0,0 +1,201 @@
1
+ import fs from 'node:fs';
2
+
3
+ import { getFileOutline } from '../../index/queryIndex.js';
4
+ import { getArtifactPath, INDEX_ARTIFACTS } from '../../index/paths.js';
5
+
6
+ /**
7
+ * Provider contracts (SPEC §8) — the future LSP adapter seam. Contract only, no new deps.
8
+ *
9
+ * SyntaxProvider (plain object or class instance):
10
+ * .name → string, unique across the registry
11
+ * .supports(filePath) → bool (sync, cheap — no I/O beyond an existence check)
12
+ * .symbols(filePath, src?) → Promise<[{ name, kind, line }]>
13
+ * .outline(filePath, src?) → Promise<[{ kind, line }]>
14
+ *
15
+ * SemanticProvider:
16
+ * .name → string
17
+ * .capabilities() → { defs: bool, refs: bool, types: bool }
18
+ * .resolve(symbol, ctx) → Promise<edge[] | null>
19
+ *
20
+ * Every returned edge carries: { from, to, kind, confidence, provider, evidence, snapshot }.
21
+ */
22
+
23
+ export class UnsupportedOperation extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'UnsupportedOperation';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Canonical edge shape for codeintel results (SPEC §8). `confidence` is 0..1,
32
+ * `provider` names the producing provider, `evidence` records where the edge came
33
+ * from (artifact, rule, probe), `snapshot` ties it to the index/content revision.
34
+ */
35
+ export function createEdge({ from, to, kind, confidence = 1, provider = 'unknown', evidence = null, snapshot = null } = {}) {
36
+ return Object.freeze({
37
+ from: from ?? null,
38
+ to: to ?? null,
39
+ kind: kind ?? 'unknown',
40
+ confidence: typeof confidence === 'number' ? confidence : 1,
41
+ provider,
42
+ evidence,
43
+ snapshot,
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Syntax provider backed by the existing `.cache/index/` artifacts — zero new
49
+ * parsing. Reads `symbols.json` through `getFileOutline` (`src/index/queryIndex.js`),
50
+ * which already handles the artifact cache, schema guard, and never-throws contract.
51
+ */
52
+ export class IndexFileSyntaxProvider {
53
+ constructor({ rootDir = process.cwd() } = {}) {
54
+ this.name = 'index-file';
55
+ this.rootDir = rootDir;
56
+ }
57
+
58
+ // Sync existence check only — the expensive read stays inside getFileOutline's
59
+ // cached artifact load, and a missing index must never throw.
60
+ supports(filePath) {
61
+ if (!filePath) {
62
+ return false;
63
+ }
64
+ try {
65
+ return fs.existsSync(getArtifactPath(this.rootDir, INDEX_ARTIFACTS.symbols));
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ async symbols(filePath) {
72
+ const outline = await this.#outline(filePath);
73
+ return outline.map((item) => ({
74
+ name: item.name,
75
+ kind: item.type,
76
+ line: item.line,
77
+ }));
78
+ }
79
+
80
+ async outline(filePath) {
81
+ const outline = await this.#outline(filePath);
82
+ return outline.map((item) => ({ kind: item.type, line: item.line }));
83
+ }
84
+
85
+ async #outline(filePath) {
86
+ if (!filePath) {
87
+ return [];
88
+ }
89
+ try {
90
+ return await getFileOutline({ rootDir: this.rootDir, filePath, limit: Number.MAX_SAFE_INTEGER });
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Default semantic provider: advertises no capabilities and resolves nothing.
99
+ * Keeps call sites uniform until a real provider (e.g. LspSemanticProvider, CI-2xx)
100
+ * registers over it.
101
+ */
102
+ export class NullSemanticProvider {
103
+ constructor() {
104
+ this.name = 'null';
105
+ }
106
+
107
+ capabilities() {
108
+ return { defs: false, refs: false, types: false };
109
+ }
110
+
111
+ async resolve() {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ const registeredProviders = [];
117
+ const registeredNames = new Set();
118
+ let semanticProvider = null;
119
+
120
+ /**
121
+ * Register a provider instance. Custom providers dispatch ahead of the built-in
122
+ * `index-file` provider (which is appended lazily so it is always the fallback).
123
+ * Duplicate names are rejected — a name collision would make dispatch ambiguous.
124
+ */
125
+ export function registerProvider(provider) {
126
+ if (!provider || typeof provider !== 'object') {
127
+ throw new TypeError('registerProvider expects a provider object');
128
+ }
129
+ if (typeof provider.name !== 'string' || provider.name.length === 0) {
130
+ throw new TypeError('provider.name must be a non-empty string');
131
+ }
132
+ if (registeredNames.has(provider.name)) {
133
+ throw new UnsupportedOperation(`provider already registered: ${provider.name}`);
134
+ }
135
+ registeredNames.add(provider.name);
136
+
137
+ if (typeof provider.resolve === 'function' && typeof provider.capabilities === 'function'
138
+ && typeof provider.symbols !== 'function') {
139
+ semanticProvider = provider;
140
+ return provider;
141
+ }
142
+
143
+ const indexFile = registeredProviders.find((entry) => entry.name === 'index-file');
144
+ if (indexFile) {
145
+ registeredProviders.splice(registeredProviders.indexOf(indexFile), 0, provider);
146
+ } else {
147
+ registeredProviders.push(provider);
148
+ }
149
+ return provider;
150
+ }
151
+
152
+ export function listProviders() {
153
+ ensureBuiltins();
154
+ return [...registeredProviders];
155
+ }
156
+
157
+ /**
158
+ * First registered provider whose `supports(filePath)` returns true, checked in
159
+ * registration order with `index-file` always last (the fallback).
160
+ */
161
+ export function getSyntaxProvider(filePath) {
162
+ ensureBuiltins();
163
+ for (const provider of registeredProviders) {
164
+ try {
165
+ if (provider.supports(filePath)) {
166
+ return provider;
167
+ }
168
+ } catch {
169
+ // A throwing supports() must not break dispatch — treat as unsupported.
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+
175
+ /**
176
+ * The registered SemanticProvider, or NullSemanticProvider when none was
177
+ * registered (the `codeIntel.providers.semantic: "null"` default).
178
+ */
179
+ export function getSemanticProvider() {
180
+ return semanticProvider ?? new NullSemanticProvider();
181
+ }
182
+
183
+ let builtinsEnsured = false;
184
+ function ensureBuiltins() {
185
+ if (builtinsEnsured) {
186
+ return;
187
+ }
188
+ builtinsEnsured = true;
189
+ if (!registeredProviders.some((entry) => entry.name === 'index-file')) {
190
+ registeredProviders.push(new IndexFileSyntaxProvider({}));
191
+ registeredNames.add('index-file');
192
+ }
193
+ }
194
+
195
+ // Exported for tests: clears registry state back to defaults.
196
+ export function resetProvidersForTest() {
197
+ registeredProviders.length = 0;
198
+ registeredNames.clear();
199
+ semanticProvider = null;
200
+ builtinsEnsured = false;
201
+ }