@skill-map/cli 0.21.0 → 0.23.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.
- package/README.md +4 -4
- package/bin/sm.js +3 -3
- package/dist/cli/tutorial/sm-tutorial.md +22 -4
- package/dist/cli.js +7958 -6881
- package/dist/cli.js.map +1 -1
- package/dist/conformance/index.d.ts +1 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.js +1612 -1400
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +583 -421
- package/dist/kernel/index.js +1612 -1400
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-GXRWH2VL.js → chunk-2TPMJJYQ.js} +1 -1
- package/dist/ui/chunk-4BVLXZO3.js +61 -0
- package/dist/ui/{chunk-MPMBTIUR.js → chunk-BMAKIDAV.js} +30 -30
- package/dist/ui/{chunk-VVOEPDQD.js → chunk-GJJZ5QH6.js} +1 -1
- package/dist/ui/chunk-I7EELB7M.js +1 -0
- package/dist/ui/chunk-K47RR2IO.js +251 -0
- package/dist/ui/{chunk-OPPQMCMQ.js → chunk-KRNW54CI.js} +1 -1
- package/dist/ui/chunk-NJ4PSNK3.js +965 -0
- package/dist/ui/{chunk-W2EFGI3J.js → chunk-OU26UMVW.js} +1 -1
- package/dist/ui/chunk-QGRY6MDS.js +123 -0
- package/dist/ui/chunk-SCSYN7U2.js +1 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/main-CVCJMGY5.js +2 -0
- package/dist/ui/{styles-M2FETVAG.css → styles-ALBMEXCF.css} +1 -1
- package/package.json +8 -7
- package/dist/ui/chunk-25AWRVIC.js +0 -965
- package/dist/ui/chunk-6FTVUS57.js +0 -123
- package/dist/ui/chunk-MF2M6GYF.js +0 -1
- package/dist/ui/chunk-N366HMME.js +0 -1
- package/dist/ui/chunk-V3SZQETX.js +0 -61
- package/dist/ui/chunk-W62WVNU4.js +0 -251
- package/dist/ui/main-NIYE2VFS.js +0 -2
package/dist/index.js
CHANGED
|
@@ -92,18 +92,15 @@ var Registry = class {
|
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
// kernel/orchestrator.ts
|
|
96
|
-
import {
|
|
97
|
-
import {
|
|
98
|
-
import { isAbsolute as isAbsolute3, resolve as resolvePath } from "path";
|
|
99
|
-
import { Tiktoken } from "js-tiktoken/lite";
|
|
95
|
+
// kernel/orchestrator/index.ts
|
|
96
|
+
import { existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
97
|
+
import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
|
|
100
98
|
import cl100k_base from "js-tiktoken/ranks/cl100k_base";
|
|
101
|
-
import yaml4 from "js-yaml";
|
|
102
99
|
|
|
103
100
|
// package.json
|
|
104
101
|
var package_default = {
|
|
105
102
|
name: "@skill-map/cli",
|
|
106
|
-
version: "0.
|
|
103
|
+
version: "0.23.0",
|
|
107
104
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
108
105
|
license: "MIT",
|
|
109
106
|
type: "module",
|
|
@@ -156,29 +153,30 @@ var package_default = {
|
|
|
156
153
|
"lint:fix": "eslint . --fix",
|
|
157
154
|
reference: "node scripts/build-reference.js",
|
|
158
155
|
"reference:check": "node scripts/build-reference.js --check",
|
|
159
|
-
validate: "npm run
|
|
156
|
+
validate: "npm run validate:compile && npm run validate:test",
|
|
157
|
+
"validate:compile": "npm run typecheck && npm run lint && npm run build && npm run reference:check",
|
|
158
|
+
"validate:test": "npm run test:ci",
|
|
160
159
|
pretest: "tsup",
|
|
161
|
-
"pretest:ci": "tsup",
|
|
162
160
|
"pretest:coverage": "tsup",
|
|
163
161
|
"pretest:coverage:html": "tsup",
|
|
164
162
|
test: "tsc --noEmit && node --import tsx --test --test-reporter=spec 'test/**/*.test.ts' 'built-in-plugins/**/*.test.ts' 'kernel/**/*.test.ts' 'server/**/*.test.ts'",
|
|
165
|
-
"test:ci": "
|
|
163
|
+
"test:ci": "node --import tsx --test 'test/**/*.test.ts' 'built-in-plugins/**/*.test.ts' 'kernel/**/*.test.ts' 'server/**/*.test.ts'",
|
|
166
164
|
"test:coverage": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage 'test/**/*.test.ts' 'built-in-plugins/**/*.test.ts' 'kernel/**/*.test.ts' 'server/**/*.test.ts'",
|
|
167
165
|
"test:coverage:html": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test 'test/**/*.test.ts' 'built-in-plugins/**/*.test.ts' 'kernel/**/*.test.ts' 'server/**/*.test.ts'",
|
|
168
166
|
clean: "rm -rf dist coverage"
|
|
169
167
|
},
|
|
170
168
|
dependencies: {
|
|
171
169
|
"@hono/node-server": "2.0.1",
|
|
172
|
-
"@skill-map/spec": "0.
|
|
170
|
+
"@skill-map/spec": "0.23.0",
|
|
173
171
|
ajv: "8.18.0",
|
|
174
172
|
"ajv-formats": "3.0.1",
|
|
175
173
|
chokidar: "5.0.0",
|
|
176
174
|
clipanion: "4.0.0-rc.4",
|
|
177
|
-
hono: "4.12.
|
|
175
|
+
hono: "4.12.18",
|
|
178
176
|
ignore: "7.0.5",
|
|
179
177
|
"js-tiktoken": "1.0.21",
|
|
180
178
|
"js-yaml": "4.1.1",
|
|
181
|
-
kysely: "0.28.
|
|
179
|
+
kysely: "0.28.17",
|
|
182
180
|
semver: "7.7.4",
|
|
183
181
|
typanion: "3.14.0",
|
|
184
182
|
ws: "8.20.0"
|
|
@@ -206,12 +204,32 @@ var package_default = {
|
|
|
206
204
|
}
|
|
207
205
|
};
|
|
208
206
|
|
|
209
|
-
// kernel/
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
// kernel/adapters/in-memory-progress.ts
|
|
208
|
+
var InMemoryProgressEmitter = class {
|
|
209
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
210
|
+
emit(event) {
|
|
211
|
+
for (const listener of this.#listeners) listener(event);
|
|
212
|
+
}
|
|
213
|
+
subscribe(listener) {
|
|
214
|
+
this.#listeners.add(listener);
|
|
215
|
+
return () => {
|
|
216
|
+
this.#listeners.delete(listener);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// kernel/adapters/plugin-loader/index.ts
|
|
212
222
|
import { createRequire } from "module";
|
|
223
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
224
|
+
import { join, resolve as resolve3 } from "path";
|
|
225
|
+
import { pathToFileURL } from "url";
|
|
226
|
+
import semver from "semver";
|
|
227
|
+
|
|
228
|
+
// kernel/adapters/plugin-loader/id-utils.ts
|
|
229
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
230
|
+
|
|
231
|
+
// kernel/adapters/plugin-loader/validation.ts
|
|
213
232
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
214
|
-
import yaml from "js-yaml";
|
|
215
233
|
|
|
216
234
|
// kernel/util/ajv-interop.ts
|
|
217
235
|
import addFormatsModule from "ajv-formats";
|
|
@@ -220,171 +238,149 @@ function applyAjvFormats(ajv) {
|
|
|
220
238
|
addFormats(ajv);
|
|
221
239
|
}
|
|
222
240
|
|
|
223
|
-
// kernel/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
const root = parsedYaml;
|
|
266
|
-
const identityBlock = root["identity"];
|
|
267
|
-
const annotationsRaw = root["annotations"];
|
|
268
|
-
const annotations = isPlainObject(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
|
|
241
|
+
// kernel/extensions/hook.ts
|
|
242
|
+
var HOOK_TRIGGERS = Object.freeze([
|
|
243
|
+
"boot",
|
|
244
|
+
"scan.started",
|
|
245
|
+
"scan.completed",
|
|
246
|
+
"extractor.completed",
|
|
247
|
+
"analyzer.completed",
|
|
248
|
+
"action.completed",
|
|
249
|
+
"job.spawning",
|
|
250
|
+
"job.completed",
|
|
251
|
+
"job.failed",
|
|
252
|
+
"shutdown"
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
// kernel/adapters/plugin-loader/validation.ts
|
|
256
|
+
var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
257
|
+
"provider",
|
|
258
|
+
"extractor",
|
|
259
|
+
"analyzer",
|
|
260
|
+
"action",
|
|
261
|
+
"formatter",
|
|
262
|
+
"hook"
|
|
263
|
+
]);
|
|
264
|
+
var KNOWN_KINDS_LIST = [...KNOWN_KINDS].join(" / ");
|
|
265
|
+
var HOOKABLE_TRIGGERS_LIST = HOOK_TRIGGERS.join(", ");
|
|
266
|
+
|
|
267
|
+
// kernel/adapters/plugin-loader/storage-schemas.ts
|
|
268
|
+
import { readFileSync } from "fs";
|
|
269
|
+
import { resolve as resolve2 } from "path";
|
|
270
|
+
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
271
|
+
|
|
272
|
+
// kernel/i18n/plugin-store.texts.ts
|
|
273
|
+
var PLUGIN_STORE_TEXTS = {
|
|
274
|
+
kvValidationFailed: "plugin '{{pluginId}}' ctx.store.set('{{key}}', value): value violates declared schema ({{schemaPath}}): {{errors}}",
|
|
275
|
+
dedicatedValidationFailed: "plugin '{{pluginId}}' ctx.store.write('{{table}}', row): row violates declared schema ({{schemaPath}}): {{errors}}"
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// kernel/adapters/plugin-store.ts
|
|
279
|
+
var KV_SCHEMA_KEY = "__kv__";
|
|
280
|
+
function makeKvStoreWrapper(opts) {
|
|
281
|
+
const { pluginId, schema, persist } = opts;
|
|
269
282
|
return {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
async set(key, value) {
|
|
284
|
+
if (schema) {
|
|
285
|
+
if (!schema.validate(value)) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
tx(PLUGIN_STORE_TEXTS.kvValidationFailed, {
|
|
288
|
+
pluginId,
|
|
289
|
+
schemaPath: schema.schemaPath,
|
|
290
|
+
key,
|
|
291
|
+
errors: formatAjvErrors(schema.validate.errors ?? null)
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
await persist(key, value);
|
|
297
|
+
}
|
|
280
298
|
};
|
|
281
299
|
}
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
303
|
-
ajv.addSchema(annotationsSchema);
|
|
304
|
-
cachedSidecarValidator = ajv.compile(sidecarSchema);
|
|
305
|
-
return cachedSidecarValidator;
|
|
300
|
+
function makeDedicatedStoreWrapper(opts) {
|
|
301
|
+
const { pluginId, schemas, persist } = opts;
|
|
302
|
+
return {
|
|
303
|
+
async write(table, row) {
|
|
304
|
+
const schema = schemas?.[table];
|
|
305
|
+
if (schema) {
|
|
306
|
+
if (!schema.validate(row)) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
tx(PLUGIN_STORE_TEXTS.dedicatedValidationFailed, {
|
|
309
|
+
pluginId,
|
|
310
|
+
table,
|
|
311
|
+
schemaPath: schema.schemaPath,
|
|
312
|
+
errors: formatAjvErrors(schema.validate.errors ?? null)
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await persist(table, row);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
306
320
|
}
|
|
307
|
-
function
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
function makePluginStore(opts) {
|
|
322
|
+
const manifest = opts.plugin.manifest;
|
|
323
|
+
if (!manifest?.storage) return void 0;
|
|
324
|
+
const storageSchemas = opts.plugin.storageSchemas;
|
|
325
|
+
if (manifest.storage.mode === "kv") {
|
|
326
|
+
if (!opts.persistKv) return void 0;
|
|
327
|
+
const schema = storageSchemas?.[KV_SCHEMA_KEY];
|
|
328
|
+
return makeKvStoreWrapper({
|
|
329
|
+
pluginId: manifest.id,
|
|
330
|
+
schema,
|
|
331
|
+
persist: opts.persistKv
|
|
332
|
+
});
|
|
316
333
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (bodyDrift) return "stale-body";
|
|
325
|
-
if (fmDrift) return "stale-frontmatter";
|
|
326
|
-
return "fresh";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// kernel/sidecar/discover-orphans.ts
|
|
330
|
-
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
331
|
-
import { join, relative, sep } from "path";
|
|
332
|
-
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
333
|
-
const out = [];
|
|
334
|
-
for (const root of roots) {
|
|
335
|
-
walk(root, root, shouldSkip ?? (() => false), out);
|
|
334
|
+
if (manifest.storage.mode === "dedicated") {
|
|
335
|
+
if (!opts.persistDedicated) return void 0;
|
|
336
|
+
return makeDedicatedStoreWrapper({
|
|
337
|
+
pluginId: manifest.id,
|
|
338
|
+
schemas: storageSchemas,
|
|
339
|
+
persist: opts.persistDedicated
|
|
340
|
+
});
|
|
336
341
|
}
|
|
337
|
-
return
|
|
342
|
+
return void 0;
|
|
338
343
|
}
|
|
339
|
-
function
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
entries = readdirSync(current, { withFileTypes: true, encoding: "utf8" });
|
|
343
|
-
} catch {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
for (const entry of entries) {
|
|
347
|
-
const full = join(current, entry.name);
|
|
348
|
-
const rel = relative(root, full).split(sep).join("/");
|
|
349
|
-
if (shouldSkip(rel)) continue;
|
|
350
|
-
if (entry.isSymbolicLink()) continue;
|
|
351
|
-
if (entry.isDirectory()) {
|
|
352
|
-
walk(root, full, shouldSkip, out);
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (!entry.isFile()) continue;
|
|
356
|
-
if (!entry.name.endsWith(".sm")) continue;
|
|
357
|
-
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
358
|
-
if (existsSync2(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
359
|
-
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
360
|
-
}
|
|
344
|
+
function formatAjvErrors(errors) {
|
|
345
|
+
if (!errors || errors.length === 0) return "(no AJV details)";
|
|
346
|
+
return errors.map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
361
347
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
348
|
+
|
|
349
|
+
// kernel/adapters/plugin-loader/index.ts
|
|
350
|
+
function installedSpecVersion() {
|
|
351
|
+
const require2 = createRequire(import.meta.url);
|
|
352
|
+
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
353
|
+
const pkgPath = resolve3(indexPath, "..", "package.json");
|
|
354
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
355
|
+
return pkg.version;
|
|
368
356
|
}
|
|
369
357
|
|
|
370
|
-
// kernel/
|
|
371
|
-
import {
|
|
372
|
-
import { dirname
|
|
373
|
-
import { createRequire as
|
|
358
|
+
// kernel/adapters/schema-validators.ts
|
|
359
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
360
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
361
|
+
import { createRequire as createRequire2 } from "module";
|
|
374
362
|
import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
|
|
375
|
-
import yaml2 from "js-yaml";
|
|
376
|
-
|
|
377
|
-
// core/config/helper.ts
|
|
378
|
-
import { isAbsolute, resolve as resolve4 } from "path";
|
|
379
363
|
|
|
380
|
-
// kernel/
|
|
381
|
-
|
|
364
|
+
// kernel/types/view-catalog.ts
|
|
365
|
+
var ALL_SLOT_NAMES = [
|
|
366
|
+
"card.title.right",
|
|
367
|
+
"card.subtitle.left",
|
|
368
|
+
"card.footer.left",
|
|
369
|
+
"card.footer.right",
|
|
370
|
+
"graph.node.alert",
|
|
371
|
+
"inspector.header.badge.counter",
|
|
372
|
+
"inspector.header.badge.tag",
|
|
373
|
+
"inspector.body.panel.breakdown",
|
|
374
|
+
"inspector.body.panel.records",
|
|
375
|
+
"inspector.body.panel.tree",
|
|
376
|
+
"inspector.body.panel.key-values",
|
|
377
|
+
"inspector.body.panel.link-list",
|
|
378
|
+
"inspector.body.panel.markdown",
|
|
379
|
+
"topbar.nav.start"
|
|
380
|
+
];
|
|
381
|
+
var KNOWN_SLOT_NAMES = new Set(ALL_SLOT_NAMES);
|
|
382
382
|
|
|
383
383
|
// kernel/adapters/schema-validators.ts
|
|
384
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
385
|
-
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
386
|
-
import { createRequire as createRequire2 } from "module";
|
|
387
|
-
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
388
384
|
var SCHEMA_FILES = {
|
|
389
385
|
node: "schemas/node.schema.json",
|
|
390
386
|
link: "schemas/link.schema.json",
|
|
@@ -419,23 +415,23 @@ function loadSchemaValidators() {
|
|
|
419
415
|
return cachedValidators;
|
|
420
416
|
}
|
|
421
417
|
function buildSchemaValidators() {
|
|
422
|
-
const specRoot =
|
|
423
|
-
const ajv = new
|
|
418
|
+
const specRoot = resolveSpecRoot();
|
|
419
|
+
const ajv = new Ajv20203({
|
|
424
420
|
strict: false,
|
|
425
421
|
allErrors: true,
|
|
426
422
|
allowUnionTypes: true
|
|
427
423
|
});
|
|
428
424
|
applyAjvFormats(ajv);
|
|
429
425
|
for (const rel of SUPPORTING_SCHEMAS) {
|
|
430
|
-
const file =
|
|
426
|
+
const file = resolve4(specRoot, rel);
|
|
431
427
|
if (!existsSyncSafe(file)) continue;
|
|
432
|
-
const schema = JSON.parse(
|
|
428
|
+
const schema = JSON.parse(readFileSync3(file, "utf8"));
|
|
433
429
|
ajv.addSchema(schema);
|
|
434
430
|
}
|
|
435
431
|
const validators = /* @__PURE__ */ new Map();
|
|
436
432
|
for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
|
|
437
|
-
const file =
|
|
438
|
-
const schema = JSON.parse(
|
|
433
|
+
const file = resolve4(specRoot, rel);
|
|
434
|
+
const schema = JSON.parse(readFileSync3(file, "utf8"));
|
|
439
435
|
const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
|
|
440
436
|
validators.set(name, byId ?? ajv.compile(schema));
|
|
441
437
|
}
|
|
@@ -452,24 +448,8 @@ function buildSchemaValidators() {
|
|
|
452
448
|
});
|
|
453
449
|
const contributionValidators = /* @__PURE__ */ new Map();
|
|
454
450
|
const VIEW_SLOTS_ID = "https://skill-map.dev/spec/v0/view-slots.schema.json";
|
|
455
|
-
const KNOWN_SLOTS = /* @__PURE__ */ new Set([
|
|
456
|
-
"card.title.right",
|
|
457
|
-
"card.subtitle.left",
|
|
458
|
-
"card.footer.left",
|
|
459
|
-
"card.footer.right",
|
|
460
|
-
"graph.node.alert",
|
|
461
|
-
"inspector.header.badge.counter",
|
|
462
|
-
"inspector.header.badge.tag",
|
|
463
|
-
"inspector.body.panel.breakdown",
|
|
464
|
-
"inspector.body.panel.records",
|
|
465
|
-
"inspector.body.panel.tree",
|
|
466
|
-
"inspector.body.panel.key-values",
|
|
467
|
-
"inspector.body.panel.link-list",
|
|
468
|
-
"inspector.body.panel.markdown",
|
|
469
|
-
"topbar.nav.start"
|
|
470
|
-
]);
|
|
471
451
|
function getContributionValidator(slot) {
|
|
472
|
-
if (!
|
|
452
|
+
if (!KNOWN_SLOT_NAMES.has(slot)) return null;
|
|
473
453
|
const existing = contributionValidators.get(slot);
|
|
474
454
|
if (existing) return existing;
|
|
475
455
|
const ref = `${VIEW_SLOTS_ID}#/$defs/payloads/${slot}`;
|
|
@@ -515,15 +495,15 @@ function buildSchemaValidators() {
|
|
|
515
495
|
};
|
|
516
496
|
}
|
|
517
497
|
function buildProviderFrontmatterValidator(providers) {
|
|
518
|
-
const specRoot =
|
|
519
|
-
const ajv = new
|
|
498
|
+
const specRoot = resolveSpecRoot();
|
|
499
|
+
const ajv = new Ajv20203({
|
|
520
500
|
strict: false,
|
|
521
501
|
allErrors: true,
|
|
522
502
|
allowUnionTypes: true
|
|
523
503
|
});
|
|
524
504
|
applyAjvFormats(ajv);
|
|
525
|
-
const baseFile =
|
|
526
|
-
const baseSchema = JSON.parse(
|
|
505
|
+
const baseFile = resolve4(specRoot, "schemas/frontmatter/base.schema.json");
|
|
506
|
+
const baseSchema = JSON.parse(readFileSync3(baseFile, "utf8"));
|
|
527
507
|
ajv.addSchema(baseSchema);
|
|
528
508
|
registerProviderAuxiliarySchemas(ajv, providers);
|
|
529
509
|
const compiled = /* @__PURE__ */ new Map();
|
|
@@ -560,20 +540,20 @@ function registerProviderAuxiliarySchemas(ajv, providers) {
|
|
|
560
540
|
}
|
|
561
541
|
}
|
|
562
542
|
}
|
|
563
|
-
function
|
|
543
|
+
function resolveSpecRoot() {
|
|
564
544
|
const require2 = createRequire2(import.meta.url);
|
|
565
545
|
try {
|
|
566
546
|
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
567
|
-
return
|
|
547
|
+
return dirname(indexPath);
|
|
568
548
|
} catch {
|
|
569
549
|
throw new Error(
|
|
570
|
-
"@skill-map/spec not resolvable
|
|
550
|
+
"@skill-map/spec not resolvable: ensure the workspace is linked or the package is installed."
|
|
571
551
|
);
|
|
572
552
|
}
|
|
573
553
|
}
|
|
574
554
|
function existsSyncSafe(path) {
|
|
575
555
|
try {
|
|
576
|
-
|
|
556
|
+
readFileSync3(path, "utf8");
|
|
577
557
|
return true;
|
|
578
558
|
} catch {
|
|
579
559
|
return false;
|
|
@@ -585,45 +565,6 @@ function formatErrorMessage(err) {
|
|
|
585
565
|
return err instanceof Error ? err.message : String(err);
|
|
586
566
|
}
|
|
587
567
|
|
|
588
|
-
// kernel/util/skill-map-paths.ts
|
|
589
|
-
import { join as join3 } from "path";
|
|
590
|
-
|
|
591
|
-
// core/paths/db-path.ts
|
|
592
|
-
import { join as join2, resolve as resolve3 } from "path";
|
|
593
|
-
var SKILL_MAP_DIR = ".skill-map";
|
|
594
|
-
var DB_FILENAME = "skill-map.db";
|
|
595
|
-
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
596
|
-
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
597
|
-
var GITIGNORE_ENTRIES = [
|
|
598
|
-
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
599
|
-
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
600
|
-
];
|
|
601
|
-
|
|
602
|
-
// core/config/atomic-write.ts
|
|
603
|
-
import {
|
|
604
|
-
existsSync as existsSync4,
|
|
605
|
-
mkdirSync,
|
|
606
|
-
readFileSync as readFileSync4,
|
|
607
|
-
renameSync,
|
|
608
|
-
unlinkSync,
|
|
609
|
-
writeFileSync
|
|
610
|
-
} from "fs";
|
|
611
|
-
import { dirname as dirname3 } from "path";
|
|
612
|
-
|
|
613
|
-
// kernel/adapters/in-memory-progress.ts
|
|
614
|
-
var InMemoryProgressEmitter = class {
|
|
615
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
616
|
-
emit(event) {
|
|
617
|
-
for (const listener of this.#listeners) listener(event);
|
|
618
|
-
}
|
|
619
|
-
subscribe(listener) {
|
|
620
|
-
this.#listeners.add(listener);
|
|
621
|
-
return () => {
|
|
622
|
-
this.#listeners.delete(listener);
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
|
|
627
568
|
// kernel/adapters/silent-logger.ts
|
|
628
569
|
var SilentLogger = class {
|
|
629
570
|
trace() {
|
|
@@ -657,606 +598,314 @@ function getActiveLogger() {
|
|
|
657
598
|
return active;
|
|
658
599
|
}
|
|
659
600
|
|
|
660
|
-
// kernel/
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
var KV_SCHEMA_KEY = "__kv__";
|
|
676
|
-
function makeKvStoreWrapper(opts) {
|
|
677
|
-
const { pluginId, schema, persist } = opts;
|
|
678
|
-
return {
|
|
679
|
-
async set(key, value) {
|
|
680
|
-
if (schema) {
|
|
681
|
-
if (!schema.validate(value)) {
|
|
682
|
-
throw new Error(
|
|
683
|
-
tx(PLUGIN_STORE_TEXTS.kvValidationFailed, {
|
|
684
|
-
pluginId,
|
|
685
|
-
schemaPath: schema.schemaPath,
|
|
686
|
-
key,
|
|
687
|
-
errors: formatAjvErrors(schema.validate.errors ?? null)
|
|
688
|
-
})
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
await persist(key, value);
|
|
601
|
+
// kernel/extensions/hook-dispatcher.ts
|
|
602
|
+
function makeHookDispatcher(hooks, emitter) {
|
|
603
|
+
if (hooks.length === 0) {
|
|
604
|
+
return { dispatch: async () => {
|
|
605
|
+
} };
|
|
606
|
+
}
|
|
607
|
+
const byTrigger = /* @__PURE__ */ new Map();
|
|
608
|
+
for (const hook of hooks) {
|
|
609
|
+
if (hook.mode === "probabilistic") {
|
|
610
|
+
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
611
|
+
log.warn(
|
|
612
|
+
`Probabilistic hook ${qualifiedId} deferred to job subsystem (future job subsystem). The hook is registered but will not dispatch in-scan.`,
|
|
613
|
+
{ hookId: qualifiedId, mode: "probabilistic" }
|
|
614
|
+
);
|
|
615
|
+
continue;
|
|
693
616
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
617
|
+
for (const trig of hook.triggers) {
|
|
618
|
+
const bucket = byTrigger.get(trig);
|
|
619
|
+
if (bucket) bucket.push(hook);
|
|
620
|
+
else byTrigger.set(trig, [hook]);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
698
623
|
return {
|
|
699
|
-
async
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
624
|
+
async dispatch(trigger, event) {
|
|
625
|
+
const subs = byTrigger.get(trigger);
|
|
626
|
+
if (!subs || subs.length === 0) return;
|
|
627
|
+
for (const hook of subs) {
|
|
628
|
+
if (!matchesFilter(hook, event)) continue;
|
|
629
|
+
const ctx = buildHookContext(hook, trigger, event);
|
|
630
|
+
try {
|
|
631
|
+
await hook.on(ctx);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
634
|
+
const message = formatErrorMessage(err);
|
|
635
|
+
emitter.emit(
|
|
636
|
+
makeEvent("extension.error", {
|
|
637
|
+
kind: "hook-error",
|
|
638
|
+
extensionId: qualifiedId,
|
|
639
|
+
trigger,
|
|
640
|
+
message
|
|
709
641
|
})
|
|
710
642
|
);
|
|
711
643
|
}
|
|
712
644
|
}
|
|
713
|
-
await persist(table, row);
|
|
714
645
|
}
|
|
715
646
|
};
|
|
716
647
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
719
|
-
if (!manifest?.storage) return void 0;
|
|
720
|
-
const storageSchemas = opts.plugin.storageSchemas;
|
|
721
|
-
if (manifest.storage.mode === "kv") {
|
|
722
|
-
if (!opts.persistKv) return void 0;
|
|
723
|
-
const schema = storageSchemas?.[KV_SCHEMA_KEY];
|
|
724
|
-
return makeKvStoreWrapper({
|
|
725
|
-
pluginId: manifest.id,
|
|
726
|
-
schema,
|
|
727
|
-
persist: opts.persistKv
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
if (manifest.storage.mode === "dedicated") {
|
|
731
|
-
if (!opts.persistDedicated) return void 0;
|
|
732
|
-
return makeDedicatedStoreWrapper({
|
|
733
|
-
pluginId: manifest.id,
|
|
734
|
-
schemas: storageSchemas,
|
|
735
|
-
persist: opts.persistDedicated
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
return void 0;
|
|
648
|
+
function makeEvent(type, data) {
|
|
649
|
+
return { type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
739
650
|
}
|
|
740
|
-
function
|
|
741
|
-
if (!
|
|
742
|
-
|
|
651
|
+
function matchesFilter(hook, event) {
|
|
652
|
+
if (!hook.filter) return true;
|
|
653
|
+
const data = event.data ?? {};
|
|
654
|
+
for (const [key, expected] of Object.entries(hook.filter)) {
|
|
655
|
+
if (data[key] !== expected) return false;
|
|
656
|
+
}
|
|
657
|
+
return true;
|
|
743
658
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
"
|
|
756
|
-
"
|
|
757
|
-
]);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
function installedSpecVersion() {
|
|
764
|
-
const require2 = createRequire4(import.meta.url);
|
|
765
|
-
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
766
|
-
const pkgPath = resolve6(indexPath, "..", "package.json");
|
|
767
|
-
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
768
|
-
return pkg.version;
|
|
659
|
+
function buildHookContext(_hook, trigger, event) {
|
|
660
|
+
const data = event.data ?? {};
|
|
661
|
+
const ctx = {
|
|
662
|
+
event: {
|
|
663
|
+
type: trigger,
|
|
664
|
+
timestamp: event.timestamp,
|
|
665
|
+
...event.runId !== void 0 ? { runId: event.runId } : {},
|
|
666
|
+
...event.jobId !== void 0 ? { jobId: event.jobId } : {},
|
|
667
|
+
data: event.data
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
if (typeof data["extractorId"] === "string") ctx.extractorId = data["extractorId"];
|
|
671
|
+
if (typeof data["analyzerId"] === "string") ctx.analyzerId = data["analyzerId"];
|
|
672
|
+
if (typeof data["actionId"] === "string") ctx.actionId = data["actionId"];
|
|
673
|
+
if (data["node"] && typeof data["node"] === "object") {
|
|
674
|
+
ctx.node = data["node"];
|
|
675
|
+
}
|
|
676
|
+
if (data["jobResult"] !== void 0) ctx.jobResult = data["jobResult"];
|
|
677
|
+
return ctx;
|
|
769
678
|
}
|
|
770
679
|
|
|
771
680
|
// kernel/i18n/orchestrator.texts.ts
|
|
772
681
|
var ORCHESTRATOR_TEXTS = {
|
|
773
682
|
frontmatterInvalid: "Frontmatter for {{path}} ({{kind}}) failed schema validation: {{errors}}",
|
|
774
|
-
frontmatterMalformedPasteWithIndent: "Frontmatter fence in {{path}} appears indented; YAML frontmatter MUST start with `---` at column 0. The file was scanned as body-only
|
|
683
|
+
frontmatterMalformedPasteWithIndent: "Frontmatter fence in {{path}} appears indented; YAML frontmatter MUST start with `---` at column 0. The file was scanned as body-only; the metadata block was silently lost. Move the `---` lines to the start of the line.",
|
|
775
684
|
frontmatterMalformedByteOrderMark: "Frontmatter fence in {{path}} is preceded by a UTF-8 byte-order mark (BOM); the file was scanned as body-only. Re-save the file as UTF-8 without BOM. The metadata block was silently lost.",
|
|
776
|
-
frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes
|
|
685
|
+
frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes (no matching `---` line at column 0 was found). The file was scanned as body-only and every metadata field was silently lost. Add a closing `---` line below the metadata block.",
|
|
777
686
|
extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
|
|
778
687
|
extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
|
|
779
688
|
extensionErrorContributionUnknownId: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}} but did not declare it in its `viewContributions` map. Contribution dropped.',
|
|
780
689
|
extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{slot}}" schema: {{errors}}. Contribution dropped.',
|
|
690
|
+
extensionErrorRecommendedActionMissing: 'Analyzer "{{analyzerId}}" declares recommendedAction "{{actionId}}" but no Action is registered under that qualified id. The analyzer stays registered; the recommendation will not surface in the inspector.',
|
|
781
691
|
runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
|
|
782
692
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
783
693
|
};
|
|
784
694
|
|
|
785
|
-
// kernel/
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
695
|
+
// kernel/orchestrator/extractors.ts
|
|
696
|
+
async function runExtractorsForNode(opts) {
|
|
697
|
+
const internalLinks = [];
|
|
698
|
+
const externalLinks = [];
|
|
699
|
+
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
700
|
+
const contributions = [];
|
|
701
|
+
const validators = loadSchemaValidators();
|
|
702
|
+
for (const extractor of opts.extractors) {
|
|
703
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
704
|
+
const emitLink = (link) => {
|
|
705
|
+
const validated = validateLink(extractor, link, opts.emitter);
|
|
706
|
+
if (!validated) return;
|
|
707
|
+
if (isExternalUrlLink(validated)) externalLinks.push(validated);
|
|
708
|
+
else internalLinks.push(validated);
|
|
709
|
+
};
|
|
710
|
+
const enrichNode = (partial) => {
|
|
711
|
+
const key = `${opts.node.path}\0${qualifiedId}`;
|
|
712
|
+
const existing = enrichmentBuffer.get(key);
|
|
713
|
+
if (existing) {
|
|
714
|
+
existing.value = { ...existing.value, ...partial };
|
|
715
|
+
existing.enrichedAt = Date.now();
|
|
716
|
+
} else {
|
|
717
|
+
enrichmentBuffer.set(key, {
|
|
718
|
+
nodePath: opts.node.path,
|
|
719
|
+
extractorId: qualifiedId,
|
|
720
|
+
bodyHashAtEnrichment: opts.bodyHash,
|
|
721
|
+
value: { ...partial },
|
|
722
|
+
enrichedAt: Date.now(),
|
|
723
|
+
// Extractors are deterministic-only; `is_probabilistic` is
|
|
724
|
+
// reserved on the row for future Action-issued enrichments.
|
|
725
|
+
isProbabilistic: false
|
|
726
|
+
});
|
|
809
727
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
// src/kernel/scan/ → src/config/defaults/
|
|
827
|
-
resolve7(here, "../config/defaults/skillmapignore"),
|
|
828
|
-
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
829
|
-
resolve7(here, "config/defaults/skillmapignore")
|
|
830
|
-
];
|
|
831
|
-
for (const candidate of candidates) {
|
|
832
|
-
if (existsSync7(candidate)) {
|
|
833
|
-
try {
|
|
834
|
-
return readFileSync7(candidate, "utf8");
|
|
835
|
-
} catch {
|
|
728
|
+
};
|
|
729
|
+
const declaredContributions = readDeclaredContributions(extractor);
|
|
730
|
+
const emitContribution = (contributionId, payload) => {
|
|
731
|
+
const declared = declaredContributions.get(contributionId);
|
|
732
|
+
if (!declared) {
|
|
733
|
+
emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
|
|
734
|
+
phase: "emitContribution",
|
|
735
|
+
contributionId,
|
|
736
|
+
reason: "unknown-contribution-id",
|
|
737
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
|
|
738
|
+
extractorId: qualifiedId,
|
|
739
|
+
contributionId,
|
|
740
|
+
nodePath: opts.node.path
|
|
741
|
+
})
|
|
742
|
+
});
|
|
743
|
+
return;
|
|
836
744
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const parsed = {};
|
|
854
|
-
try {
|
|
855
|
-
const doc = yaml3.load(frontmatterRaw, { schema: yaml3.JSON_SCHEMA });
|
|
856
|
-
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
857
|
-
for (const [k, v] of Object.entries(doc)) {
|
|
858
|
-
if (FORBIDDEN_FRONTMATTER_KEYS.has(k)) continue;
|
|
859
|
-
parsed[k] = v;
|
|
860
|
-
}
|
|
745
|
+
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
746
|
+
if (!result.ok) {
|
|
747
|
+
emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
|
|
748
|
+
phase: "emitContribution",
|
|
749
|
+
contributionId,
|
|
750
|
+
slot: declared.slot,
|
|
751
|
+
reason: result.errors,
|
|
752
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
753
|
+
extractorId: qualifiedId,
|
|
754
|
+
contributionId,
|
|
755
|
+
nodePath: opts.node.path,
|
|
756
|
+
slot: declared.slot,
|
|
757
|
+
errors: result.errors
|
|
758
|
+
})
|
|
759
|
+
});
|
|
760
|
+
return;
|
|
861
761
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
762
|
+
contributions.push({
|
|
763
|
+
pluginId: extractor.pluginId,
|
|
764
|
+
extensionId: extractor.id,
|
|
765
|
+
nodePath: opts.node.path,
|
|
766
|
+
contributionId,
|
|
767
|
+
slot: declared.slot,
|
|
768
|
+
payload,
|
|
769
|
+
emittedAt: Date.now()
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
773
|
+
const ctx = buildExtractorContext(
|
|
774
|
+
extractor,
|
|
775
|
+
opts.node,
|
|
776
|
+
opts.body,
|
|
777
|
+
opts.frontmatter,
|
|
778
|
+
emitLink,
|
|
779
|
+
enrichNode,
|
|
780
|
+
emitContribution,
|
|
781
|
+
store
|
|
782
|
+
);
|
|
783
|
+
await extractor.extract(ctx);
|
|
873
784
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
]);
|
|
881
|
-
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
882
|
-
function getParser(id) {
|
|
883
|
-
return REGISTRY.get(id);
|
|
785
|
+
return {
|
|
786
|
+
internalLinks,
|
|
787
|
+
externalLinks,
|
|
788
|
+
enrichments: Array.from(enrichmentBuffer.values()),
|
|
789
|
+
contributions
|
|
790
|
+
};
|
|
884
791
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
792
|
+
function readDeclaredContributions(extension) {
|
|
793
|
+
const out = /* @__PURE__ */ new Map();
|
|
794
|
+
const raw = extension.viewContributions;
|
|
795
|
+
if (typeof raw !== "object" || raw === null) return out;
|
|
796
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
797
|
+
if (typeof value !== "object" || value === null) continue;
|
|
798
|
+
const slot = value.slot;
|
|
799
|
+
if (typeof slot !== "string") continue;
|
|
800
|
+
out.set(id, { slot });
|
|
891
801
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
802
|
+
return out;
|
|
803
|
+
}
|
|
804
|
+
function emitExtensionError(emitter, qualifiedId, nodePath, data) {
|
|
805
|
+
emitter.emit(
|
|
806
|
+
makeEvent("extension.error", {
|
|
807
|
+
kind: "contribution-rejected",
|
|
808
|
+
extensionId: qualifiedId,
|
|
809
|
+
nodePath,
|
|
810
|
+
...data
|
|
811
|
+
})
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
815
|
+
const scope = extractor.scope;
|
|
816
|
+
return {
|
|
817
|
+
node,
|
|
818
|
+
body: scope === "frontmatter" ? "" : body,
|
|
819
|
+
frontmatter: scope === "body" ? {} : frontmatter,
|
|
820
|
+
emitLink,
|
|
821
|
+
enrichNode,
|
|
822
|
+
emitContribution,
|
|
823
|
+
...store !== void 0 ? { store } : {}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function validateLink(extractor, link, emitter) {
|
|
827
|
+
if (!extractor.emitsLinkKinds.includes(link.kind)) {
|
|
828
|
+
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
829
|
+
emitter.emit(
|
|
830
|
+
makeEvent("extension.error", {
|
|
831
|
+
kind: "link-kind-not-declared",
|
|
832
|
+
extensionId: qualifiedId,
|
|
833
|
+
linkKind: link.kind,
|
|
834
|
+
declaredKinds: extractor.emitsLinkKinds,
|
|
835
|
+
link: { source: link.source, target: link.target, kind: link.kind },
|
|
836
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorLinkKindNotDeclared, {
|
|
837
|
+
extractorId: qualifiedId,
|
|
838
|
+
linkKind: link.kind,
|
|
839
|
+
declaredKinds: extractor.emitsLinkKinds.join(", ")
|
|
840
|
+
})
|
|
841
|
+
})
|
|
842
|
+
);
|
|
843
|
+
return null;
|
|
915
844
|
}
|
|
845
|
+
const confidence = link.confidence ?? extractor.defaultConfidence;
|
|
846
|
+
return { ...link, confidence };
|
|
916
847
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
848
|
+
function recomputeLinkCounts(nodes, links) {
|
|
849
|
+
const byPath2 = /* @__PURE__ */ new Map();
|
|
850
|
+
for (const node of nodes) {
|
|
851
|
+
node.linksOutCount = 0;
|
|
852
|
+
node.linksInCount = 0;
|
|
853
|
+
byPath2.set(node.path, node);
|
|
923
854
|
}
|
|
924
|
-
for (const
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
const
|
|
928
|
-
if (
|
|
929
|
-
if (entry.isSymbolicLink()) continue;
|
|
930
|
-
if (entry.isDirectory()) {
|
|
931
|
-
yield* walkRoot(root, full, filter, extensions);
|
|
932
|
-
} else if (entry.isFile() && hasMatchingExtension(name, extensions)) {
|
|
933
|
-
try {
|
|
934
|
-
const s = await stat(full);
|
|
935
|
-
if (s.isFile()) yield full;
|
|
936
|
-
} catch {
|
|
937
|
-
}
|
|
938
|
-
}
|
|
855
|
+
for (const link of links) {
|
|
856
|
+
const source = byPath2.get(link.source);
|
|
857
|
+
if (source) source.linksOutCount += 1;
|
|
858
|
+
const target = byPath2.get(link.target);
|
|
859
|
+
if (target) target.linksInCount += 1;
|
|
939
860
|
}
|
|
940
861
|
}
|
|
941
|
-
function
|
|
942
|
-
|
|
943
|
-
|
|
862
|
+
function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
863
|
+
const byPath2 = /* @__PURE__ */ new Map();
|
|
864
|
+
for (const node of nodes) {
|
|
865
|
+
if (!cachedPaths.has(node.path)) node.externalRefsCount = 0;
|
|
866
|
+
byPath2.set(node.path, node);
|
|
944
867
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
// kernel/extensions/provider.ts
|
|
949
|
-
var DEFAULT_READ_CONFIG = Object.freeze({
|
|
950
|
-
extensions: Object.freeze([".md"]),
|
|
951
|
-
parser: "frontmatter-yaml"
|
|
952
|
-
});
|
|
953
|
-
function resolveProviderWalk(provider) {
|
|
954
|
-
if (provider.walk) {
|
|
955
|
-
const walk2 = provider.walk.bind(provider);
|
|
956
|
-
return walk2;
|
|
868
|
+
for (const link of externalLinks) {
|
|
869
|
+
const source = byPath2.get(link.source);
|
|
870
|
+
if (source && !cachedPaths.has(source.path)) source.externalRefsCount += 1;
|
|
957
871
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
parser: read.parser
|
|
963
|
-
};
|
|
964
|
-
if (options?.ignoreFilter) walkOptions.ignoreFilter = options.ignoreFilter;
|
|
965
|
-
return walkContent(roots, walkOptions);
|
|
966
|
-
};
|
|
872
|
+
}
|
|
873
|
+
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
874
|
+
function isExternalUrlLink(link) {
|
|
875
|
+
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
967
876
|
}
|
|
968
877
|
|
|
969
|
-
// kernel/
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
return { dispatch: async () => {
|
|
973
|
-
} };
|
|
974
|
-
}
|
|
975
|
-
const byTrigger = /* @__PURE__ */ new Map();
|
|
976
|
-
for (const hook of hooks) {
|
|
977
|
-
if (hook.mode === "probabilistic") {
|
|
978
|
-
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
979
|
-
log.warn(
|
|
980
|
-
`Probabilistic hook ${qualifiedId} deferred to job subsystem (future job subsystem). The hook is registered but will not dispatch in-scan.`,
|
|
981
|
-
{ hookId: qualifiedId, mode: "probabilistic" }
|
|
982
|
-
);
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
for (const trig of hook.triggers) {
|
|
986
|
-
const bucket = byTrigger.get(trig);
|
|
987
|
-
if (bucket) bucket.push(hook);
|
|
988
|
-
else byTrigger.set(trig, [hook]);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return {
|
|
992
|
-
async dispatch(trigger, event) {
|
|
993
|
-
const subs = byTrigger.get(trigger);
|
|
994
|
-
if (!subs || subs.length === 0) return;
|
|
995
|
-
for (const hook of subs) {
|
|
996
|
-
if (!matchesFilter(hook, event)) continue;
|
|
997
|
-
const ctx = buildHookContext(hook, trigger, event);
|
|
998
|
-
try {
|
|
999
|
-
await hook.on(ctx);
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
1002
|
-
const message = formatErrorMessage(err);
|
|
1003
|
-
emitter.emit(
|
|
1004
|
-
makeEvent("extension.error", {
|
|
1005
|
-
kind: "hook-error",
|
|
1006
|
-
extensionId: qualifiedId,
|
|
1007
|
-
trigger,
|
|
1008
|
-
message
|
|
1009
|
-
})
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
function makeEvent(type, data) {
|
|
1017
|
-
return { type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1018
|
-
}
|
|
1019
|
-
function matchesFilter(hook, event) {
|
|
1020
|
-
if (!hook.filter) return true;
|
|
1021
|
-
const data = event.data ?? {};
|
|
1022
|
-
for (const [key, expected] of Object.entries(hook.filter)) {
|
|
1023
|
-
if (data[key] !== expected) return false;
|
|
1024
|
-
}
|
|
1025
|
-
return true;
|
|
1026
|
-
}
|
|
1027
|
-
function buildHookContext(_hook, trigger, event) {
|
|
1028
|
-
const data = event.data ?? {};
|
|
1029
|
-
const ctx = {
|
|
1030
|
-
event: {
|
|
1031
|
-
type: trigger,
|
|
1032
|
-
timestamp: event.timestamp,
|
|
1033
|
-
...event.runId !== void 0 ? { runId: event.runId } : {},
|
|
1034
|
-
...event.jobId !== void 0 ? { jobId: event.jobId } : {},
|
|
1035
|
-
data: event.data
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
if (typeof data["extractorId"] === "string") ctx.extractorId = data["extractorId"];
|
|
1039
|
-
if (typeof data["analyzerId"] === "string") ctx.analyzerId = data["analyzerId"];
|
|
1040
|
-
if (typeof data["actionId"] === "string") ctx.actionId = data["actionId"];
|
|
1041
|
-
if (data["node"] && typeof data["node"] === "object") {
|
|
1042
|
-
ctx.node = data["node"];
|
|
1043
|
-
}
|
|
1044
|
-
if (data["jobResult"] !== void 0) ctx.jobResult = data["jobResult"];
|
|
1045
|
-
return ctx;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// kernel/orchestrator.ts
|
|
1049
|
-
var SCANNED_BY = {
|
|
1050
|
-
name: "skill-map",
|
|
1051
|
-
version: package_default.version,
|
|
1052
|
-
specVersion: resolveSpecVersionSafe()
|
|
1053
|
-
};
|
|
1054
|
-
function resolveSpecVersionSafe() {
|
|
1055
|
-
try {
|
|
1056
|
-
return installedSpecVersion();
|
|
1057
|
-
} catch {
|
|
1058
|
-
return "unknown";
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
async function runScanWithRenames(_kernel, options) {
|
|
1062
|
-
return runScanInternal(_kernel, options);
|
|
1063
|
-
}
|
|
1064
|
-
async function runScan(_kernel, options) {
|
|
1065
|
-
const { result } = await runScanInternal(_kernel, options);
|
|
1066
|
-
return result;
|
|
1067
|
-
}
|
|
1068
|
-
async function runScanInternal(_kernel, options) {
|
|
1069
|
-
validateRoots(options.roots);
|
|
1070
|
-
const start = Date.now();
|
|
1071
|
-
const scannedAt = start;
|
|
1072
|
-
const emitter = options.emitter ?? new InMemoryProgressEmitter();
|
|
1073
|
-
const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
|
|
1074
|
-
const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
|
|
1075
|
-
const tokenize = options.tokenize !== false;
|
|
1076
|
-
const scope = options.scope ?? "project";
|
|
1077
|
-
const strict = options.strict === true;
|
|
1078
|
-
const encoder = tokenize ? new Tiktoken(cl100k_base) : null;
|
|
1079
|
-
const prior = options.priorSnapshot ?? null;
|
|
1080
|
-
const enableCache = options.enableCache === true;
|
|
1081
|
-
const priorExtractorRuns = options.priorExtractorRuns;
|
|
1082
|
-
const priorIndex = indexPriorSnapshot(prior);
|
|
1083
|
-
const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
|
|
1084
|
-
const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
|
|
1085
|
-
emitter.emit(scanStartedEvent);
|
|
1086
|
-
await hookDispatcher.dispatch("scan.started", scanStartedEvent);
|
|
1087
|
-
const walked = await walkAndExtract({
|
|
1088
|
-
providers: exts.providers,
|
|
1089
|
-
extractors: exts.extractors,
|
|
1090
|
-
roots: options.roots,
|
|
1091
|
-
...options.ignoreFilter ? { ignoreFilter: options.ignoreFilter } : {},
|
|
1092
|
-
emitter,
|
|
1093
|
-
encoder,
|
|
1094
|
-
strict,
|
|
1095
|
-
enableCache,
|
|
1096
|
-
prior,
|
|
1097
|
-
priorIndex,
|
|
1098
|
-
priorExtractorRuns,
|
|
1099
|
-
providerFrontmatter,
|
|
1100
|
-
pluginStores: options.pluginStores
|
|
1101
|
-
});
|
|
1102
|
-
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
1103
|
-
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
1104
|
-
for (const extractor of exts.extractors) {
|
|
1105
|
-
const extractorId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
1106
|
-
const evt = makeEvent("extractor.completed", { extractorId });
|
|
1107
|
-
emitter.emit(evt);
|
|
1108
|
-
await hookDispatcher.dispatch("extractor.completed", evt);
|
|
1109
|
-
}
|
|
1110
|
-
const analyzerResult = await runAnalyzers(
|
|
1111
|
-
exts.analyzers,
|
|
1112
|
-
walked.nodes,
|
|
1113
|
-
walked.internalLinks,
|
|
1114
|
-
walked.orphanSidecars,
|
|
1115
|
-
walked.sidecarRoots,
|
|
1116
|
-
options.annotationContributions ?? [],
|
|
1117
|
-
options.viewContributions ?? [],
|
|
1118
|
-
options.orphanJobFiles ?? [],
|
|
1119
|
-
options.referenceablePaths,
|
|
1120
|
-
options.cwd,
|
|
1121
|
-
emitter,
|
|
1122
|
-
hookDispatcher
|
|
1123
|
-
);
|
|
1124
|
-
const issues = analyzerResult.issues;
|
|
1125
|
-
for (const c of analyzerResult.contributions) walked.contributions.push(c);
|
|
1126
|
-
for (const analyzer of exts.analyzers ?? []) {
|
|
1127
|
-
if (analyzer.viewContributions === void 0) continue;
|
|
1128
|
-
for (const node of walked.nodes) {
|
|
1129
|
-
walked.freshlyRunTuples.add(`${analyzer.pluginId}\0${analyzer.id}\0${node.path}`);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
for (const issue of walked.frontmatterIssues) issues.push(issue);
|
|
1133
|
-
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
|
|
1134
|
-
const stats = {
|
|
1135
|
-
// `filesSkipped` is "files walked but not classified by any Provider".
|
|
1136
|
-
// Today every walked file IS classified by its Provider (the `claude`
|
|
1137
|
-
// Provider's `classify()` always returns a kind, falling back to
|
|
1138
|
-
// `'markdown'`), so this is always 0. Wired now so the field shape is
|
|
1139
|
-
// spec-conformant; meaningful once multiple Providers compete.
|
|
1140
|
-
filesWalked: walked.filesWalked,
|
|
1141
|
-
filesSkipped: 0,
|
|
1142
|
-
nodesCount: walked.nodes.length,
|
|
1143
|
-
linksCount: walked.internalLinks.length,
|
|
1144
|
-
issuesCount: issues.length,
|
|
1145
|
-
durationMs: Date.now() - start
|
|
1146
|
-
};
|
|
1147
|
-
const scanCompletedEvent = makeEvent("scan.completed", { stats });
|
|
1148
|
-
emitter.emit(scanCompletedEvent);
|
|
1149
|
-
await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
|
|
1150
|
-
return {
|
|
1151
|
-
result: {
|
|
1152
|
-
schemaVersion: 1,
|
|
1153
|
-
scannedAt,
|
|
1154
|
-
scope,
|
|
1155
|
-
roots: options.roots,
|
|
1156
|
-
providers: exts.providers.map((a) => a.id),
|
|
1157
|
-
scannedBy: SCANNED_BY,
|
|
1158
|
-
nodes: walked.nodes,
|
|
1159
|
-
links: walked.internalLinks,
|
|
1160
|
-
issues,
|
|
1161
|
-
stats
|
|
1162
|
-
},
|
|
1163
|
-
renameOps,
|
|
1164
|
-
extractorRuns: walked.extractorRuns,
|
|
1165
|
-
enrichments: walked.enrichments,
|
|
1166
|
-
contributions: walked.contributions,
|
|
1167
|
-
freshlyRunTuples: walked.freshlyRunTuples
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
function validateRoots(roots) {
|
|
1171
|
-
if (roots.length === 0) {
|
|
1172
|
-
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
1173
|
-
}
|
|
1174
|
-
for (const root of roots) {
|
|
1175
|
-
if (!existsSync8(root) || !statSync2(root).isDirectory()) {
|
|
1176
|
-
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
function indexPriorSnapshot(prior) {
|
|
1181
|
-
const priorNodesByPath = /* @__PURE__ */ new Map();
|
|
1182
|
-
const priorNodePaths = /* @__PURE__ */ new Set();
|
|
1183
|
-
const priorLinksByOriginating = /* @__PURE__ */ new Map();
|
|
1184
|
-
const priorFrontmatterIssuesByNode = /* @__PURE__ */ new Map();
|
|
1185
|
-
if (!prior) {
|
|
1186
|
-
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
1187
|
-
}
|
|
1188
|
-
for (const node of prior.nodes) {
|
|
1189
|
-
priorNodesByPath.set(node.path, node);
|
|
1190
|
-
priorNodePaths.add(node.path);
|
|
1191
|
-
}
|
|
1192
|
-
for (const link of prior.links) {
|
|
1193
|
-
const key = originatingNodeOf(link, priorNodePaths);
|
|
1194
|
-
const list = priorLinksByOriginating.get(key);
|
|
1195
|
-
if (list) list.push(link);
|
|
1196
|
-
else priorLinksByOriginating.set(key, [link]);
|
|
1197
|
-
}
|
|
1198
|
-
for (const issue of prior.issues) {
|
|
1199
|
-
if (issue.analyzerId !== "frontmatter-invalid" && issue.analyzerId !== "frontmatter-malformed") continue;
|
|
1200
|
-
if (issue.nodeIds.length !== 1) continue;
|
|
1201
|
-
const path = issue.nodeIds[0];
|
|
1202
|
-
const list = priorFrontmatterIssuesByNode.get(path);
|
|
1203
|
-
if (list) list.push(issue);
|
|
1204
|
-
else priorFrontmatterIssuesByNode.set(path, [issue]);
|
|
1205
|
-
}
|
|
1206
|
-
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
1207
|
-
}
|
|
1208
|
-
async function runExtractorsForNode(opts) {
|
|
1209
|
-
const internalLinks = [];
|
|
1210
|
-
const externalLinks = [];
|
|
1211
|
-
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
878
|
+
// kernel/orchestrator/analyzers.ts
|
|
879
|
+
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher) {
|
|
880
|
+
const issues = [];
|
|
1212
881
|
const contributions = [];
|
|
1213
882
|
const validators = loadSchemaValidators();
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
const
|
|
1223
|
-
const key = `${opts.node.path}\0${qualifiedId}`;
|
|
1224
|
-
const existing = enrichmentBuffer.get(key);
|
|
1225
|
-
if (existing) {
|
|
1226
|
-
existing.value = { ...existing.value, ...partial };
|
|
1227
|
-
existing.enrichedAt = Date.now();
|
|
1228
|
-
} else {
|
|
1229
|
-
enrichmentBuffer.set(key, {
|
|
1230
|
-
nodePath: opts.node.path,
|
|
1231
|
-
extractorId: qualifiedId,
|
|
1232
|
-
bodyHashAtEnrichment: opts.bodyHash,
|
|
1233
|
-
value: { ...partial },
|
|
1234
|
-
enrichedAt: Date.now(),
|
|
1235
|
-
// Extractors are deterministic-only; `is_probabilistic` is
|
|
1236
|
-
// reserved on the row for future Action-issued enrichments.
|
|
1237
|
-
isProbabilistic: false
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
};
|
|
1241
|
-
const declaredContributions = readDeclaredContributions(extractor);
|
|
1242
|
-
const emitContribution = (contributionId, payload) => {
|
|
883
|
+
validateRecommendedActions(analyzers, registeredActionIds, emitter);
|
|
884
|
+
const analyzerOrphans = orphanSidecars.map((o) => ({
|
|
885
|
+
relativePath: o.relativePath,
|
|
886
|
+
expectedMdPath: o.expectedMdPath
|
|
887
|
+
}));
|
|
888
|
+
for (const analyzer of analyzers) {
|
|
889
|
+
const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
890
|
+
const declaredContributions = readDeclaredContributions(analyzer);
|
|
891
|
+
const emitContribution = (nodePath, contributionId, payload) => {
|
|
1243
892
|
const declared = declaredContributions.get(contributionId);
|
|
1244
893
|
if (!declared) {
|
|
1245
|
-
emitExtensionError(
|
|
894
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
1246
895
|
phase: "emitContribution",
|
|
1247
896
|
contributionId,
|
|
1248
897
|
reason: "unknown-contribution-id",
|
|
1249
898
|
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
|
|
1250
899
|
extractorId: qualifiedId,
|
|
1251
900
|
contributionId,
|
|
1252
|
-
nodePath
|
|
901
|
+
nodePath
|
|
1253
902
|
})
|
|
1254
903
|
});
|
|
1255
904
|
return;
|
|
1256
905
|
}
|
|
1257
906
|
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
1258
907
|
if (!result.ok) {
|
|
1259
|
-
emitExtensionError(
|
|
908
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
1260
909
|
phase: "emitContribution",
|
|
1261
910
|
contributionId,
|
|
1262
911
|
slot: declared.slot,
|
|
@@ -1264,7 +913,7 @@ async function runExtractorsForNode(opts) {
|
|
|
1264
913
|
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
1265
914
|
extractorId: qualifiedId,
|
|
1266
915
|
contributionId,
|
|
1267
|
-
nodePath
|
|
916
|
+
nodePath,
|
|
1268
917
|
slot: declared.slot,
|
|
1269
918
|
errors: result.errors
|
|
1270
919
|
})
|
|
@@ -1272,56 +921,133 @@ async function runExtractorsForNode(opts) {
|
|
|
1272
921
|
return;
|
|
1273
922
|
}
|
|
1274
923
|
contributions.push({
|
|
1275
|
-
pluginId:
|
|
1276
|
-
extensionId:
|
|
1277
|
-
nodePath
|
|
924
|
+
pluginId: analyzer.pluginId,
|
|
925
|
+
extensionId: analyzer.id,
|
|
926
|
+
nodePath,
|
|
1278
927
|
contributionId,
|
|
1279
928
|
slot: declared.slot,
|
|
1280
929
|
payload,
|
|
1281
930
|
emittedAt: Date.now()
|
|
1282
931
|
});
|
|
1283
932
|
};
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
933
|
+
const emitted = await analyzer.evaluate({
|
|
934
|
+
nodes,
|
|
935
|
+
links: internalLinks,
|
|
936
|
+
orphanSidecars: analyzerOrphans,
|
|
937
|
+
sidecarRoots,
|
|
938
|
+
annotationContributions,
|
|
939
|
+
viewContributions,
|
|
940
|
+
orphanJobFiles,
|
|
941
|
+
...referenceablePaths ? { referenceablePaths } : {},
|
|
942
|
+
...cwd ? { cwd } : {},
|
|
943
|
+
emitContribution
|
|
944
|
+
});
|
|
945
|
+
for (const issue of emitted) {
|
|
946
|
+
const validated = validateIssue(analyzer, issue, emitter);
|
|
947
|
+
if (validated) issues.push(validated);
|
|
948
|
+
}
|
|
949
|
+
const evt = makeEvent("analyzer.completed", { analyzerId: qualifiedId });
|
|
950
|
+
emitter.emit(evt);
|
|
951
|
+
await hookDispatcher.dispatch("analyzer.completed", evt);
|
|
952
|
+
}
|
|
953
|
+
return { issues, contributions };
|
|
954
|
+
}
|
|
955
|
+
function validateRecommendedActions(analyzers, registeredActionIds, emitter) {
|
|
956
|
+
for (const analyzer of analyzers) {
|
|
957
|
+
const refs = analyzer.recommendedActions;
|
|
958
|
+
if (refs === void 0 || refs.length === 0) continue;
|
|
959
|
+
const analyzerId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
960
|
+
for (const actionId of refs) {
|
|
961
|
+
if (registeredActionIds.has(actionId)) continue;
|
|
962
|
+
emitter.emit(
|
|
963
|
+
makeEvent("extension.error", {
|
|
964
|
+
kind: "recommended-action-missing",
|
|
965
|
+
extensionId: analyzerId,
|
|
966
|
+
actionId,
|
|
967
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorRecommendedActionMissing, {
|
|
968
|
+
analyzerId,
|
|
969
|
+
actionId
|
|
970
|
+
})
|
|
971
|
+
})
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function validateIssue(analyzer, issue, emitter) {
|
|
977
|
+
const severity = issue.severity;
|
|
978
|
+
if (severity !== "error" && severity !== "warn" && severity !== "info") {
|
|
979
|
+
const qualifiedId = `${analyzer.pluginId}/${analyzer.id}`;
|
|
980
|
+
emitter.emit(
|
|
981
|
+
makeEvent("extension.error", {
|
|
982
|
+
kind: "issue-invalid-severity",
|
|
983
|
+
extensionId: qualifiedId,
|
|
984
|
+
severity,
|
|
985
|
+
issue: { analyzerId: issue.analyzerId || analyzer.id, message: issue.message, nodeIds: issue.nodeIds },
|
|
986
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorIssueInvalidSeverity, {
|
|
987
|
+
analyzerId: qualifiedId,
|
|
988
|
+
severity: JSON.stringify(severity)
|
|
989
|
+
})
|
|
990
|
+
})
|
|
1294
991
|
);
|
|
1295
|
-
|
|
992
|
+
return null;
|
|
1296
993
|
}
|
|
1297
|
-
return {
|
|
1298
|
-
internalLinks,
|
|
1299
|
-
externalLinks,
|
|
1300
|
-
enrichments: Array.from(enrichmentBuffer.values()),
|
|
1301
|
-
contributions
|
|
1302
|
-
};
|
|
994
|
+
return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
|
|
1303
995
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
996
|
+
|
|
997
|
+
// kernel/orchestrator/cache.ts
|
|
998
|
+
function indexPriorSnapshot(prior) {
|
|
999
|
+
const priorNodesByPath = /* @__PURE__ */ new Map();
|
|
1000
|
+
const priorNodePaths = /* @__PURE__ */ new Set();
|
|
1001
|
+
const priorLinksByOriginating = /* @__PURE__ */ new Map();
|
|
1002
|
+
const priorFrontmatterIssuesByNode = /* @__PURE__ */ new Map();
|
|
1003
|
+
if (!prior) {
|
|
1004
|
+
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
1313
1005
|
}
|
|
1314
|
-
|
|
1006
|
+
indexPriorNodes(prior.nodes, priorNodesByPath, priorNodePaths);
|
|
1007
|
+
indexPriorLinks(prior.links, priorNodePaths, priorLinksByOriginating);
|
|
1008
|
+
indexPriorFrontmatterIssues(prior.issues, priorFrontmatterIssuesByNode);
|
|
1009
|
+
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
1315
1010
|
}
|
|
1316
|
-
function
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1011
|
+
function indexPriorNodes(nodes, byPath2, paths) {
|
|
1012
|
+
for (const node of nodes) {
|
|
1013
|
+
byPath2.set(node.path, node);
|
|
1014
|
+
paths.add(node.path);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function indexPriorLinks(links, priorNodePaths, byOriginating) {
|
|
1018
|
+
for (const link of links) {
|
|
1019
|
+
const key = originatingNodeOf(link, priorNodePaths);
|
|
1020
|
+
const list = byOriginating.get(key);
|
|
1021
|
+
if (list) list.push(link);
|
|
1022
|
+
else byOriginating.set(key, [link]);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
|
|
1026
|
+
"frontmatter-invalid",
|
|
1027
|
+
"frontmatter-malformed",
|
|
1028
|
+
// Audit L1: parser parse-error is emitted by
|
|
1029
|
+
// `buildFreshNodeAndValidateFrontmatter` from `raw.parseIssues`. The
|
|
1030
|
+
// raw.parseIssues only flows through the non-cache path; a cached
|
|
1031
|
+
// node skips the rebuild, so the prior issue MUST survive the
|
|
1032
|
+
// incremental scan or the warning silently disappears on a clean
|
|
1033
|
+
// re-scan of an unchanged file.
|
|
1034
|
+
"frontmatter-parse-error"
|
|
1035
|
+
]);
|
|
1036
|
+
function indexPriorFrontmatterIssues(issues, byNode) {
|
|
1037
|
+
for (const issue of issues) {
|
|
1038
|
+
if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
|
|
1039
|
+
if (issue.nodeIds.length !== 1) continue;
|
|
1040
|
+
const path = issue.nodeIds[0];
|
|
1041
|
+
const list = byNode.get(path);
|
|
1042
|
+
if (list) list.push(issue);
|
|
1043
|
+
else byNode.set(path, [issue]);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function originatingNodeOf(link, priorNodePaths) {
|
|
1047
|
+
if (link.kind === "supersedes" && !priorNodePaths.has(link.source)) {
|
|
1048
|
+
return link.target;
|
|
1049
|
+
}
|
|
1050
|
+
return link.source;
|
|
1325
1051
|
}
|
|
1326
1052
|
function computeCacheDecision(opts) {
|
|
1327
1053
|
const applicableExtractors = opts.extractors.filter(
|
|
@@ -1330,35 +1056,39 @@ function computeCacheDecision(opts) {
|
|
|
1330
1056
|
const applicableQualifiedIds = new Set(
|
|
1331
1057
|
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
1332
1058
|
);
|
|
1059
|
+
const split = opts.priorExtractorRuns === void 0 ? splitLegacy(applicableExtractors, applicableQualifiedIds, opts.nodeHashCacheEligible) : splitFineGrained(applicableExtractors, opts);
|
|
1060
|
+
return {
|
|
1061
|
+
applicableExtractors,
|
|
1062
|
+
applicableQualifiedIds,
|
|
1063
|
+
cachedQualifiedIds: split.cachedQualifiedIds,
|
|
1064
|
+
missingExtractors: split.missingExtractors,
|
|
1065
|
+
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
1333
1069
|
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1334
1070
|
const missingExtractors = [];
|
|
1335
|
-
if (
|
|
1336
|
-
|
|
1337
|
-
for (const id of applicableQualifiedIds) cachedQualifiedIds.add(id);
|
|
1338
|
-
} else {
|
|
1339
|
-
for (const ex of applicableExtractors) missingExtractors.push(ex);
|
|
1340
|
-
}
|
|
1071
|
+
if (nodeHashCacheEligible) {
|
|
1072
|
+
for (const id of applicableQualifiedIds) cachedQualifiedIds.add(id);
|
|
1341
1073
|
} else {
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1074
|
+
for (const ex of applicableExtractors) missingExtractors.push(ex);
|
|
1075
|
+
}
|
|
1076
|
+
return { cachedQualifiedIds, missingExtractors };
|
|
1077
|
+
}
|
|
1078
|
+
function splitFineGrained(applicableExtractors, opts) {
|
|
1079
|
+
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1080
|
+
const missingExtractors = [];
|
|
1081
|
+
const priorRunsForNode = opts.priorExtractorRuns.get(opts.nodePath) ?? /* @__PURE__ */ new Map();
|
|
1082
|
+
for (const ex of applicableExtractors) {
|
|
1083
|
+
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1084
|
+
const prior = priorRunsForNode.get(qualified);
|
|
1085
|
+
if (opts.nodeHashCacheEligible && prior !== void 0 && prior.bodyHash === opts.bodyHash && prior.sidecarAnnotationsHash === opts.sidecarAnnotationsHash) {
|
|
1086
|
+
cachedQualifiedIds.add(qualified);
|
|
1087
|
+
} else {
|
|
1088
|
+
missingExtractors.push(ex);
|
|
1353
1089
|
}
|
|
1354
1090
|
}
|
|
1355
|
-
return {
|
|
1356
|
-
applicableExtractors,
|
|
1357
|
-
applicableQualifiedIds,
|
|
1358
|
-
cachedQualifiedIds,
|
|
1359
|
-
missingExtractors,
|
|
1360
|
-
fullCacheHit: opts.nodeHashCacheEligible && missingExtractors.length === 0
|
|
1361
|
-
};
|
|
1091
|
+
return { cachedQualifiedIds, missingExtractors };
|
|
1362
1092
|
}
|
|
1363
1093
|
function cloneNodeAndReshapeLinks(opts) {
|
|
1364
1094
|
const node = { ...opts.priorNode, bytes: { ...opts.priorNode.bytes } };
|
|
@@ -1396,467 +1126,645 @@ function reusePriorNode(opts) {
|
|
|
1396
1126
|
}
|
|
1397
1127
|
return { ...base, extractorRuns };
|
|
1398
1128
|
}
|
|
1399
|
-
function
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1129
|
+
function reuseCachedLink(link, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1130
|
+
if (!Array.isArray(link.sources) || link.sources.length === 0) return null;
|
|
1131
|
+
const partition = partitionLinkSources(
|
|
1132
|
+
link.sources,
|
|
1133
|
+
shortIdToQualified,
|
|
1134
|
+
cachedQualifiedIds,
|
|
1135
|
+
applicableQualifiedIds
|
|
1136
|
+
);
|
|
1137
|
+
if (partition.hasMissing) return null;
|
|
1138
|
+
if (partition.cached.length === 0) return null;
|
|
1139
|
+
if (partition.obsolete.length === 0) return link;
|
|
1140
|
+
return { ...link, sources: partition.cached };
|
|
1141
|
+
}
|
|
1142
|
+
function partitionLinkSources(sources, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1143
|
+
const cached = [];
|
|
1144
|
+
const obsolete = [];
|
|
1145
|
+
let hasMissing = false;
|
|
1146
|
+
for (const source of sources) {
|
|
1147
|
+
const category = classifyLinkSource(
|
|
1148
|
+
source,
|
|
1149
|
+
shortIdToQualified,
|
|
1150
|
+
cachedQualifiedIds,
|
|
1151
|
+
applicableQualifiedIds
|
|
1420
1152
|
);
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
if (malformed) frontmatterIssues.push(malformed);
|
|
1153
|
+
if (category === "cached") cached.push(source);
|
|
1154
|
+
else if (category === "missing") hasMissing = true;
|
|
1155
|
+
else obsolete.push(source);
|
|
1425
1156
|
}
|
|
1426
|
-
return {
|
|
1157
|
+
return { cached, obsolete, hasMissing };
|
|
1427
1158
|
}
|
|
1428
|
-
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
1451
|
-
const contributionsBuffer = [];
|
|
1452
|
-
const freshlyRunTuples = /* @__PURE__ */ new Set();
|
|
1453
|
-
const extractorRuns = [];
|
|
1454
|
-
const sidecarRoots = /* @__PURE__ */ new Map();
|
|
1455
|
-
let filesWalked = 0;
|
|
1456
|
-
let index = 0;
|
|
1457
|
-
const walkOptions = ignoreFilter ? { ignoreFilter } : {};
|
|
1458
|
-
const shortIdToQualified = /* @__PURE__ */ new Map();
|
|
1459
|
-
for (const ex of extractors) {
|
|
1460
|
-
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1461
|
-
const list = shortIdToQualified.get(ex.id);
|
|
1462
|
-
if (list) list.push(qualified);
|
|
1463
|
-
else shortIdToQualified.set(ex.id, [qualified]);
|
|
1464
|
-
}
|
|
1465
|
-
const claimedPaths = /* @__PURE__ */ new Set();
|
|
1466
|
-
for (const provider of providers) {
|
|
1467
|
-
for await (const raw of resolveProviderWalk(provider)(roots, walkOptions)) {
|
|
1468
|
-
filesWalked += 1;
|
|
1469
|
-
if (claimedPaths.has(raw.path)) continue;
|
|
1470
|
-
const bodyHash = sha256(raw.body);
|
|
1471
|
-
const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
|
|
1472
|
-
const priorNode = priorNodesByPath.get(raw.path);
|
|
1473
|
-
const nodeHashCacheEligible = enableCache && prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
|
|
1474
|
-
const kind = provider.classify(raw.path, raw.frontmatter);
|
|
1475
|
-
if (kind === null) {
|
|
1476
|
-
continue;
|
|
1477
|
-
}
|
|
1478
|
-
claimedPaths.add(raw.path);
|
|
1479
|
-
index += 1;
|
|
1480
|
-
const sidecarResolution = resolveSidecarOverlay(
|
|
1481
|
-
raw.path,
|
|
1482
|
-
raw.path,
|
|
1483
|
-
roots,
|
|
1484
|
-
bodyHash,
|
|
1485
|
-
frontmatterHash
|
|
1486
|
-
);
|
|
1487
|
-
const sidecarAnnotationsHash = sha256(
|
|
1488
|
-
canonicalSidecarAnnotations(sidecarResolution.overlay.annotations)
|
|
1489
|
-
);
|
|
1490
|
-
const cacheDecision = computeCacheDecision({
|
|
1491
|
-
extractors,
|
|
1492
|
-
kind,
|
|
1493
|
-
nodePath: raw.path,
|
|
1494
|
-
bodyHash,
|
|
1495
|
-
sidecarAnnotationsHash,
|
|
1496
|
-
nodeHashCacheEligible,
|
|
1497
|
-
priorExtractorRuns
|
|
1498
|
-
});
|
|
1499
|
-
const {
|
|
1500
|
-
applicableExtractors,
|
|
1501
|
-
applicableQualifiedIds,
|
|
1502
|
-
cachedQualifiedIds,
|
|
1503
|
-
missingExtractors,
|
|
1504
|
-
fullCacheHit
|
|
1505
|
-
} = cacheDecision;
|
|
1506
|
-
const attachSidecar = (node2) => {
|
|
1507
|
-
node2.sidecar = sidecarResolution.overlay;
|
|
1508
|
-
if (sidecarResolution.parsedRoot !== null) {
|
|
1509
|
-
sidecarRoots.set(node2.path, sidecarResolution.parsedRoot);
|
|
1510
|
-
}
|
|
1511
|
-
return sidecarResolution.issues.map(
|
|
1512
|
-
(i) => i.nodeIds.length > 0 ? i : { ...i, nodeIds: [node2.path] }
|
|
1513
|
-
);
|
|
1514
|
-
};
|
|
1515
|
-
if (fullCacheHit && priorNode) {
|
|
1516
|
-
const reused = reusePriorNode({
|
|
1517
|
-
priorNode,
|
|
1518
|
-
bodyHash,
|
|
1519
|
-
sidecarAnnotationsHash,
|
|
1520
|
-
strict,
|
|
1521
|
-
cachedQualifiedIds,
|
|
1522
|
-
applicableQualifiedIds,
|
|
1523
|
-
shortIdToQualified,
|
|
1524
|
-
priorLinksByOriginating,
|
|
1525
|
-
priorFrontmatterIssuesByNode
|
|
1526
|
-
});
|
|
1527
|
-
const reusedSidecarIssues = attachSidecar(reused.node);
|
|
1528
|
-
nodes.push(reused.node);
|
|
1529
|
-
cachedPaths.add(reused.node.path);
|
|
1530
|
-
for (const link of reused.internalLinks) internalLinks.push(link);
|
|
1531
|
-
for (const issue of reused.frontmatterIssues) frontmatterIssues.push(issue);
|
|
1532
|
-
for (const issue of reusedSidecarIssues) frontmatterIssues.push(issue);
|
|
1533
|
-
for (const run of reused.extractorRuns) extractorRuns.push(run);
|
|
1534
|
-
emitter.emit(makeEvent("scan.progress", { index, path: raw.path, kind, cached: true }));
|
|
1535
|
-
continue;
|
|
1536
|
-
}
|
|
1537
|
-
let node;
|
|
1538
|
-
const partialCacheHit = nodeHashCacheEligible && cachedQualifiedIds.size > 0 && priorNode !== void 0;
|
|
1539
|
-
if (partialCacheHit && priorNode) {
|
|
1540
|
-
const partial = cloneNodeAndReshapeLinks({
|
|
1541
|
-
priorNode,
|
|
1542
|
-
strict,
|
|
1543
|
-
cachedQualifiedIds,
|
|
1544
|
-
applicableQualifiedIds,
|
|
1545
|
-
shortIdToQualified,
|
|
1546
|
-
priorLinksByOriginating,
|
|
1547
|
-
priorFrontmatterIssuesByNode
|
|
1548
|
-
});
|
|
1549
|
-
node = partial.node;
|
|
1550
|
-
for (const link of partial.internalLinks) internalLinks.push(link);
|
|
1551
|
-
for (const issue of partial.frontmatterIssues) frontmatterIssues.push(issue);
|
|
1552
|
-
nodes.push(node);
|
|
1553
|
-
} else {
|
|
1554
|
-
const fresh = buildFreshNodeAndValidateFrontmatter({
|
|
1555
|
-
raw,
|
|
1556
|
-
kind,
|
|
1557
|
-
provider,
|
|
1558
|
-
bodyHash,
|
|
1559
|
-
frontmatterHash,
|
|
1560
|
-
encoder,
|
|
1561
|
-
providerFrontmatter,
|
|
1562
|
-
strict
|
|
1563
|
-
});
|
|
1564
|
-
node = fresh.node;
|
|
1565
|
-
nodes.push(node);
|
|
1566
|
-
for (const issue of fresh.frontmatterIssues) frontmatterIssues.push(issue);
|
|
1567
|
-
}
|
|
1568
|
-
const sidecarIssues = attachSidecar(node);
|
|
1569
|
-
for (const issue of sidecarIssues) frontmatterIssues.push(issue);
|
|
1570
|
-
emitter.emit(makeEvent("scan.progress", {
|
|
1571
|
-
index,
|
|
1572
|
-
path: raw.path,
|
|
1573
|
-
kind,
|
|
1574
|
-
cached: false,
|
|
1575
|
-
...partialCacheHit ? { partialCache: true } : {}
|
|
1576
|
-
}));
|
|
1577
|
-
const extractorsToRun = partialCacheHit ? missingExtractors : applicableExtractors;
|
|
1578
|
-
for (const ex of extractorsToRun) {
|
|
1579
|
-
freshlyRunTuples.add(`${ex.pluginId}\0${ex.id}\0${node.path}`);
|
|
1580
|
-
}
|
|
1581
|
-
const extractResult = await runExtractorsForNode({
|
|
1582
|
-
extractors: extractorsToRun,
|
|
1583
|
-
node,
|
|
1584
|
-
body: raw.body,
|
|
1585
|
-
frontmatter: raw.frontmatter,
|
|
1586
|
-
bodyHash,
|
|
1587
|
-
emitter,
|
|
1588
|
-
...pluginStores ? { pluginStores } : {}
|
|
1589
|
-
});
|
|
1590
|
-
for (const link of extractResult.internalLinks) internalLinks.push(link);
|
|
1591
|
-
for (const link of extractResult.externalLinks) externalLinks.push(link);
|
|
1592
|
-
for (const enr of extractResult.enrichments) {
|
|
1593
|
-
enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
1594
|
-
}
|
|
1595
|
-
for (const c of extractResult.contributions) contributionsBuffer.push(c);
|
|
1596
|
-
const ranAt = Date.now();
|
|
1597
|
-
for (const ex of applicableExtractors) {
|
|
1598
|
-
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1599
|
-
extractorRuns.push({
|
|
1600
|
-
nodePath: node.path,
|
|
1601
|
-
extractorId: qualified,
|
|
1602
|
-
bodyHashAtRun: bodyHash,
|
|
1603
|
-
ranAt,
|
|
1604
|
-
sidecarAnnotationsHashAtRun: sidecarAnnotationsHash
|
|
1605
|
-
});
|
|
1159
|
+
function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1160
|
+
const candidates = shortIdToQualified.get(source);
|
|
1161
|
+
if (!candidates || candidates.length === 0) return "obsolete";
|
|
1162
|
+
if (candidates.some((q) => cachedQualifiedIds.has(q))) return "cached";
|
|
1163
|
+
if (candidates.some((q) => applicableQualifiedIds.has(q))) return "missing";
|
|
1164
|
+
return "obsolete";
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// kernel/orchestrator/renames.ts
|
|
1168
|
+
function findHighConfidenceRenames(opts) {
|
|
1169
|
+
const ops = [];
|
|
1170
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1171
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1172
|
+
const fromNode = opts.priorByPath.get(fromPath);
|
|
1173
|
+
for (const toPath of opts.newPaths) {
|
|
1174
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1175
|
+
const toNode = opts.currentByPath.get(toPath);
|
|
1176
|
+
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
1177
|
+
ops.push({ from: fromPath, to: toPath, confidence: "high" });
|
|
1178
|
+
opts.claimedDeleted.add(fromPath);
|
|
1179
|
+
opts.claimedNew.add(toPath);
|
|
1180
|
+
break;
|
|
1606
1181
|
}
|
|
1607
1182
|
}
|
|
1608
1183
|
}
|
|
1609
|
-
|
|
1610
|
-
return {
|
|
1611
|
-
nodes,
|
|
1612
|
-
internalLinks,
|
|
1613
|
-
externalLinks,
|
|
1614
|
-
cachedPaths,
|
|
1615
|
-
frontmatterIssues,
|
|
1616
|
-
filesWalked,
|
|
1617
|
-
enrichments: [...enrichmentBuffer.values()],
|
|
1618
|
-
extractorRuns,
|
|
1619
|
-
contributions: contributionsBuffer,
|
|
1620
|
-
freshlyRunTuples,
|
|
1621
|
-
orphanSidecars,
|
|
1622
|
-
sidecarRoots
|
|
1623
|
-
};
|
|
1184
|
+
return ops;
|
|
1624
1185
|
}
|
|
1625
|
-
function
|
|
1626
|
-
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
cachedSources.push(source);
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
if (candidates.some((q) => applicableQualifiedIds.has(q))) {
|
|
1641
|
-
hasMissing = true;
|
|
1642
|
-
continue;
|
|
1186
|
+
function buildFrontmatterRenameCandidates(opts) {
|
|
1187
|
+
const candidatesByNew = /* @__PURE__ */ new Map();
|
|
1188
|
+
for (const toPath of opts.newPaths) {
|
|
1189
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1190
|
+
const toNode = opts.currentByPath.get(toPath);
|
|
1191
|
+
const matches = [];
|
|
1192
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1193
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1194
|
+
const fromNode = opts.priorByPath.get(fromPath);
|
|
1195
|
+
if (toNode.frontmatterHash === fromNode.frontmatterHash) {
|
|
1196
|
+
matches.push(fromPath);
|
|
1197
|
+
}
|
|
1643
1198
|
}
|
|
1644
|
-
|
|
1199
|
+
if (matches.length > 0) candidatesByNew.set(toPath, matches);
|
|
1645
1200
|
}
|
|
1646
|
-
|
|
1647
|
-
if (cachedSources.length === 0) return null;
|
|
1648
|
-
if (obsoleteSources.length === 0) return link;
|
|
1649
|
-
return { ...link, sources: cachedSources };
|
|
1201
|
+
return candidatesByNew;
|
|
1650
1202
|
}
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1203
|
+
function claimSingletonRenames(opts) {
|
|
1204
|
+
const ops = [];
|
|
1205
|
+
for (const toPath of opts.newPaths) {
|
|
1206
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1207
|
+
const candidates = opts.candidatesByNew.get(toPath);
|
|
1208
|
+
if (!candidates) continue;
|
|
1209
|
+
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1210
|
+
if (remaining.length === 1) {
|
|
1211
|
+
const fromPath = remaining[0];
|
|
1212
|
+
ops.push({ from: fromPath, to: toPath, confidence: "medium" });
|
|
1213
|
+
opts.issues.push({
|
|
1214
|
+
analyzerId: "auto-rename-medium",
|
|
1215
|
+
severity: "warn",
|
|
1216
|
+
nodeIds: [toPath],
|
|
1217
|
+
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
1218
|
+
data: { from: fromPath, to: toPath, confidence: "medium" }
|
|
1219
|
+
});
|
|
1220
|
+
opts.claimedDeleted.add(fromPath);
|
|
1221
|
+
opts.claimedNew.add(toPath);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return ops;
|
|
1225
|
+
}
|
|
1226
|
+
function flagAmbiguousRenames(opts) {
|
|
1227
|
+
for (const toPath of opts.newPaths) {
|
|
1228
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1229
|
+
const candidates = opts.candidatesByNew.get(toPath);
|
|
1230
|
+
if (!candidates) continue;
|
|
1231
|
+
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1232
|
+
if (remaining.length > 1) {
|
|
1233
|
+
opts.issues.push({
|
|
1234
|
+
analyzerId: "auto-rename-ambiguous",
|
|
1235
|
+
severity: "warn",
|
|
1236
|
+
nodeIds: [toPath],
|
|
1237
|
+
message: `Auto-rename ambiguous: ${toPath} matches ${remaining.length} prior frontmatters; pick one with \`sm orphans undo-rename ${toPath} --from <old.path>\`.`,
|
|
1238
|
+
data: { to: toPath, candidates: remaining }
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
function flagOrphans(opts) {
|
|
1244
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1245
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1246
|
+
opts.issues.push({
|
|
1247
|
+
analyzerId: "orphan",
|
|
1248
|
+
severity: "info",
|
|
1249
|
+
nodeIds: [fromPath],
|
|
1250
|
+
message: `Orphan history: ${fromPath} was deleted; no rename match found.`,
|
|
1251
|
+
data: { path: fromPath }
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
function detectRenamesAndOrphans(prior, current, issues) {
|
|
1256
|
+
const priorByPath = /* @__PURE__ */ new Map();
|
|
1257
|
+
for (const n of prior.nodes) priorByPath.set(n.path, n);
|
|
1258
|
+
const currentByPath = /* @__PURE__ */ new Map();
|
|
1259
|
+
for (const n of current) currentByPath.set(n.path, n);
|
|
1260
|
+
const deletedPaths = [...priorByPath.keys()].filter((p) => !currentByPath.has(p)).sort();
|
|
1261
|
+
const newPaths = [...currentByPath.keys()].filter((p) => !priorByPath.has(p)).sort();
|
|
1262
|
+
const claimedDeleted = /* @__PURE__ */ new Set();
|
|
1263
|
+
const claimedNew = /* @__PURE__ */ new Set();
|
|
1264
|
+
const ops = [];
|
|
1265
|
+
ops.push(...findHighConfidenceRenames({
|
|
1266
|
+
deletedPaths,
|
|
1267
|
+
newPaths,
|
|
1268
|
+
priorByPath,
|
|
1269
|
+
currentByPath,
|
|
1270
|
+
claimedDeleted,
|
|
1271
|
+
claimedNew
|
|
1658
1272
|
}));
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1273
|
+
const candidatesByNew = buildFrontmatterRenameCandidates({
|
|
1274
|
+
deletedPaths,
|
|
1275
|
+
newPaths,
|
|
1276
|
+
priorByPath,
|
|
1277
|
+
currentByPath,
|
|
1278
|
+
claimedDeleted,
|
|
1279
|
+
claimedNew
|
|
1280
|
+
});
|
|
1281
|
+
ops.push(...claimSingletonRenames({
|
|
1282
|
+
newPaths,
|
|
1283
|
+
candidatesByNew,
|
|
1284
|
+
claimedDeleted,
|
|
1285
|
+
claimedNew,
|
|
1286
|
+
issues
|
|
1287
|
+
}));
|
|
1288
|
+
flagAmbiguousRenames({ newPaths, candidatesByNew, claimedDeleted, claimedNew, issues });
|
|
1289
|
+
flagOrphans({ deletedPaths, claimedDeleted, issues });
|
|
1290
|
+
return ops;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// kernel/scan/walk-content.ts
|
|
1294
|
+
import { readFile, readdir, lstat } from "fs/promises";
|
|
1295
|
+
import { join as join2, relative as relative2, sep } from "path";
|
|
1296
|
+
|
|
1297
|
+
// kernel/scan/ignore.ts
|
|
1298
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
1299
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
1300
|
+
import { fileURLToPath } from "url";
|
|
1301
|
+
import ignoreFactory from "ignore";
|
|
1302
|
+
function buildIgnoreFilter(opts = {}) {
|
|
1303
|
+
const ig = ignoreFactory();
|
|
1304
|
+
if (opts.includeDefaults !== false) {
|
|
1305
|
+
ig.add(loadDefaultsText());
|
|
1306
|
+
}
|
|
1307
|
+
if (opts.configIgnore && opts.configIgnore.length > 0) {
|
|
1308
|
+
ig.add(opts.configIgnore);
|
|
1309
|
+
}
|
|
1310
|
+
if (opts.ignoreFileText && opts.ignoreFileText.length > 0) {
|
|
1311
|
+
ig.add(opts.ignoreFileText);
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
ignores(relativePath) {
|
|
1315
|
+
if (relativePath === "" || relativePath === "." || relativePath === "./") {
|
|
1316
|
+
return false;
|
|
1676
1317
|
}
|
|
1677
|
-
const
|
|
1678
|
-
if (
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1318
|
+
const normalised = relativePath.replace(/^\.\//, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
1319
|
+
if (normalised === "") return false;
|
|
1320
|
+
return ig.ignores(normalised);
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
var cachedDefaults = null;
|
|
1325
|
+
function loadDefaultsText() {
|
|
1326
|
+
if (cachedDefaults !== null) return cachedDefaults;
|
|
1327
|
+
cachedDefaults = readDefaultsFromDisk();
|
|
1328
|
+
return cachedDefaults;
|
|
1329
|
+
}
|
|
1330
|
+
function readDefaultsFromDisk() {
|
|
1331
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
1332
|
+
const candidates = [
|
|
1333
|
+
resolve5(here, "../../config/defaults/skillmapignore"),
|
|
1334
|
+
// src/kernel/scan/ → src/config/defaults/
|
|
1335
|
+
resolve5(here, "../config/defaults/skillmapignore"),
|
|
1336
|
+
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
1337
|
+
resolve5(here, "config/defaults/skillmapignore")
|
|
1338
|
+
];
|
|
1339
|
+
for (const candidate of candidates) {
|
|
1340
|
+
if (existsSync2(candidate)) {
|
|
1341
|
+
try {
|
|
1342
|
+
return readFileSync4(candidate, "utf8");
|
|
1343
|
+
} catch {
|
|
1693
1344
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return "";
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// built-in-plugins/parsers/frontmatter-yaml/index.ts
|
|
1351
|
+
import yaml from "js-yaml";
|
|
1352
|
+
|
|
1353
|
+
// kernel/util/strip-prototype-pollution.ts
|
|
1354
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
1355
|
+
"__proto__",
|
|
1356
|
+
"constructor",
|
|
1357
|
+
"prototype"
|
|
1358
|
+
]);
|
|
1359
|
+
function stripPrototypePollution(value) {
|
|
1360
|
+
return strip(value);
|
|
1361
|
+
}
|
|
1362
|
+
function strip(value) {
|
|
1363
|
+
if (value === null || value === void 0) return value;
|
|
1364
|
+
if (typeof value !== "object") return value;
|
|
1365
|
+
if (Array.isArray(value)) return value.map(strip);
|
|
1366
|
+
const out = {};
|
|
1367
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1368
|
+
if (FORBIDDEN_KEYS.has(k)) continue;
|
|
1369
|
+
out[k] = strip(v);
|
|
1370
|
+
}
|
|
1371
|
+
return out;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// built-in-plugins/parsers/frontmatter-yaml/index.ts
|
|
1375
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1376
|
+
var frontmatterYamlParser = {
|
|
1377
|
+
id: "frontmatter-yaml",
|
|
1378
|
+
parse(raw, _path) {
|
|
1379
|
+
const match = FRONTMATTER_RE.exec(raw);
|
|
1380
|
+
if (!match) return { frontmatterRaw: "", frontmatter: {}, body: raw };
|
|
1381
|
+
const frontmatterRaw = match[1];
|
|
1382
|
+
const body = match[2];
|
|
1383
|
+
let parsed = {};
|
|
1384
|
+
const issues = [];
|
|
1385
|
+
try {
|
|
1386
|
+
const doc = yaml.load(frontmatterRaw, { schema: yaml.JSON_SCHEMA });
|
|
1387
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
1388
|
+
parsed = stripPrototypePollution(doc);
|
|
1389
|
+
}
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
issues.push({
|
|
1392
|
+
code: "frontmatter-parse-error",
|
|
1393
|
+
message: sanitiseParseErrorMessage(err)
|
|
1702
1394
|
});
|
|
1703
|
-
};
|
|
1704
|
-
const emitted = await analyzer.evaluate({
|
|
1705
|
-
nodes,
|
|
1706
|
-
links: internalLinks,
|
|
1707
|
-
orphanSidecars: analyzerOrphans,
|
|
1708
|
-
sidecarRoots,
|
|
1709
|
-
annotationContributions,
|
|
1710
|
-
viewContributions,
|
|
1711
|
-
orphanJobFiles,
|
|
1712
|
-
...referenceablePaths ? { referenceablePaths } : {},
|
|
1713
|
-
...cwd ? { cwd } : {},
|
|
1714
|
-
emitContribution
|
|
1715
|
-
});
|
|
1716
|
-
for (const issue of emitted) {
|
|
1717
|
-
const validated = validateIssue(analyzer, issue, emitter);
|
|
1718
|
-
if (validated) issues.push(validated);
|
|
1719
1395
|
}
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1396
|
+
const out = { frontmatterRaw, frontmatter: parsed, body };
|
|
1397
|
+
if (issues.length > 0) {
|
|
1398
|
+
return { ...out, issues };
|
|
1399
|
+
}
|
|
1400
|
+
return out;
|
|
1723
1401
|
}
|
|
1724
|
-
|
|
1402
|
+
};
|
|
1403
|
+
function sanitiseParseErrorMessage(err) {
|
|
1404
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
1405
|
+
return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1725
1406
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1407
|
+
|
|
1408
|
+
// built-in-plugins/parsers/plain/index.ts
|
|
1409
|
+
var plainParser = {
|
|
1410
|
+
id: "plain",
|
|
1411
|
+
parse(raw, _path) {
|
|
1412
|
+
return { frontmatter: {}, frontmatterRaw: "", body: raw };
|
|
1729
1413
|
}
|
|
1730
|
-
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// kernel/scan/parsers/index.ts
|
|
1417
|
+
var REGISTRY = /* @__PURE__ */ new Map([
|
|
1418
|
+
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
1419
|
+
[plainParser.id, plainParser]
|
|
1420
|
+
]);
|
|
1421
|
+
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
1422
|
+
function getParser(id) {
|
|
1423
|
+
return REGISTRY.get(id);
|
|
1731
1424
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1425
|
+
|
|
1426
|
+
// kernel/scan/walk-content.ts
|
|
1427
|
+
var UnknownParserError = class extends Error {
|
|
1428
|
+
constructor(parserId) {
|
|
1429
|
+
super(`Unknown parser id '${parserId}'. Built-in parsers: 'frontmatter-yaml', 'plain'.`);
|
|
1430
|
+
this.name = "UnknownParserError";
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
async function* walkContent(roots, options) {
|
|
1434
|
+
const parser = getParser(options.parser);
|
|
1435
|
+
if (!parser) throw new UnknownParserError(options.parser);
|
|
1436
|
+
const filter = options.ignoreFilter ?? buildIgnoreFilter();
|
|
1437
|
+
const extensions = options.extensions;
|
|
1438
|
+
for (const root of roots) {
|
|
1439
|
+
for await (const file of walkRoot(root, root, filter, extensions)) {
|
|
1440
|
+
const relPath = relative2(root, file).split(sep).join("/");
|
|
1441
|
+
let raw;
|
|
1442
|
+
try {
|
|
1443
|
+
raw = await readFile(file, "utf8");
|
|
1444
|
+
} catch {
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const parsed = parser.parse(raw, relPath);
|
|
1448
|
+
yield {
|
|
1449
|
+
path: relPath,
|
|
1450
|
+
body: parsed.body,
|
|
1451
|
+
frontmatterRaw: parsed.frontmatterRaw,
|
|
1452
|
+
frontmatter: parsed.frontmatter,
|
|
1453
|
+
// Audit L1: forward parser diagnostics (e.g. malformed YAML)
|
|
1454
|
+
// through the IRawNode surface so the orchestrator can
|
|
1455
|
+
// convert them into warn-level kernel `Issue` rows. Omitted
|
|
1456
|
+
// when the parser reported no issues (happy path).
|
|
1457
|
+
...parsed.issues && parsed.issues.length > 0 ? { parseIssues: parsed.issues } : {}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async function* walkRoot(root, current, filter, extensions) {
|
|
1463
|
+
let entries;
|
|
1464
|
+
try {
|
|
1465
|
+
entries = await readdir(current, { withFileTypes: true, encoding: "utf8" });
|
|
1466
|
+
} catch {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
for (const entry of entries) {
|
|
1470
|
+
const name = entry.name;
|
|
1471
|
+
const full = join2(current, name);
|
|
1472
|
+
const rel = relative2(root, full).split(sep).join("/");
|
|
1473
|
+
if (filter.ignores(rel)) continue;
|
|
1474
|
+
if (entry.isSymbolicLink()) continue;
|
|
1475
|
+
if (entry.isDirectory()) {
|
|
1476
|
+
yield* walkRoot(root, full, filter, extensions);
|
|
1477
|
+
} else if (entry.isFile() && hasMatchingExtension(name, extensions)) {
|
|
1478
|
+
try {
|
|
1479
|
+
const s = await lstat(full);
|
|
1480
|
+
if (s.isFile()) yield full;
|
|
1481
|
+
} catch {
|
|
1745
1482
|
}
|
|
1746
1483
|
}
|
|
1747
1484
|
}
|
|
1748
|
-
return ops;
|
|
1749
1485
|
}
|
|
1750
|
-
function
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1486
|
+
function hasMatchingExtension(name, extensions) {
|
|
1487
|
+
for (const ext of extensions) {
|
|
1488
|
+
if (name.endsWith(ext)) return true;
|
|
1489
|
+
}
|
|
1490
|
+
return false;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// kernel/extensions/provider.ts
|
|
1494
|
+
var DEFAULT_READ_CONFIG = Object.freeze({
|
|
1495
|
+
extensions: Object.freeze([".md"]),
|
|
1496
|
+
parser: "frontmatter-yaml"
|
|
1497
|
+
});
|
|
1498
|
+
function resolveProviderWalk(provider) {
|
|
1499
|
+
if (provider.walk) {
|
|
1500
|
+
const walk2 = provider.walk.bind(provider);
|
|
1501
|
+
return walk2;
|
|
1502
|
+
}
|
|
1503
|
+
const read = provider.read ?? DEFAULT_READ_CONFIG;
|
|
1504
|
+
return (roots, options) => {
|
|
1505
|
+
const walkOptions = {
|
|
1506
|
+
extensions: read.extensions,
|
|
1507
|
+
parser: read.parser
|
|
1508
|
+
};
|
|
1509
|
+
if (options?.ignoreFilter) walkOptions.ignoreFilter = options.ignoreFilter;
|
|
1510
|
+
return walkContent(roots, walkOptions);
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// kernel/sidecar/parse.ts
|
|
1515
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
1516
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
1517
|
+
import { createRequire as createRequire3 } from "module";
|
|
1518
|
+
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
1519
|
+
import yaml2 from "js-yaml";
|
|
1520
|
+
function readSidecarFor(mdAbsolutePath) {
|
|
1521
|
+
const sidecarPath = sidecarPathFor(mdAbsolutePath);
|
|
1522
|
+
if (!existsSync3(sidecarPath)) {
|
|
1523
|
+
return { parsed: null, present: false, issues: [] };
|
|
1524
|
+
}
|
|
1525
|
+
let raw;
|
|
1526
|
+
try {
|
|
1527
|
+
raw = readFileSync5(sidecarPath, "utf8");
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
return {
|
|
1530
|
+
parsed: null,
|
|
1531
|
+
present: true,
|
|
1532
|
+
issues: [{ message: `cannot read ${sidecarPath}: ${err.message}` }]
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
let parsedYaml;
|
|
1536
|
+
try {
|
|
1537
|
+
parsedYaml = yaml2.load(raw);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
return {
|
|
1540
|
+
parsed: null,
|
|
1541
|
+
present: true,
|
|
1542
|
+
issues: [{ message: `malformed YAML in ${sidecarPath}: ${err.message}` }]
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
parsedYaml = stripPrototypePollution(parsedYaml);
|
|
1546
|
+
if (!isPlainObject(parsedYaml)) {
|
|
1547
|
+
return {
|
|
1548
|
+
parsed: null,
|
|
1549
|
+
present: true,
|
|
1550
|
+
issues: [{ message: `sidecar root must be a YAML mapping at ${sidecarPath}` }]
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
const sidecarValidator = getSidecarValidator();
|
|
1554
|
+
if (!sidecarValidator(parsedYaml)) {
|
|
1555
|
+
const errors = (sidecarValidator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
1556
|
+
return {
|
|
1557
|
+
parsed: null,
|
|
1558
|
+
present: true,
|
|
1559
|
+
issues: [{ message: `sidecar schema validation failed at ${sidecarPath}: ${errors}` }]
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
const root = parsedYaml;
|
|
1563
|
+
const identityBlock = root["identity"];
|
|
1564
|
+
const annotationsRaw = root["annotations"];
|
|
1565
|
+
const annotations = isPlainObject(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
|
|
1566
|
+
return {
|
|
1567
|
+
parsed: {
|
|
1568
|
+
filePath: sidecarPath,
|
|
1569
|
+
identityBodyHash: String(identityBlock["bodyHash"]),
|
|
1570
|
+
identityFrontmatterHash: String(identityBlock["frontmatterHash"]),
|
|
1571
|
+
identityPath: String(identityBlock["path"]),
|
|
1572
|
+
annotations,
|
|
1573
|
+
raw: root
|
|
1574
|
+
},
|
|
1575
|
+
present: true,
|
|
1576
|
+
issues: []
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function sidecarPathFor(mdAbsolutePath) {
|
|
1580
|
+
if (mdAbsolutePath.endsWith(".md")) {
|
|
1581
|
+
return `${mdAbsolutePath.slice(0, -".md".length)}.sm`;
|
|
1582
|
+
}
|
|
1583
|
+
return `${mdAbsolutePath}.sm`;
|
|
1584
|
+
}
|
|
1585
|
+
function isPlainObject(value) {
|
|
1586
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1587
|
+
}
|
|
1588
|
+
var cachedSidecarValidator = null;
|
|
1589
|
+
function getSidecarValidator() {
|
|
1590
|
+
if (cachedSidecarValidator) return cachedSidecarValidator;
|
|
1591
|
+
const ajv = new Ajv20204({ strict: false, allErrors: true, allowUnionTypes: true });
|
|
1592
|
+
applyAjvFormats(ajv);
|
|
1593
|
+
const specRoot = resolveSpecRoot2();
|
|
1594
|
+
const annotationsSchema = JSON.parse(
|
|
1595
|
+
readFileSync5(resolve6(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
1596
|
+
);
|
|
1597
|
+
const sidecarSchema = JSON.parse(
|
|
1598
|
+
readFileSync5(resolve6(specRoot, "schemas/sidecar.schema.json"), "utf8")
|
|
1599
|
+
);
|
|
1600
|
+
ajv.addSchema(annotationsSchema);
|
|
1601
|
+
cachedSidecarValidator = ajv.compile(sidecarSchema);
|
|
1602
|
+
return cachedSidecarValidator;
|
|
1603
|
+
}
|
|
1604
|
+
function resolveSpecRoot2() {
|
|
1605
|
+
const require2 = createRequire3(import.meta.url);
|
|
1606
|
+
try {
|
|
1607
|
+
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
1608
|
+
return dirname3(indexPath);
|
|
1609
|
+
} catch {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
"@skill-map/spec not resolvable: sidecar reader cannot load schemas."
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// kernel/sidecar/drift.ts
|
|
1617
|
+
function computeDriftStatus(args) {
|
|
1618
|
+
const bodyDrift = args.storedBodyHash !== args.liveBodyHash;
|
|
1619
|
+
const fmDrift = args.storedFrontmatterHash !== args.liveFrontmatterHash;
|
|
1620
|
+
if (bodyDrift && fmDrift) return "stale-both";
|
|
1621
|
+
if (bodyDrift) return "stale-body";
|
|
1622
|
+
if (fmDrift) return "stale-frontmatter";
|
|
1623
|
+
return "fresh";
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// kernel/sidecar/discover-orphans.ts
|
|
1627
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
|
|
1628
|
+
import { join as join3, relative as relative3, sep as sep2 } from "path";
|
|
1629
|
+
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
1630
|
+
const out = [];
|
|
1631
|
+
for (const root of roots) {
|
|
1632
|
+
walk(root, root, shouldSkip ?? (() => false), out);
|
|
1633
|
+
}
|
|
1634
|
+
return out;
|
|
1635
|
+
}
|
|
1636
|
+
function walk(root, current, shouldSkip, out) {
|
|
1637
|
+
let entries;
|
|
1638
|
+
try {
|
|
1639
|
+
entries = readdirSync2(current, { withFileTypes: true, encoding: "utf8" });
|
|
1640
|
+
} catch {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
for (const entry of entries) {
|
|
1644
|
+
const full = join3(current, entry.name);
|
|
1645
|
+
const rel = relative3(root, full).split(sep2).join("/");
|
|
1646
|
+
if (shouldSkip(rel)) continue;
|
|
1647
|
+
if (entry.isSymbolicLink()) continue;
|
|
1648
|
+
if (entry.isDirectory()) {
|
|
1649
|
+
walk(root, full, shouldSkip, out);
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
if (!entry.isFile()) continue;
|
|
1653
|
+
if (!entry.name.endsWith(".sm")) continue;
|
|
1654
|
+
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
1655
|
+
if (existsSync4(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
1656
|
+
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
function safeIsFile(path) {
|
|
1660
|
+
try {
|
|
1661
|
+
return statSync(path).isFile();
|
|
1662
|
+
} catch {
|
|
1663
|
+
return false;
|
|
1764
1664
|
}
|
|
1765
|
-
return candidatesByNew;
|
|
1766
1665
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1666
|
+
|
|
1667
|
+
// kernel/sidecar/store.ts
|
|
1668
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
1669
|
+
import { dirname as dirname5, resolve as resolve9 } from "path";
|
|
1670
|
+
import { createRequire as createRequire4 } from "module";
|
|
1671
|
+
import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
|
|
1672
|
+
import yaml3 from "js-yaml";
|
|
1673
|
+
|
|
1674
|
+
// core/config/atomic-write.ts
|
|
1675
|
+
import {
|
|
1676
|
+
closeSync,
|
|
1677
|
+
constants as fsConstants,
|
|
1678
|
+
existsSync as existsSync5,
|
|
1679
|
+
mkdirSync,
|
|
1680
|
+
openSync,
|
|
1681
|
+
readFileSync as readFileSync6,
|
|
1682
|
+
renameSync,
|
|
1683
|
+
unlinkSync,
|
|
1684
|
+
writeSync
|
|
1685
|
+
} from "fs";
|
|
1686
|
+
import { randomBytes } from "crypto";
|
|
1687
|
+
import { dirname as dirname4 } from "path";
|
|
1688
|
+
|
|
1689
|
+
// core/config/helper.ts
|
|
1690
|
+
import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
|
|
1691
|
+
|
|
1692
|
+
// kernel/config/loader.ts
|
|
1693
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
1694
|
+
|
|
1695
|
+
// kernel/util/skill-map-paths.ts
|
|
1696
|
+
import { join as join5 } from "path";
|
|
1697
|
+
|
|
1698
|
+
// core/paths/db-path.ts
|
|
1699
|
+
import { join as join4, resolve as resolve7 } from "path";
|
|
1700
|
+
var SKILL_MAP_DIR = ".skill-map";
|
|
1701
|
+
var DB_FILENAME = "skill-map.db";
|
|
1702
|
+
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
1703
|
+
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
1704
|
+
var GITIGNORE_ENTRIES = [
|
|
1705
|
+
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
1706
|
+
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
1707
|
+
];
|
|
1708
|
+
|
|
1709
|
+
// kernel/orchestrator/node-build.ts
|
|
1710
|
+
import { createHash } from "crypto";
|
|
1711
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1712
|
+
import { isAbsolute as isAbsolute3, resolve as resolvePath } from "path";
|
|
1713
|
+
import "js-tiktoken/lite";
|
|
1714
|
+
import yaml4 from "js-yaml";
|
|
1715
|
+
|
|
1716
|
+
// kernel/orchestrator/frontmatter.ts
|
|
1717
|
+
function validateFrontmatter(providerFrontmatter, provider, kind, frontmatter, path, strict) {
|
|
1718
|
+
const result = providerFrontmatter.validate(provider, kind, frontmatter);
|
|
1719
|
+
if (result.ok) return null;
|
|
1720
|
+
return {
|
|
1721
|
+
analyzerId: "frontmatter-invalid",
|
|
1722
|
+
severity: strict ? "error" : "warn",
|
|
1723
|
+
nodeIds: [path],
|
|
1724
|
+
message: tx(ORCHESTRATOR_TEXTS.frontmatterInvalid, { path, kind, errors: result.errors }),
|
|
1725
|
+
data: { kind, errors: result.errors }
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
function detectMalformedFrontmatter(body, path, strict) {
|
|
1729
|
+
const hint = classifyMalformedFrontmatter(body);
|
|
1730
|
+
if (!hint) return null;
|
|
1731
|
+
return {
|
|
1732
|
+
analyzerId: "frontmatter-malformed",
|
|
1733
|
+
severity: strict ? "error" : "warn",
|
|
1734
|
+
nodeIds: [path],
|
|
1735
|
+
message: malformedMessage(hint, path),
|
|
1736
|
+
data: { hint }
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function classifyMalformedFrontmatter(body) {
|
|
1740
|
+
if (body.startsWith("\uFEFF")) {
|
|
1741
|
+
if (/^---\r?\n[\s\S]*?[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1742
|
+
return "byte-order-mark";
|
|
1786
1743
|
}
|
|
1787
1744
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1796
|
-
if (remaining.length > 1) {
|
|
1797
|
-
opts.issues.push({
|
|
1798
|
-
analyzerId: "auto-rename-ambiguous",
|
|
1799
|
-
severity: "warn",
|
|
1800
|
-
nodeIds: [toPath],
|
|
1801
|
-
message: `Auto-rename ambiguous: ${toPath} matches ${remaining.length} prior frontmatters \u2014 pick one with \`sm orphans undo-rename ${toPath} --from <old.path>\`.`,
|
|
1802
|
-
data: { to: toPath, candidates: remaining }
|
|
1803
|
-
});
|
|
1745
|
+
if (/^[ \t]+---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1746
|
+
return "paste-with-indent";
|
|
1747
|
+
}
|
|
1748
|
+
if (/^---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1749
|
+
const hasCloseFence = /\r?\n---(?:\r?\n|$)/.test(body);
|
|
1750
|
+
if (!hasCloseFence) {
|
|
1751
|
+
return "missing-close";
|
|
1804
1752
|
}
|
|
1805
1753
|
}
|
|
1754
|
+
return null;
|
|
1806
1755
|
}
|
|
1807
|
-
function
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
data: { path: fromPath }
|
|
1816
|
-
});
|
|
1756
|
+
function malformedMessage(hint, path) {
|
|
1757
|
+
switch (hint) {
|
|
1758
|
+
case "paste-with-indent":
|
|
1759
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedPasteWithIndent, { path });
|
|
1760
|
+
case "byte-order-mark":
|
|
1761
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedByteOrderMark, { path });
|
|
1762
|
+
case "missing-close":
|
|
1763
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
|
|
1817
1764
|
}
|
|
1818
1765
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
for (const n of prior.nodes) priorByPath.set(n.path, n);
|
|
1822
|
-
const currentByPath = /* @__PURE__ */ new Map();
|
|
1823
|
-
for (const n of current) currentByPath.set(n.path, n);
|
|
1824
|
-
const deletedPaths = [...priorByPath.keys()].filter((p) => !currentByPath.has(p)).sort();
|
|
1825
|
-
const newPaths = [...currentByPath.keys()].filter((p) => !priorByPath.has(p)).sort();
|
|
1826
|
-
const claimedDeleted = /* @__PURE__ */ new Set();
|
|
1827
|
-
const claimedNew = /* @__PURE__ */ new Set();
|
|
1828
|
-
const ops = [];
|
|
1829
|
-
ops.push(...findHighConfidenceRenames({
|
|
1830
|
-
deletedPaths,
|
|
1831
|
-
newPaths,
|
|
1832
|
-
priorByPath,
|
|
1833
|
-
currentByPath,
|
|
1834
|
-
claimedDeleted,
|
|
1835
|
-
claimedNew
|
|
1836
|
-
}));
|
|
1837
|
-
const candidatesByNew = buildFrontmatterRenameCandidates({
|
|
1838
|
-
deletedPaths,
|
|
1839
|
-
newPaths,
|
|
1840
|
-
priorByPath,
|
|
1841
|
-
currentByPath,
|
|
1842
|
-
claimedDeleted,
|
|
1843
|
-
claimedNew
|
|
1844
|
-
});
|
|
1845
|
-
ops.push(...claimSingletonRenames({
|
|
1846
|
-
newPaths,
|
|
1847
|
-
candidatesByNew,
|
|
1848
|
-
claimedDeleted,
|
|
1849
|
-
claimedNew,
|
|
1850
|
-
issues
|
|
1851
|
-
}));
|
|
1852
|
-
flagAmbiguousRenames({ newPaths, candidatesByNew, claimedDeleted, claimedNew, issues });
|
|
1853
|
-
flagOrphans({ deletedPaths, claimedDeleted, issues });
|
|
1854
|
-
return ops;
|
|
1855
|
-
}
|
|
1856
|
-
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
1857
|
-
function isExternalUrlLink(link) {
|
|
1858
|
-
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
1859
|
-
}
|
|
1766
|
+
|
|
1767
|
+
// kernel/orchestrator/node-build.ts
|
|
1860
1768
|
function buildNode(args) {
|
|
1861
1769
|
const bytesFrontmatter = Buffer.byteLength(args.frontmatterRaw, "utf8");
|
|
1862
1770
|
const bytesBody = Buffer.byteLength(args.body, "utf8");
|
|
@@ -1946,11 +1854,11 @@ function resolveSidecarOverlay(relativePath, nodePathForIssue, roots, liveBodyHa
|
|
|
1946
1854
|
liveFrontmatterHash
|
|
1947
1855
|
});
|
|
1948
1856
|
return {
|
|
1949
|
-
// R15 closure (2026-05-07)
|
|
1857
|
+
// R15 closure (2026-05-07), surface the full parsed root on the
|
|
1950
1858
|
// overlay so BFF consumers (UI inspector audit / plugin-contributions
|
|
1951
1859
|
// / debug panels) can read `for.*`, `audit.*`, `settings.*`, and
|
|
1952
1860
|
// plugin-namespaced sub-keys without re-reading the file. The
|
|
1953
|
-
// `annotations` field above stays
|
|
1861
|
+
// `annotations` field above stays, it duplicates `root.annotations`
|
|
1954
1862
|
// by design so existing consumers keep working unchanged.
|
|
1955
1863
|
overlay: {
|
|
1956
1864
|
present: true,
|
|
@@ -1981,157 +1889,460 @@ function relativePathFromRoots(absolutePath, roots) {
|
|
|
1981
1889
|
}
|
|
1982
1890
|
return absolutePath;
|
|
1983
1891
|
}
|
|
1984
|
-
function
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
if (
|
|
1998
|
-
const
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1892
|
+
function buildFreshNodeAndValidateFrontmatter(opts) {
|
|
1893
|
+
const node = buildNode({
|
|
1894
|
+
path: opts.raw.path,
|
|
1895
|
+
kind: opts.kind,
|
|
1896
|
+
providerId: opts.provider.id,
|
|
1897
|
+
frontmatterRaw: opts.raw.frontmatterRaw,
|
|
1898
|
+
body: opts.raw.body,
|
|
1899
|
+
frontmatter: opts.raw.frontmatter,
|
|
1900
|
+
bodyHash: opts.bodyHash,
|
|
1901
|
+
frontmatterHash: opts.frontmatterHash,
|
|
1902
|
+
encoder: opts.encoder
|
|
1903
|
+
});
|
|
1904
|
+
const frontmatterIssues = [];
|
|
1905
|
+
if (opts.raw.parseIssues && opts.raw.parseIssues.length > 0) {
|
|
1906
|
+
for (const pi of opts.raw.parseIssues) {
|
|
1907
|
+
frontmatterIssues.push({
|
|
1908
|
+
analyzerId: pi.code,
|
|
1909
|
+
severity: opts.strict ? "error" : "warn",
|
|
1910
|
+
nodeIds: [opts.raw.path],
|
|
1911
|
+
message: pi.message
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (opts.raw.frontmatterRaw.length > 0) {
|
|
1916
|
+
const fmIssue = validateFrontmatter(
|
|
1917
|
+
opts.providerFrontmatter,
|
|
1918
|
+
opts.provider,
|
|
1919
|
+
opts.kind,
|
|
1920
|
+
opts.raw.frontmatter,
|
|
1921
|
+
opts.raw.path,
|
|
1922
|
+
opts.strict
|
|
2012
1923
|
);
|
|
2013
|
-
|
|
1924
|
+
if (fmIssue) frontmatterIssues.push(fmIssue);
|
|
1925
|
+
} else {
|
|
1926
|
+
const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
|
|
1927
|
+
if (malformed) frontmatterIssues.push(malformed);
|
|
1928
|
+
}
|
|
1929
|
+
return { node, frontmatterIssues };
|
|
1930
|
+
}
|
|
1931
|
+
function mergeNodeWithEnrichments(node, enrichments, opts = {}) {
|
|
1932
|
+
const includeStale = opts.includeStale === true;
|
|
1933
|
+
const applicable = enrichments.filter((e) => e.nodePath === node.path).filter((e) => includeStale || !e.stale).sort((a, b) => a.enrichedAt - b.enrichedAt);
|
|
1934
|
+
const base = {};
|
|
1935
|
+
assignSafe(base, node.frontmatter ?? {});
|
|
1936
|
+
for (const row of applicable) {
|
|
1937
|
+
assignSafe(base, row.value);
|
|
2014
1938
|
}
|
|
2015
|
-
|
|
2016
|
-
return { ...link, confidence };
|
|
1939
|
+
return base;
|
|
2017
1940
|
}
|
|
2018
|
-
function
|
|
2019
|
-
const
|
|
2020
|
-
|
|
1941
|
+
function assignSafe(target, source) {
|
|
1942
|
+
const safe = stripPrototypePollution(source);
|
|
1943
|
+
for (const [k, v] of Object.entries(safe)) {
|
|
1944
|
+
target[k] = v;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// kernel/orchestrator/walk.ts
|
|
1949
|
+
async function walkAndExtract(opts) {
|
|
1950
|
+
const accum = createWalkAccumulators();
|
|
1951
|
+
const wctx = buildWalkContext(opts);
|
|
1952
|
+
const claimedPaths = /* @__PURE__ */ new Set();
|
|
1953
|
+
const walkOptions = opts.ignoreFilter ? { ignoreFilter: opts.ignoreFilter } : {};
|
|
1954
|
+
let filesWalked = 0;
|
|
1955
|
+
let index = 0;
|
|
1956
|
+
for (const provider of opts.providers) {
|
|
1957
|
+
for await (const raw of resolveProviderWalk(provider)(opts.roots, walkOptions)) {
|
|
1958
|
+
filesWalked += 1;
|
|
1959
|
+
if (claimedPaths.has(raw.path)) continue;
|
|
1960
|
+
const advanced = await processRawNode(raw, provider, wctx, accum, claimedPaths, index + 1);
|
|
1961
|
+
if (advanced) index += 1;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
const orphanSidecars = discoverOrphanSidecars(opts.roots);
|
|
2021
1965
|
return {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1966
|
+
nodes: accum.nodes,
|
|
1967
|
+
internalLinks: accum.internalLinks,
|
|
1968
|
+
externalLinks: accum.externalLinks,
|
|
1969
|
+
cachedPaths: accum.cachedPaths,
|
|
1970
|
+
frontmatterIssues: accum.frontmatterIssues,
|
|
1971
|
+
filesWalked,
|
|
1972
|
+
enrichments: [...accum.enrichmentBuffer.values()],
|
|
1973
|
+
extractorRuns: accum.extractorRuns,
|
|
1974
|
+
contributions: accum.contributionsBuffer,
|
|
1975
|
+
freshlyRunTuples: accum.freshlyRunTuples,
|
|
1976
|
+
orphanSidecars,
|
|
1977
|
+
sidecarRoots: accum.sidecarRoots
|
|
2027
1978
|
};
|
|
2028
1979
|
}
|
|
2029
|
-
function
|
|
2030
|
-
const hint = classifyMalformedFrontmatter(body);
|
|
2031
|
-
if (!hint) return null;
|
|
1980
|
+
function createWalkAccumulators() {
|
|
2032
1981
|
return {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
1982
|
+
nodes: [],
|
|
1983
|
+
internalLinks: [],
|
|
1984
|
+
externalLinks: [],
|
|
1985
|
+
cachedPaths: /* @__PURE__ */ new Set(),
|
|
1986
|
+
frontmatterIssues: [],
|
|
1987
|
+
enrichmentBuffer: /* @__PURE__ */ new Map(),
|
|
1988
|
+
contributionsBuffer: [],
|
|
1989
|
+
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
1990
|
+
extractorRuns: [],
|
|
1991
|
+
sidecarRoots: /* @__PURE__ */ new Map()
|
|
2038
1992
|
};
|
|
2039
1993
|
}
|
|
2040
|
-
function
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
1994
|
+
function buildWalkContext(opts) {
|
|
1995
|
+
const { priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode } = opts.priorIndex;
|
|
1996
|
+
const shortIdToQualified = /* @__PURE__ */ new Map();
|
|
1997
|
+
for (const ex of opts.extractors) {
|
|
1998
|
+
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1999
|
+
const list = shortIdToQualified.get(ex.id);
|
|
2000
|
+
if (list) list.push(qualified);
|
|
2001
|
+
else shortIdToQualified.set(ex.id, [qualified]);
|
|
2045
2002
|
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2003
|
+
return { opts, priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode, shortIdToQualified };
|
|
2004
|
+
}
|
|
2005
|
+
async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextIndex) {
|
|
2006
|
+
const bodyHash = sha256(raw.body);
|
|
2007
|
+
const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
|
|
2008
|
+
const kind = provider.classify(raw.path, raw.frontmatter);
|
|
2009
|
+
if (kind === null) {
|
|
2010
|
+
return false;
|
|
2048
2011
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2012
|
+
claimedPaths.add(raw.path);
|
|
2013
|
+
const priorNode = wctx.priorNodesByPath.get(raw.path);
|
|
2014
|
+
const nodeHashCacheEligible = wctx.opts.enableCache && wctx.opts.prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
|
|
2015
|
+
const sidecarResolution = resolveSidecarOverlay(
|
|
2016
|
+
raw.path,
|
|
2017
|
+
raw.path,
|
|
2018
|
+
wctx.opts.roots,
|
|
2019
|
+
bodyHash,
|
|
2020
|
+
frontmatterHash
|
|
2021
|
+
);
|
|
2022
|
+
const sidecarAnnotationsHash = sha256(
|
|
2023
|
+
canonicalSidecarAnnotations(sidecarResolution.overlay.annotations)
|
|
2024
|
+
);
|
|
2025
|
+
const cacheDecision = computeCacheDecision({
|
|
2026
|
+
extractors: wctx.opts.extractors,
|
|
2027
|
+
kind,
|
|
2028
|
+
nodePath: raw.path,
|
|
2029
|
+
bodyHash,
|
|
2030
|
+
sidecarAnnotationsHash,
|
|
2031
|
+
nodeHashCacheEligible,
|
|
2032
|
+
priorExtractorRuns: wctx.opts.priorExtractorRuns
|
|
2033
|
+
});
|
|
2034
|
+
const ctx = {
|
|
2035
|
+
raw,
|
|
2036
|
+
provider,
|
|
2037
|
+
kind,
|
|
2038
|
+
bodyHash,
|
|
2039
|
+
frontmatterHash,
|
|
2040
|
+
sidecarResolution,
|
|
2041
|
+
sidecarAnnotationsHash,
|
|
2042
|
+
nodeHashCacheEligible,
|
|
2043
|
+
cacheDecision,
|
|
2044
|
+
priorNode,
|
|
2045
|
+
index: nextIndex
|
|
2046
|
+
};
|
|
2047
|
+
if (cacheDecision.fullCacheHit && priorNode) {
|
|
2048
|
+
applyFullCacheHit(ctx, wctx, accum);
|
|
2049
|
+
} else {
|
|
2050
|
+
await applyExtractPath(ctx, wctx, accum);
|
|
2054
2051
|
}
|
|
2055
|
-
return
|
|
2052
|
+
return true;
|
|
2056
2053
|
}
|
|
2057
|
-
function
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
case "byte-order-mark":
|
|
2062
|
-
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedByteOrderMark, { path });
|
|
2063
|
-
case "missing-close":
|
|
2064
|
-
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
|
|
2054
|
+
function attachSidecar(node, resolution, sidecarRoots) {
|
|
2055
|
+
node.sidecar = resolution.overlay;
|
|
2056
|
+
if (resolution.parsedRoot !== null) {
|
|
2057
|
+
sidecarRoots.set(node.path, resolution.parsedRoot);
|
|
2065
2058
|
}
|
|
2059
|
+
return resolution.issues.map(
|
|
2060
|
+
(i) => i.nodeIds.length > 0 ? i : { ...i, nodeIds: [node.path] }
|
|
2061
|
+
);
|
|
2066
2062
|
}
|
|
2067
|
-
function
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2063
|
+
function applyFullCacheHit(ctx, wctx, accum) {
|
|
2064
|
+
const reused = reusePriorNode({
|
|
2065
|
+
priorNode: ctx.priorNode,
|
|
2066
|
+
bodyHash: ctx.bodyHash,
|
|
2067
|
+
sidecarAnnotationsHash: ctx.sidecarAnnotationsHash,
|
|
2068
|
+
strict: wctx.opts.strict,
|
|
2069
|
+
cachedQualifiedIds: ctx.cacheDecision.cachedQualifiedIds,
|
|
2070
|
+
applicableQualifiedIds: ctx.cacheDecision.applicableQualifiedIds,
|
|
2071
|
+
shortIdToQualified: wctx.shortIdToQualified,
|
|
2072
|
+
priorLinksByOriginating: wctx.priorLinksByOriginating,
|
|
2073
|
+
priorFrontmatterIssuesByNode: wctx.priorFrontmatterIssuesByNode
|
|
2074
|
+
});
|
|
2075
|
+
const reusedSidecarIssues = attachSidecar(reused.node, ctx.sidecarResolution, accum.sidecarRoots);
|
|
2076
|
+
accum.nodes.push(reused.node);
|
|
2077
|
+
accum.cachedPaths.add(reused.node.path);
|
|
2078
|
+
for (const link of reused.internalLinks) accum.internalLinks.push(link);
|
|
2079
|
+
for (const issue of reused.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
2080
|
+
for (const issue of reusedSidecarIssues) accum.frontmatterIssues.push(issue);
|
|
2081
|
+
for (const run of reused.extractorRuns) accum.extractorRuns.push(run);
|
|
2082
|
+
wctx.opts.emitter.emit(makeEvent("scan.progress", {
|
|
2083
|
+
index: ctx.index,
|
|
2084
|
+
path: ctx.raw.path,
|
|
2085
|
+
kind: ctx.kind,
|
|
2086
|
+
cached: true
|
|
2087
|
+
}));
|
|
2086
2088
|
}
|
|
2087
|
-
function
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2089
|
+
async function applyExtractPath(ctx, wctx, accum) {
|
|
2090
|
+
const node = buildOrReuseNode(ctx, wctx, accum);
|
|
2091
|
+
const sidecarIssues = attachSidecar(node, ctx.sidecarResolution, accum.sidecarRoots);
|
|
2092
|
+
for (const issue of sidecarIssues) accum.frontmatterIssues.push(issue);
|
|
2093
|
+
const partialCacheHit = isPartialCacheHit(ctx);
|
|
2094
|
+
emitExtractProgress(ctx, wctx, partialCacheHit);
|
|
2095
|
+
const extractorsToRun = partialCacheHit ? ctx.cacheDecision.missingExtractors : ctx.cacheDecision.applicableExtractors;
|
|
2096
|
+
recordFreshlyRunTuples(extractorsToRun, node.path, accum);
|
|
2097
|
+
const extractResult = await runExtractorsForNode({
|
|
2098
|
+
extractors: extractorsToRun,
|
|
2099
|
+
node,
|
|
2100
|
+
body: ctx.raw.body,
|
|
2101
|
+
frontmatter: ctx.raw.frontmatter,
|
|
2102
|
+
bodyHash: ctx.bodyHash,
|
|
2103
|
+
emitter: wctx.opts.emitter,
|
|
2104
|
+
...wctx.opts.pluginStores ? { pluginStores: wctx.opts.pluginStores } : {}
|
|
2105
|
+
});
|
|
2106
|
+
mergeExtractResult(extractResult, accum);
|
|
2107
|
+
recordExtractorRuns(node.path, ctx, accum);
|
|
2108
|
+
}
|
|
2109
|
+
function emitExtractProgress(ctx, wctx, partialCacheHit) {
|
|
2110
|
+
wctx.opts.emitter.emit(makeEvent("scan.progress", {
|
|
2111
|
+
index: ctx.index,
|
|
2112
|
+
path: ctx.raw.path,
|
|
2113
|
+
kind: ctx.kind,
|
|
2114
|
+
cached: false,
|
|
2115
|
+
...partialCacheHit ? { partialCache: true } : {}
|
|
2116
|
+
}));
|
|
2117
|
+
}
|
|
2118
|
+
function recordFreshlyRunTuples(extractors, nodePath, accum) {
|
|
2119
|
+
for (const ex of extractors) {
|
|
2120
|
+
accum.freshlyRunTuples.add(`${ex.pluginId}\0${ex.id}\0${nodePath}`);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
function mergeExtractResult(extractResult, accum) {
|
|
2124
|
+
for (const link of extractResult.internalLinks) accum.internalLinks.push(link);
|
|
2125
|
+
for (const link of extractResult.externalLinks) accum.externalLinks.push(link);
|
|
2126
|
+
for (const enr of extractResult.enrichments) {
|
|
2127
|
+
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
2128
|
+
}
|
|
2129
|
+
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
2130
|
+
}
|
|
2131
|
+
function isPartialCacheHit(ctx) {
|
|
2132
|
+
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|
|
2133
|
+
}
|
|
2134
|
+
function buildOrReuseNode(ctx, wctx, accum) {
|
|
2135
|
+
if (isPartialCacheHit(ctx) && ctx.priorNode) {
|
|
2136
|
+
const partial = cloneNodeAndReshapeLinks({
|
|
2137
|
+
priorNode: ctx.priorNode,
|
|
2138
|
+
strict: wctx.opts.strict,
|
|
2139
|
+
cachedQualifiedIds: ctx.cacheDecision.cachedQualifiedIds,
|
|
2140
|
+
applicableQualifiedIds: ctx.cacheDecision.applicableQualifiedIds,
|
|
2141
|
+
shortIdToQualified: wctx.shortIdToQualified,
|
|
2142
|
+
priorLinksByOriginating: wctx.priorLinksByOriginating,
|
|
2143
|
+
priorFrontmatterIssuesByNode: wctx.priorFrontmatterIssuesByNode
|
|
2144
|
+
});
|
|
2145
|
+
for (const link of partial.internalLinks) accum.internalLinks.push(link);
|
|
2146
|
+
for (const issue of partial.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
2147
|
+
accum.nodes.push(partial.node);
|
|
2148
|
+
return partial.node;
|
|
2149
|
+
}
|
|
2150
|
+
const fresh = buildFreshNodeAndValidateFrontmatter({
|
|
2151
|
+
raw: ctx.raw,
|
|
2152
|
+
kind: ctx.kind,
|
|
2153
|
+
provider: ctx.provider,
|
|
2154
|
+
bodyHash: ctx.bodyHash,
|
|
2155
|
+
frontmatterHash: ctx.frontmatterHash,
|
|
2156
|
+
encoder: wctx.opts.encoder,
|
|
2157
|
+
providerFrontmatter: wctx.opts.providerFrontmatter,
|
|
2158
|
+
strict: wctx.opts.strict
|
|
2159
|
+
});
|
|
2160
|
+
accum.nodes.push(fresh.node);
|
|
2161
|
+
for (const issue of fresh.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
2162
|
+
return fresh.node;
|
|
2163
|
+
}
|
|
2164
|
+
function recordExtractorRuns(nodePath, ctx, accum) {
|
|
2165
|
+
const ranAt = Date.now();
|
|
2166
|
+
for (const ex of ctx.cacheDecision.applicableExtractors) {
|
|
2167
|
+
accum.extractorRuns.push({
|
|
2168
|
+
nodePath,
|
|
2169
|
+
extractorId: qualifiedExtensionId(ex.pluginId, ex.id),
|
|
2170
|
+
bodyHashAtRun: ctx.bodyHash,
|
|
2171
|
+
ranAt,
|
|
2172
|
+
sidecarAnnotationsHashAtRun: ctx.sidecarAnnotationsHash
|
|
2173
|
+
});
|
|
2099
2174
|
}
|
|
2100
2175
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2176
|
+
|
|
2177
|
+
// kernel/orchestrator/index.ts
|
|
2178
|
+
var SCANNED_BY = {
|
|
2179
|
+
name: "skill-map",
|
|
2180
|
+
version: package_default.version,
|
|
2181
|
+
specVersion: resolveSpecVersionSafe()
|
|
2182
|
+
};
|
|
2183
|
+
function resolveSpecVersionSafe() {
|
|
2184
|
+
try {
|
|
2185
|
+
return installedSpecVersion();
|
|
2186
|
+
} catch {
|
|
2187
|
+
return "unknown";
|
|
2106
2188
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2189
|
+
}
|
|
2190
|
+
async function runScanWithRenames(_kernel, options) {
|
|
2191
|
+
return runScanInternal(_kernel, options);
|
|
2192
|
+
}
|
|
2193
|
+
async function runScan(_kernel, options) {
|
|
2194
|
+
const { result } = await runScanInternal(_kernel, options);
|
|
2195
|
+
return result;
|
|
2196
|
+
}
|
|
2197
|
+
async function runScanInternal(_kernel, options) {
|
|
2198
|
+
validateRoots(options.roots);
|
|
2199
|
+
const setup = buildScanSetup(options);
|
|
2200
|
+
const { emitter, exts, hookDispatcher, encoder, prior, start } = setup;
|
|
2201
|
+
const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
|
|
2202
|
+
emitter.emit(scanStartedEvent);
|
|
2203
|
+
await hookDispatcher.dispatch("scan.started", scanStartedEvent);
|
|
2204
|
+
const walked = await walkAndExtract({
|
|
2205
|
+
providers: exts.providers,
|
|
2206
|
+
extractors: exts.extractors,
|
|
2207
|
+
roots: options.roots,
|
|
2208
|
+
...options.ignoreFilter ? { ignoreFilter: options.ignoreFilter } : {},
|
|
2209
|
+
emitter,
|
|
2210
|
+
encoder,
|
|
2211
|
+
strict: setup.strict,
|
|
2212
|
+
enableCache: setup.enableCache,
|
|
2213
|
+
prior,
|
|
2214
|
+
priorIndex: setup.priorIndex,
|
|
2215
|
+
priorExtractorRuns: setup.priorExtractorRuns,
|
|
2216
|
+
providerFrontmatter: setup.providerFrontmatter,
|
|
2217
|
+
pluginStores: options.pluginStores
|
|
2218
|
+
});
|
|
2219
|
+
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
2220
|
+
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
2221
|
+
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
2222
|
+
const registeredActionIds = new Set(
|
|
2223
|
+
_kernel.registry.all("action").map((a) => qualifiedExtensionId(a.pluginId, a.id))
|
|
2224
|
+
);
|
|
2225
|
+
const analyzerResult = await runAnalyzers(
|
|
2226
|
+
exts.analyzers,
|
|
2227
|
+
walked.nodes,
|
|
2228
|
+
walked.internalLinks,
|
|
2229
|
+
walked.orphanSidecars,
|
|
2230
|
+
walked.sidecarRoots,
|
|
2231
|
+
options.annotationContributions ?? [],
|
|
2232
|
+
options.viewContributions ?? [],
|
|
2233
|
+
options.orphanJobFiles ?? [],
|
|
2234
|
+
options.referenceablePaths,
|
|
2235
|
+
options.cwd,
|
|
2236
|
+
registeredActionIds,
|
|
2237
|
+
emitter,
|
|
2238
|
+
hookDispatcher
|
|
2239
|
+
);
|
|
2240
|
+
mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
|
|
2241
|
+
const issues = analyzerResult.issues;
|
|
2242
|
+
for (const issue of walked.frontmatterIssues) issues.push(issue);
|
|
2243
|
+
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
|
|
2244
|
+
const stats = buildScanStats(walked, issues, start);
|
|
2245
|
+
const scanCompletedEvent = makeEvent("scan.completed", { stats });
|
|
2246
|
+
emitter.emit(scanCompletedEvent);
|
|
2247
|
+
await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
|
|
2248
|
+
return buildScanReturn(walked, issues, renameOps, stats, options, setup);
|
|
2249
|
+
}
|
|
2250
|
+
function buildScanSetup(options) {
|
|
2251
|
+
const start = Date.now();
|
|
2252
|
+
const emitter = options.emitter ?? new InMemoryProgressEmitter();
|
|
2253
|
+
const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
|
|
2254
|
+
const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
|
|
2255
|
+
const tokenize = options.tokenize !== false;
|
|
2256
|
+
const encoder = tokenize ? new Tiktoken2(cl100k_base) : null;
|
|
2257
|
+
const prior = options.priorSnapshot ?? null;
|
|
2258
|
+
const priorIndex = indexPriorSnapshot(prior);
|
|
2259
|
+
const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
|
|
2260
|
+
return {
|
|
2261
|
+
start,
|
|
2262
|
+
scannedAt: start,
|
|
2263
|
+
emitter,
|
|
2264
|
+
exts,
|
|
2265
|
+
hookDispatcher,
|
|
2266
|
+
encoder,
|
|
2267
|
+
prior,
|
|
2268
|
+
priorIndex,
|
|
2269
|
+
priorExtractorRuns: options.priorExtractorRuns,
|
|
2270
|
+
providerFrontmatter,
|
|
2271
|
+
scope: options.scope ?? "project",
|
|
2272
|
+
strict: options.strict === true,
|
|
2273
|
+
enableCache: options.enableCache === true
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
async function dispatchExtractorCompleted(extractors, emitter, hookDispatcher) {
|
|
2277
|
+
for (const extractor of extractors) {
|
|
2278
|
+
const extractorId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
2279
|
+
const evt = makeEvent("extractor.completed", { extractorId });
|
|
2280
|
+
emitter.emit(evt);
|
|
2281
|
+
await hookDispatcher.dispatch("extractor.completed", evt);
|
|
2110
2282
|
}
|
|
2111
2283
|
}
|
|
2112
|
-
function
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2284
|
+
function mergeAnalyzerEmissions(walked, analyzerResult, analyzers) {
|
|
2285
|
+
for (const c of analyzerResult.contributions) walked.contributions.push(c);
|
|
2286
|
+
for (const analyzer of analyzers ?? []) {
|
|
2287
|
+
if (analyzer.viewContributions === void 0) continue;
|
|
2288
|
+
for (const node of walked.nodes) {
|
|
2289
|
+
walked.freshlyRunTuples.add(`${analyzer.pluginId}\0${analyzer.id}\0${node.path}`);
|
|
2290
|
+
}
|
|
2119
2291
|
}
|
|
2120
|
-
return base;
|
|
2121
2292
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2293
|
+
function buildScanStats(walked, issues, start) {
|
|
2294
|
+
return {
|
|
2295
|
+
// `filesSkipped` is "files walked but not classified by any
|
|
2296
|
+
// Provider". Today every walked file IS classified by its Provider
|
|
2297
|
+
// (the `claude` Provider's `classify()` always returns a kind,
|
|
2298
|
+
// falling back to `'markdown'`), so this is always 0. Wired now
|
|
2299
|
+
// so the field shape is spec-conformant; meaningful once multiple
|
|
2300
|
+
// Providers compete.
|
|
2301
|
+
filesWalked: walked.filesWalked,
|
|
2302
|
+
filesSkipped: 0,
|
|
2303
|
+
nodesCount: walked.nodes.length,
|
|
2304
|
+
linksCount: walked.internalLinks.length,
|
|
2305
|
+
issuesCount: issues.length,
|
|
2306
|
+
durationMs: Date.now() - start
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
2310
|
+
return {
|
|
2311
|
+
result: {
|
|
2312
|
+
schemaVersion: 1,
|
|
2313
|
+
scannedAt: setup.scannedAt,
|
|
2314
|
+
scope: setup.scope,
|
|
2315
|
+
roots: options.roots,
|
|
2316
|
+
providers: setup.exts.providers.map((a) => a.id),
|
|
2317
|
+
scannedBy: SCANNED_BY,
|
|
2318
|
+
nodes: walked.nodes,
|
|
2319
|
+
links: walked.internalLinks,
|
|
2320
|
+
issues,
|
|
2321
|
+
stats
|
|
2322
|
+
},
|
|
2323
|
+
renameOps,
|
|
2324
|
+
extractorRuns: walked.extractorRuns,
|
|
2325
|
+
enrichments: walked.enrichments,
|
|
2326
|
+
contributions: walked.contributions,
|
|
2327
|
+
freshlyRunTuples: walked.freshlyRunTuples
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
function validateRoots(roots) {
|
|
2331
|
+
if (roots.length === 0) {
|
|
2332
|
+
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
2333
|
+
}
|
|
2334
|
+
for (const root of roots) {
|
|
2335
|
+
if (!existsSync9(root) || !statSync2(root).isDirectory()) {
|
|
2336
|
+
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
2337
|
+
}
|
|
2127
2338
|
}
|
|
2128
2339
|
}
|
|
2129
2340
|
|
|
2130
2341
|
// kernel/scan/watcher.ts
|
|
2131
|
-
import { resolve as
|
|
2342
|
+
import { resolve as resolve10, relative as relative4, sep as sep3 } from "path";
|
|
2132
2343
|
import chokidar from "chokidar";
|
|
2133
2344
|
function createChokidarWatcher(opts) {
|
|
2134
|
-
const absRoots = opts.roots.map((r) =>
|
|
2345
|
+
const absRoots = opts.roots.map((r) => resolve10(opts.cwd, r));
|
|
2135
2346
|
const ignoreFilterOpt = opts.ignoreFilter;
|
|
2136
2347
|
const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
|
|
2137
2348
|
const ignored = getFilter ? (path) => {
|
|
@@ -2144,7 +2355,8 @@ function createChokidarWatcher(opts) {
|
|
|
2144
2355
|
const watcher = chokidar.watch(absRoots, {
|
|
2145
2356
|
ignoreInitial: true,
|
|
2146
2357
|
persistent: true,
|
|
2147
|
-
...ignored ? { ignored } : {}
|
|
2358
|
+
...ignored ? { ignored } : {},
|
|
2359
|
+
...opts.depth !== void 0 ? { depth: opts.depth } : {}
|
|
2148
2360
|
});
|
|
2149
2361
|
let pending = [];
|
|
2150
2362
|
let timer = null;
|