@skill-map/cli 0.21.0 → 0.22.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/dist/cli.js +6974 -6661
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1588 -1471
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +217 -191
- package/dist/kernel/index.js +1588 -1471
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/chunk-GETTEQ3S.js +123 -0
- package/dist/ui/{chunk-W62WVNU4.js → chunk-HC6PNQMW.js} +7 -7
- package/dist/ui/chunk-HJHWJTFH.js +1 -0
- package/dist/ui/index.html +1 -1
- package/dist/ui/{main-NIYE2VFS.js → main-Q2WC254P.js} +2 -2
- package/package.json +6 -5
- package/dist/ui/chunk-6FTVUS57.js +0 -123
- package/dist/ui/chunk-N366HMME.js +0 -1
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.22.0",
|
|
107
104
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
108
105
|
license: "MIT",
|
|
109
106
|
type: "module",
|
|
@@ -156,20 +153,21 @@ 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.22.0",
|
|
173
171
|
ajv: "8.18.0",
|
|
174
172
|
"ajv-formats": "3.0.1",
|
|
175
173
|
chokidar: "5.0.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,128 @@ 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}}) \u2014 {{errors}}",
|
|
275
|
+
dedicatedValidationFailed: "plugin '{{pluginId}}' ctx.store.write('{{table}}', row): row violates declared schema ({{schemaPath}}) \u2014 {{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;
|
|
306
|
-
}
|
|
307
|
-
function resolveSpecRoot() {
|
|
308
|
-
const require2 = createRequire(import.meta.url);
|
|
309
|
-
try {
|
|
310
|
-
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
311
|
-
return dirname(indexPath);
|
|
312
|
-
} catch {
|
|
313
|
-
throw new Error(
|
|
314
|
-
"@skill-map/spec not resolvable \u2014 sidecar reader cannot load schemas."
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// kernel/sidecar/drift.ts
|
|
320
|
-
function computeDriftStatus(args) {
|
|
321
|
-
const bodyDrift = args.storedBodyHash !== args.liveBodyHash;
|
|
322
|
-
const fmDrift = args.storedFrontmatterHash !== args.liveFrontmatterHash;
|
|
323
|
-
if (bodyDrift && fmDrift) return "stale-both";
|
|
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);
|
|
336
|
-
}
|
|
337
|
-
return out;
|
|
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
|
+
};
|
|
338
320
|
}
|
|
339
|
-
function
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return;
|
|
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
|
+
});
|
|
345
333
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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 });
|
|
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
|
+
});
|
|
360
341
|
}
|
|
342
|
+
return void 0;
|
|
361
343
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
} catch {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
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("; ");
|
|
368
347
|
}
|
|
369
348
|
|
|
370
|
-
// kernel/
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
import { isAbsolute, resolve as resolve4 } from "path";
|
|
379
|
-
|
|
380
|
-
// kernel/config/loader.ts
|
|
381
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
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;
|
|
356
|
+
}
|
|
382
357
|
|
|
383
358
|
// kernel/adapters/schema-validators.ts
|
|
384
|
-
import { readFileSync as
|
|
385
|
-
import { dirname
|
|
359
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
360
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
386
361
|
import { createRequire as createRequire2 } from "module";
|
|
387
|
-
import { Ajv2020 as
|
|
362
|
+
import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
|
|
388
363
|
var SCHEMA_FILES = {
|
|
389
364
|
node: "schemas/node.schema.json",
|
|
390
365
|
link: "schemas/link.schema.json",
|
|
@@ -419,23 +394,23 @@ function loadSchemaValidators() {
|
|
|
419
394
|
return cachedValidators;
|
|
420
395
|
}
|
|
421
396
|
function buildSchemaValidators() {
|
|
422
|
-
const specRoot =
|
|
423
|
-
const ajv = new
|
|
397
|
+
const specRoot = resolveSpecRoot();
|
|
398
|
+
const ajv = new Ajv20203({
|
|
424
399
|
strict: false,
|
|
425
400
|
allErrors: true,
|
|
426
401
|
allowUnionTypes: true
|
|
427
402
|
});
|
|
428
403
|
applyAjvFormats(ajv);
|
|
429
404
|
for (const rel of SUPPORTING_SCHEMAS) {
|
|
430
|
-
const file =
|
|
405
|
+
const file = resolve4(specRoot, rel);
|
|
431
406
|
if (!existsSyncSafe(file)) continue;
|
|
432
|
-
const schema = JSON.parse(
|
|
407
|
+
const schema = JSON.parse(readFileSync3(file, "utf8"));
|
|
433
408
|
ajv.addSchema(schema);
|
|
434
409
|
}
|
|
435
410
|
const validators = /* @__PURE__ */ new Map();
|
|
436
411
|
for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
|
|
437
|
-
const file =
|
|
438
|
-
const schema = JSON.parse(
|
|
412
|
+
const file = resolve4(specRoot, rel);
|
|
413
|
+
const schema = JSON.parse(readFileSync3(file, "utf8"));
|
|
439
414
|
const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
|
|
440
415
|
validators.set(name, byId ?? ajv.compile(schema));
|
|
441
416
|
}
|
|
@@ -515,15 +490,15 @@ function buildSchemaValidators() {
|
|
|
515
490
|
};
|
|
516
491
|
}
|
|
517
492
|
function buildProviderFrontmatterValidator(providers) {
|
|
518
|
-
const specRoot =
|
|
519
|
-
const ajv = new
|
|
493
|
+
const specRoot = resolveSpecRoot();
|
|
494
|
+
const ajv = new Ajv20203({
|
|
520
495
|
strict: false,
|
|
521
496
|
allErrors: true,
|
|
522
497
|
allowUnionTypes: true
|
|
523
498
|
});
|
|
524
499
|
applyAjvFormats(ajv);
|
|
525
|
-
const baseFile =
|
|
526
|
-
const baseSchema = JSON.parse(
|
|
500
|
+
const baseFile = resolve4(specRoot, "schemas/frontmatter/base.schema.json");
|
|
501
|
+
const baseSchema = JSON.parse(readFileSync3(baseFile, "utf8"));
|
|
527
502
|
ajv.addSchema(baseSchema);
|
|
528
503
|
registerProviderAuxiliarySchemas(ajv, providers);
|
|
529
504
|
const compiled = /* @__PURE__ */ new Map();
|
|
@@ -560,11 +535,11 @@ function registerProviderAuxiliarySchemas(ajv, providers) {
|
|
|
560
535
|
}
|
|
561
536
|
}
|
|
562
537
|
}
|
|
563
|
-
function
|
|
538
|
+
function resolveSpecRoot() {
|
|
564
539
|
const require2 = createRequire2(import.meta.url);
|
|
565
540
|
try {
|
|
566
541
|
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
567
|
-
return
|
|
542
|
+
return dirname(indexPath);
|
|
568
543
|
} catch {
|
|
569
544
|
throw new Error(
|
|
570
545
|
"@skill-map/spec not resolvable \u2014 ensure the workspace is linked or the package is installed."
|
|
@@ -573,7 +548,7 @@ function resolveSpecRoot2() {
|
|
|
573
548
|
}
|
|
574
549
|
function existsSyncSafe(path) {
|
|
575
550
|
try {
|
|
576
|
-
|
|
551
|
+
readFileSync3(path, "utf8");
|
|
577
552
|
return true;
|
|
578
553
|
} catch {
|
|
579
554
|
return false;
|
|
@@ -585,45 +560,6 @@ function formatErrorMessage(err) {
|
|
|
585
560
|
return err instanceof Error ? err.message : String(err);
|
|
586
561
|
}
|
|
587
562
|
|
|
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
563
|
// kernel/adapters/silent-logger.ts
|
|
628
564
|
var SilentLogger = class {
|
|
629
565
|
trace() {
|
|
@@ -657,115 +593,83 @@ function getActiveLogger() {
|
|
|
657
593
|
return active;
|
|
658
594
|
}
|
|
659
595
|
|
|
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);
|
|
596
|
+
// kernel/extensions/hook-dispatcher.ts
|
|
597
|
+
function makeHookDispatcher(hooks, emitter) {
|
|
598
|
+
if (hooks.length === 0) {
|
|
599
|
+
return { dispatch: async () => {
|
|
600
|
+
} };
|
|
601
|
+
}
|
|
602
|
+
const byTrigger = /* @__PURE__ */ new Map();
|
|
603
|
+
for (const hook of hooks) {
|
|
604
|
+
if (hook.mode === "probabilistic") {
|
|
605
|
+
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
606
|
+
log.warn(
|
|
607
|
+
`Probabilistic hook ${qualifiedId} deferred to job subsystem (future job subsystem). The hook is registered but will not dispatch in-scan.`,
|
|
608
|
+
{ hookId: qualifiedId, mode: "probabilistic" }
|
|
609
|
+
);
|
|
610
|
+
continue;
|
|
693
611
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
612
|
+
for (const trig of hook.triggers) {
|
|
613
|
+
const bucket = byTrigger.get(trig);
|
|
614
|
+
if (bucket) bucket.push(hook);
|
|
615
|
+
else byTrigger.set(trig, [hook]);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
698
618
|
return {
|
|
699
|
-
async
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
619
|
+
async dispatch(trigger, event) {
|
|
620
|
+
const subs = byTrigger.get(trigger);
|
|
621
|
+
if (!subs || subs.length === 0) return;
|
|
622
|
+
for (const hook of subs) {
|
|
623
|
+
if (!matchesFilter(hook, event)) continue;
|
|
624
|
+
const ctx = buildHookContext(hook, trigger, event);
|
|
625
|
+
try {
|
|
626
|
+
await hook.on(ctx);
|
|
627
|
+
} catch (err) {
|
|
628
|
+
const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
|
|
629
|
+
const message = formatErrorMessage(err);
|
|
630
|
+
emitter.emit(
|
|
631
|
+
makeEvent("extension.error", {
|
|
632
|
+
kind: "hook-error",
|
|
633
|
+
extensionId: qualifiedId,
|
|
634
|
+
trigger,
|
|
635
|
+
message
|
|
709
636
|
})
|
|
710
637
|
);
|
|
711
638
|
}
|
|
712
639
|
}
|
|
713
|
-
await persist(table, row);
|
|
714
640
|
}
|
|
715
641
|
};
|
|
716
642
|
}
|
|
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;
|
|
643
|
+
function makeEvent(type, data) {
|
|
644
|
+
return { type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
739
645
|
}
|
|
740
|
-
function
|
|
741
|
-
if (!
|
|
742
|
-
|
|
646
|
+
function matchesFilter(hook, event) {
|
|
647
|
+
if (!hook.filter) return true;
|
|
648
|
+
const data = event.data ?? {};
|
|
649
|
+
for (const [key, expected] of Object.entries(hook.filter)) {
|
|
650
|
+
if (data[key] !== expected) return false;
|
|
651
|
+
}
|
|
652
|
+
return true;
|
|
743
653
|
}
|
|
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;
|
|
654
|
+
function buildHookContext(_hook, trigger, event) {
|
|
655
|
+
const data = event.data ?? {};
|
|
656
|
+
const ctx = {
|
|
657
|
+
event: {
|
|
658
|
+
type: trigger,
|
|
659
|
+
timestamp: event.timestamp,
|
|
660
|
+
...event.runId !== void 0 ? { runId: event.runId } : {},
|
|
661
|
+
...event.jobId !== void 0 ? { jobId: event.jobId } : {},
|
|
662
|
+
data: event.data
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
if (typeof data["extractorId"] === "string") ctx.extractorId = data["extractorId"];
|
|
666
|
+
if (typeof data["analyzerId"] === "string") ctx.analyzerId = data["analyzerId"];
|
|
667
|
+
if (typeof data["actionId"] === "string") ctx.actionId = data["actionId"];
|
|
668
|
+
if (data["node"] && typeof data["node"] === "object") {
|
|
669
|
+
ctx.node = data["node"];
|
|
670
|
+
}
|
|
671
|
+
if (data["jobResult"] !== void 0) ctx.jobResult = data["jobResult"];
|
|
672
|
+
return ctx;
|
|
769
673
|
}
|
|
770
674
|
|
|
771
675
|
// kernel/i18n/orchestrator.texts.ts
|
|
@@ -782,1081 +686,1004 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
782
686
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
783
687
|
};
|
|
784
688
|
|
|
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
|
-
|
|
689
|
+
// kernel/orchestrator/extractors.ts
|
|
690
|
+
async function runExtractorsForNode(opts) {
|
|
691
|
+
const internalLinks = [];
|
|
692
|
+
const externalLinks = [];
|
|
693
|
+
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
694
|
+
const contributions = [];
|
|
695
|
+
const validators = loadSchemaValidators();
|
|
696
|
+
for (const extractor of opts.extractors) {
|
|
697
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
698
|
+
const emitLink = (link) => {
|
|
699
|
+
const validated = validateLink(extractor, link, opts.emitter);
|
|
700
|
+
if (!validated) return;
|
|
701
|
+
if (isExternalUrlLink(validated)) externalLinks.push(validated);
|
|
702
|
+
else internalLinks.push(validated);
|
|
703
|
+
};
|
|
704
|
+
const enrichNode = (partial) => {
|
|
705
|
+
const key = `${opts.node.path}\0${qualifiedId}`;
|
|
706
|
+
const existing = enrichmentBuffer.get(key);
|
|
707
|
+
if (existing) {
|
|
708
|
+
existing.value = { ...existing.value, ...partial };
|
|
709
|
+
existing.enrichedAt = Date.now();
|
|
710
|
+
} else {
|
|
711
|
+
enrichmentBuffer.set(key, {
|
|
712
|
+
nodePath: opts.node.path,
|
|
713
|
+
extractorId: qualifiedId,
|
|
714
|
+
bodyHashAtEnrichment: opts.bodyHash,
|
|
715
|
+
value: { ...partial },
|
|
716
|
+
enrichedAt: Date.now(),
|
|
717
|
+
// Extractors are deterministic-only; `is_probabilistic` is
|
|
718
|
+
// reserved on the row for future Action-issued enrichments.
|
|
719
|
+
isProbabilistic: false
|
|
720
|
+
});
|
|
809
721
|
}
|
|
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 {
|
|
722
|
+
};
|
|
723
|
+
const declaredContributions = readDeclaredContributions(extractor);
|
|
724
|
+
const emitContribution = (contributionId, payload) => {
|
|
725
|
+
const declared = declaredContributions.get(contributionId);
|
|
726
|
+
if (!declared) {
|
|
727
|
+
emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
|
|
728
|
+
phase: "emitContribution",
|
|
729
|
+
contributionId,
|
|
730
|
+
reason: "unknown-contribution-id",
|
|
731
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
|
|
732
|
+
extractorId: qualifiedId,
|
|
733
|
+
contributionId,
|
|
734
|
+
nodePath: opts.node.path
|
|
735
|
+
})
|
|
736
|
+
});
|
|
737
|
+
return;
|
|
836
738
|
}
|
|
837
|
-
|
|
739
|
+
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
740
|
+
if (!result.ok) {
|
|
741
|
+
emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
|
|
742
|
+
phase: "emitContribution",
|
|
743
|
+
contributionId,
|
|
744
|
+
slot: declared.slot,
|
|
745
|
+
reason: result.errors,
|
|
746
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
747
|
+
extractorId: qualifiedId,
|
|
748
|
+
contributionId,
|
|
749
|
+
nodePath: opts.node.path,
|
|
750
|
+
slot: declared.slot,
|
|
751
|
+
errors: result.errors
|
|
752
|
+
})
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
contributions.push({
|
|
757
|
+
pluginId: extractor.pluginId,
|
|
758
|
+
extensionId: extractor.id,
|
|
759
|
+
nodePath: opts.node.path,
|
|
760
|
+
contributionId,
|
|
761
|
+
slot: declared.slot,
|
|
762
|
+
payload,
|
|
763
|
+
emittedAt: Date.now()
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
767
|
+
const ctx = buildExtractorContext(
|
|
768
|
+
extractor,
|
|
769
|
+
opts.node,
|
|
770
|
+
opts.body,
|
|
771
|
+
opts.frontmatter,
|
|
772
|
+
emitLink,
|
|
773
|
+
enrichNode,
|
|
774
|
+
emitContribution,
|
|
775
|
+
store
|
|
776
|
+
);
|
|
777
|
+
await extractor.extract(ctx);
|
|
838
778
|
}
|
|
839
|
-
return
|
|
779
|
+
return {
|
|
780
|
+
internalLinks,
|
|
781
|
+
externalLinks,
|
|
782
|
+
enrichments: Array.from(enrichmentBuffer.values()),
|
|
783
|
+
contributions
|
|
784
|
+
};
|
|
840
785
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (!match) return { frontmatterRaw: "", frontmatter: {}, body: raw };
|
|
851
|
-
const frontmatterRaw = match[1];
|
|
852
|
-
const body = match[2];
|
|
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
|
-
}
|
|
861
|
-
}
|
|
862
|
-
} catch {
|
|
863
|
-
}
|
|
864
|
-
return { frontmatterRaw, frontmatter: parsed, body };
|
|
786
|
+
function readDeclaredContributions(extension) {
|
|
787
|
+
const out = /* @__PURE__ */ new Map();
|
|
788
|
+
const raw = extension.viewContributions;
|
|
789
|
+
if (typeof raw !== "object" || raw === null) return out;
|
|
790
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
791
|
+
if (typeof value !== "object" || value === null) continue;
|
|
792
|
+
const slot = value.slot;
|
|
793
|
+
if (typeof slot !== "string") continue;
|
|
794
|
+
out.set(id, { slot });
|
|
865
795
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
function emitExtensionError(emitter, qualifiedId, nodePath, data) {
|
|
799
|
+
emitter.emit(
|
|
800
|
+
makeEvent("extension.error", {
|
|
801
|
+
kind: "contribution-rejected",
|
|
802
|
+
extensionId: qualifiedId,
|
|
803
|
+
nodePath,
|
|
804
|
+
...data
|
|
805
|
+
})
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
809
|
+
const scope = extractor.scope;
|
|
810
|
+
return {
|
|
811
|
+
node,
|
|
812
|
+
body: scope === "frontmatter" ? "" : body,
|
|
813
|
+
frontmatter: scope === "body" ? {} : frontmatter,
|
|
814
|
+
emitLink,
|
|
815
|
+
enrichNode,
|
|
816
|
+
emitContribution,
|
|
817
|
+
...store !== void 0 ? { store } : {}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function validateLink(extractor, link, emitter) {
|
|
821
|
+
if (!extractor.emitsLinkKinds.includes(link.kind)) {
|
|
822
|
+
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
823
|
+
emitter.emit(
|
|
824
|
+
makeEvent("extension.error", {
|
|
825
|
+
kind: "link-kind-not-declared",
|
|
826
|
+
extensionId: qualifiedId,
|
|
827
|
+
linkKind: link.kind,
|
|
828
|
+
declaredKinds: extractor.emitsLinkKinds,
|
|
829
|
+
link: { source: link.source, target: link.target, kind: link.kind },
|
|
830
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorLinkKindNotDeclared, {
|
|
831
|
+
extractorId: qualifiedId,
|
|
832
|
+
linkKind: link.kind,
|
|
833
|
+
declaredKinds: extractor.emitsLinkKinds.join(", ")
|
|
834
|
+
})
|
|
835
|
+
})
|
|
836
|
+
);
|
|
837
|
+
return null;
|
|
873
838
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
// kernel/scan/parsers/index.ts
|
|
877
|
-
var REGISTRY = /* @__PURE__ */ new Map([
|
|
878
|
-
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
879
|
-
[plainParser.id, plainParser]
|
|
880
|
-
]);
|
|
881
|
-
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
882
|
-
function getParser(id) {
|
|
883
|
-
return REGISTRY.get(id);
|
|
839
|
+
const confidence = link.confidence ?? extractor.defaultConfidence;
|
|
840
|
+
return { ...link, confidence };
|
|
884
841
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
842
|
+
function recomputeLinkCounts(nodes, links) {
|
|
843
|
+
const byPath2 = /* @__PURE__ */ new Map();
|
|
844
|
+
for (const node of nodes) {
|
|
845
|
+
node.linksOutCount = 0;
|
|
846
|
+
node.linksInCount = 0;
|
|
847
|
+
byPath2.set(node.path, node);
|
|
891
848
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const extensions = options.extensions;
|
|
898
|
-
for (const root of roots) {
|
|
899
|
-
for await (const file of walkRoot(root, root, filter, extensions)) {
|
|
900
|
-
const relPath = relative3(root, file).split(sep2).join("/");
|
|
901
|
-
let raw;
|
|
902
|
-
try {
|
|
903
|
-
raw = await readFile(file, "utf8");
|
|
904
|
-
} catch {
|
|
905
|
-
continue;
|
|
906
|
-
}
|
|
907
|
-
const parsed = parser.parse(raw, relPath);
|
|
908
|
-
yield {
|
|
909
|
-
path: relPath,
|
|
910
|
-
body: parsed.body,
|
|
911
|
-
frontmatterRaw: parsed.frontmatterRaw,
|
|
912
|
-
frontmatter: parsed.frontmatter
|
|
913
|
-
};
|
|
914
|
-
}
|
|
849
|
+
for (const link of links) {
|
|
850
|
+
const source = byPath2.get(link.source);
|
|
851
|
+
if (source) source.linksOutCount += 1;
|
|
852
|
+
const target = byPath2.get(link.target);
|
|
853
|
+
if (target) target.linksInCount += 1;
|
|
915
854
|
}
|
|
916
855
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
return;
|
|
856
|
+
function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
857
|
+
const byPath2 = /* @__PURE__ */ new Map();
|
|
858
|
+
for (const node of nodes) {
|
|
859
|
+
if (!cachedPaths.has(node.path)) node.externalRefsCount = 0;
|
|
860
|
+
byPath2.set(node.path, node);
|
|
923
861
|
}
|
|
924
|
-
for (const
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
const rel = relative3(root, full).split(sep2).join("/");
|
|
928
|
-
if (filter.ignores(rel)) continue;
|
|
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
|
-
}
|
|
862
|
+
for (const link of externalLinks) {
|
|
863
|
+
const source = byPath2.get(link.source);
|
|
864
|
+
if (source && !cachedPaths.has(source.path)) source.externalRefsCount += 1;
|
|
939
865
|
}
|
|
940
866
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
return false;
|
|
867
|
+
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
868
|
+
function isExternalUrlLink(link) {
|
|
869
|
+
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
946
870
|
}
|
|
947
871
|
|
|
948
|
-
// kernel/
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
872
|
+
// kernel/orchestrator/analyzers.ts
|
|
873
|
+
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, emitter, hookDispatcher) {
|
|
874
|
+
const issues = [];
|
|
875
|
+
const contributions = [];
|
|
876
|
+
const validators = loadSchemaValidators();
|
|
877
|
+
const analyzerOrphans = orphanSidecars.map((o) => ({
|
|
878
|
+
relativePath: o.relativePath,
|
|
879
|
+
expectedMdPath: o.expectedMdPath
|
|
880
|
+
}));
|
|
881
|
+
for (const analyzer of analyzers) {
|
|
882
|
+
const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
883
|
+
const declaredContributions = readDeclaredContributions(analyzer);
|
|
884
|
+
const emitContribution = (nodePath, contributionId, payload) => {
|
|
885
|
+
const declared = declaredContributions.get(contributionId);
|
|
886
|
+
if (!declared) {
|
|
887
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
888
|
+
phase: "emitContribution",
|
|
889
|
+
contributionId,
|
|
890
|
+
reason: "unknown-contribution-id",
|
|
891
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
|
|
892
|
+
extractorId: qualifiedId,
|
|
893
|
+
contributionId,
|
|
894
|
+
nodePath
|
|
895
|
+
})
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
900
|
+
if (!result.ok) {
|
|
901
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
902
|
+
phase: "emitContribution",
|
|
903
|
+
contributionId,
|
|
904
|
+
slot: declared.slot,
|
|
905
|
+
reason: result.errors,
|
|
906
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
907
|
+
extractorId: qualifiedId,
|
|
908
|
+
contributionId,
|
|
909
|
+
nodePath,
|
|
910
|
+
slot: declared.slot,
|
|
911
|
+
errors: result.errors
|
|
912
|
+
})
|
|
913
|
+
});
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
contributions.push({
|
|
917
|
+
pluginId: analyzer.pluginId,
|
|
918
|
+
extensionId: analyzer.id,
|
|
919
|
+
nodePath,
|
|
920
|
+
contributionId,
|
|
921
|
+
slot: declared.slot,
|
|
922
|
+
payload,
|
|
923
|
+
emittedAt: Date.now()
|
|
924
|
+
});
|
|
963
925
|
};
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
926
|
+
const emitted = await analyzer.evaluate({
|
|
927
|
+
nodes,
|
|
928
|
+
links: internalLinks,
|
|
929
|
+
orphanSidecars: analyzerOrphans,
|
|
930
|
+
sidecarRoots,
|
|
931
|
+
annotationContributions,
|
|
932
|
+
viewContributions,
|
|
933
|
+
orphanJobFiles,
|
|
934
|
+
...referenceablePaths ? { referenceablePaths } : {},
|
|
935
|
+
...cwd ? { cwd } : {},
|
|
936
|
+
emitContribution
|
|
937
|
+
});
|
|
938
|
+
for (const issue of emitted) {
|
|
939
|
+
const validated = validateIssue(analyzer, issue, emitter);
|
|
940
|
+
if (validated) issues.push(validated);
|
|
941
|
+
}
|
|
942
|
+
const evt = makeEvent("analyzer.completed", { analyzerId: qualifiedId });
|
|
943
|
+
emitter.emit(evt);
|
|
944
|
+
await hookDispatcher.dispatch("analyzer.completed", evt);
|
|
945
|
+
}
|
|
946
|
+
return { issues, contributions };
|
|
947
|
+
}
|
|
948
|
+
function validateIssue(analyzer, issue, emitter) {
|
|
949
|
+
const severity = issue.severity;
|
|
950
|
+
if (severity !== "error" && severity !== "warn" && severity !== "info") {
|
|
951
|
+
const qualifiedId = `${analyzer.pluginId}/${analyzer.id}`;
|
|
952
|
+
emitter.emit(
|
|
953
|
+
makeEvent("extension.error", {
|
|
954
|
+
kind: "issue-invalid-severity",
|
|
955
|
+
extensionId: qualifiedId,
|
|
956
|
+
severity,
|
|
957
|
+
issue: { analyzerId: issue.analyzerId || analyzer.id, message: issue.message, nodeIds: issue.nodeIds },
|
|
958
|
+
message: tx(ORCHESTRATOR_TEXTS.extensionErrorIssueInvalidSeverity, {
|
|
959
|
+
analyzerId: qualifiedId,
|
|
960
|
+
severity: JSON.stringify(severity)
|
|
961
|
+
})
|
|
962
|
+
})
|
|
963
|
+
);
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
-
// kernel/
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
969
|
+
// kernel/orchestrator/cache.ts
|
|
970
|
+
function indexPriorSnapshot(prior) {
|
|
971
|
+
const priorNodesByPath = /* @__PURE__ */ new Map();
|
|
972
|
+
const priorNodePaths = /* @__PURE__ */ new Set();
|
|
973
|
+
const priorLinksByOriginating = /* @__PURE__ */ new Map();
|
|
974
|
+
const priorFrontmatterIssuesByNode = /* @__PURE__ */ new Map();
|
|
975
|
+
if (!prior) {
|
|
976
|
+
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
974
977
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
-
}
|
|
978
|
+
indexPriorNodes(prior.nodes, priorNodesByPath, priorNodePaths);
|
|
979
|
+
indexPriorLinks(prior.links, priorNodePaths, priorLinksByOriginating);
|
|
980
|
+
indexPriorFrontmatterIssues(prior.issues, priorFrontmatterIssuesByNode);
|
|
981
|
+
return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
|
|
982
|
+
}
|
|
983
|
+
function indexPriorNodes(nodes, byPath2, paths) {
|
|
984
|
+
for (const node of nodes) {
|
|
985
|
+
byPath2.set(node.path, node);
|
|
986
|
+
paths.add(node.path);
|
|
990
987
|
}
|
|
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
988
|
}
|
|
1016
|
-
function
|
|
1017
|
-
|
|
989
|
+
function indexPriorLinks(links, priorNodePaths, byOriginating) {
|
|
990
|
+
for (const link of links) {
|
|
991
|
+
const key = originatingNodeOf(link, priorNodePaths);
|
|
992
|
+
const list = byOriginating.get(key);
|
|
993
|
+
if (list) list.push(link);
|
|
994
|
+
else byOriginating.set(key, [link]);
|
|
995
|
+
}
|
|
1018
996
|
}
|
|
1019
|
-
function
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
997
|
+
function indexPriorFrontmatterIssues(issues, byNode) {
|
|
998
|
+
for (const issue of issues) {
|
|
999
|
+
if (issue.analyzerId !== "frontmatter-invalid" && issue.analyzerId !== "frontmatter-malformed") continue;
|
|
1000
|
+
if (issue.nodeIds.length !== 1) continue;
|
|
1001
|
+
const path = issue.nodeIds[0];
|
|
1002
|
+
const list = byNode.get(path);
|
|
1003
|
+
if (list) list.push(issue);
|
|
1004
|
+
else byNode.set(path, [issue]);
|
|
1024
1005
|
}
|
|
1025
|
-
return true;
|
|
1026
1006
|
}
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
|
|
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"];
|
|
1007
|
+
function originatingNodeOf(link, priorNodePaths) {
|
|
1008
|
+
if (link.kind === "supersedes" && !priorNodePaths.has(link.source)) {
|
|
1009
|
+
return link.target;
|
|
1043
1010
|
}
|
|
1044
|
-
|
|
1045
|
-
return ctx;
|
|
1011
|
+
return link.source;
|
|
1046
1012
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1013
|
+
function computeCacheDecision(opts) {
|
|
1014
|
+
const applicableExtractors = opts.extractors.filter(
|
|
1015
|
+
(ex) => ex.applicableKinds === void 0 || ex.applicableKinds.includes(opts.kind)
|
|
1016
|
+
);
|
|
1017
|
+
const applicableQualifiedIds = new Set(
|
|
1018
|
+
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
1019
|
+
);
|
|
1020
|
+
const split = opts.priorExtractorRuns === void 0 ? splitLegacy(applicableExtractors, applicableQualifiedIds, opts.nodeHashCacheEligible) : splitFineGrained(applicableExtractors, opts);
|
|
1021
|
+
return {
|
|
1022
|
+
applicableExtractors,
|
|
1023
|
+
applicableQualifiedIds,
|
|
1024
|
+
cachedQualifiedIds: split.cachedQualifiedIds,
|
|
1025
|
+
missingExtractors: split.missingExtractors,
|
|
1026
|
+
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
1030
|
+
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1031
|
+
const missingExtractors = [];
|
|
1032
|
+
if (nodeHashCacheEligible) {
|
|
1033
|
+
for (const id of applicableQualifiedIds) cachedQualifiedIds.add(id);
|
|
1034
|
+
} else {
|
|
1035
|
+
for (const ex of applicableExtractors) missingExtractors.push(ex);
|
|
1059
1036
|
}
|
|
1037
|
+
return { cachedQualifiedIds, missingExtractors };
|
|
1060
1038
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1039
|
+
function splitFineGrained(applicableExtractors, opts) {
|
|
1040
|
+
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1041
|
+
const missingExtractors = [];
|
|
1042
|
+
const priorRunsForNode = opts.priorExtractorRuns.get(opts.nodePath) ?? /* @__PURE__ */ new Map();
|
|
1043
|
+
for (const ex of applicableExtractors) {
|
|
1044
|
+
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1045
|
+
const prior = priorRunsForNode.get(qualified);
|
|
1046
|
+
if (opts.nodeHashCacheEligible && prior !== void 0 && prior.bodyHash === opts.bodyHash && prior.sidecarAnnotationsHash === opts.sidecarAnnotationsHash) {
|
|
1047
|
+
cachedQualifiedIds.add(qualified);
|
|
1048
|
+
} else {
|
|
1049
|
+
missingExtractors.push(ex);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return { cachedQualifiedIds, missingExtractors };
|
|
1063
1053
|
}
|
|
1064
|
-
|
|
1065
|
-
const {
|
|
1066
|
-
|
|
1054
|
+
function cloneNodeAndReshapeLinks(opts) {
|
|
1055
|
+
const node = { ...opts.priorNode, bytes: { ...opts.priorNode.bytes } };
|
|
1056
|
+
if (opts.priorNode.tokens) node.tokens = { ...opts.priorNode.tokens };
|
|
1057
|
+
const internalLinks = [];
|
|
1058
|
+
const reusedLinks = opts.priorLinksByOriginating.get(opts.priorNode.path) ?? [];
|
|
1059
|
+
for (const link of reusedLinks) {
|
|
1060
|
+
const reshaped = reuseCachedLink(
|
|
1061
|
+
link,
|
|
1062
|
+
opts.shortIdToQualified,
|
|
1063
|
+
opts.cachedQualifiedIds,
|
|
1064
|
+
opts.applicableQualifiedIds
|
|
1065
|
+
);
|
|
1066
|
+
if (reshaped) internalLinks.push(reshaped);
|
|
1067
|
+
}
|
|
1068
|
+
const frontmatterIssues = [];
|
|
1069
|
+
const reusedFm = opts.priorFrontmatterIssuesByNode.get(opts.priorNode.path) ?? [];
|
|
1070
|
+
for (const issue of reusedFm) {
|
|
1071
|
+
frontmatterIssues.push({ ...issue, severity: opts.strict ? "error" : "warn" });
|
|
1072
|
+
}
|
|
1073
|
+
return { node, internalLinks, frontmatterIssues };
|
|
1067
1074
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
const
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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);
|
|
1075
|
+
function reusePriorNode(opts) {
|
|
1076
|
+
const base = cloneNodeAndReshapeLinks(opts);
|
|
1077
|
+
const ranAt = Date.now();
|
|
1078
|
+
const extractorRuns = [];
|
|
1079
|
+
for (const qualified of opts.cachedQualifiedIds) {
|
|
1080
|
+
extractorRuns.push({
|
|
1081
|
+
nodePath: opts.priorNode.path,
|
|
1082
|
+
extractorId: qualified,
|
|
1083
|
+
bodyHashAtRun: opts.bodyHash,
|
|
1084
|
+
ranAt,
|
|
1085
|
+
sidecarAnnotationsHashAtRun: opts.sidecarAnnotationsHash
|
|
1086
|
+
});
|
|
1109
1087
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
options.referenceablePaths,
|
|
1120
|
-
options.cwd,
|
|
1121
|
-
emitter,
|
|
1122
|
-
hookDispatcher
|
|
1088
|
+
return { ...base, extractorRuns };
|
|
1089
|
+
}
|
|
1090
|
+
function reuseCachedLink(link, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1091
|
+
if (!Array.isArray(link.sources) || link.sources.length === 0) return null;
|
|
1092
|
+
const partition = partitionLinkSources(
|
|
1093
|
+
link.sources,
|
|
1094
|
+
shortIdToQualified,
|
|
1095
|
+
cachedQualifiedIds,
|
|
1096
|
+
applicableQualifiedIds
|
|
1123
1097
|
);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1098
|
+
if (partition.hasMissing) return null;
|
|
1099
|
+
if (partition.cached.length === 0) return null;
|
|
1100
|
+
if (partition.obsolete.length === 0) return link;
|
|
1101
|
+
return { ...link, sources: partition.cached };
|
|
1102
|
+
}
|
|
1103
|
+
function partitionLinkSources(sources, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1104
|
+
const cached = [];
|
|
1105
|
+
const obsolete = [];
|
|
1106
|
+
let hasMissing = false;
|
|
1107
|
+
for (const source of sources) {
|
|
1108
|
+
const category = classifyLinkSource(
|
|
1109
|
+
source,
|
|
1110
|
+
shortIdToQualified,
|
|
1111
|
+
cachedQualifiedIds,
|
|
1112
|
+
applicableQualifiedIds
|
|
1113
|
+
);
|
|
1114
|
+
if (category === "cached") cached.push(source);
|
|
1115
|
+
else if (category === "missing") hasMissing = true;
|
|
1116
|
+
else obsolete.push(source);
|
|
1117
|
+
}
|
|
1118
|
+
return { cached, obsolete, hasMissing };
|
|
1119
|
+
}
|
|
1120
|
+
function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
|
|
1121
|
+
const candidates = shortIdToQualified.get(source);
|
|
1122
|
+
if (!candidates || candidates.length === 0) return "obsolete";
|
|
1123
|
+
if (candidates.some((q) => cachedQualifiedIds.has(q))) return "cached";
|
|
1124
|
+
if (candidates.some((q) => applicableQualifiedIds.has(q))) return "missing";
|
|
1125
|
+
return "obsolete";
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// kernel/orchestrator/renames.ts
|
|
1129
|
+
function findHighConfidenceRenames(opts) {
|
|
1130
|
+
const ops = [];
|
|
1131
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1132
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1133
|
+
const fromNode = opts.priorByPath.get(fromPath);
|
|
1134
|
+
for (const toPath of opts.newPaths) {
|
|
1135
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1136
|
+
const toNode = opts.currentByPath.get(toPath);
|
|
1137
|
+
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
1138
|
+
ops.push({ from: fromPath, to: toPath, confidence: "high" });
|
|
1139
|
+
opts.claimedDeleted.add(fromPath);
|
|
1140
|
+
opts.claimedNew.add(toPath);
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1130
1143
|
}
|
|
1131
1144
|
}
|
|
1132
|
-
|
|
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
|
-
};
|
|
1145
|
+
return ops;
|
|
1169
1146
|
}
|
|
1170
|
-
function
|
|
1171
|
-
|
|
1172
|
-
|
|
1147
|
+
function buildFrontmatterRenameCandidates(opts) {
|
|
1148
|
+
const candidatesByNew = /* @__PURE__ */ new Map();
|
|
1149
|
+
for (const toPath of opts.newPaths) {
|
|
1150
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1151
|
+
const toNode = opts.currentByPath.get(toPath);
|
|
1152
|
+
const matches = [];
|
|
1153
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1154
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1155
|
+
const fromNode = opts.priorByPath.get(fromPath);
|
|
1156
|
+
if (toNode.frontmatterHash === fromNode.frontmatterHash) {
|
|
1157
|
+
matches.push(fromPath);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (matches.length > 0) candidatesByNew.set(toPath, matches);
|
|
1173
1161
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1162
|
+
return candidatesByNew;
|
|
1163
|
+
}
|
|
1164
|
+
function claimSingletonRenames(opts) {
|
|
1165
|
+
const ops = [];
|
|
1166
|
+
for (const toPath of opts.newPaths) {
|
|
1167
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1168
|
+
const candidates = opts.candidatesByNew.get(toPath);
|
|
1169
|
+
if (!candidates) continue;
|
|
1170
|
+
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1171
|
+
if (remaining.length === 1) {
|
|
1172
|
+
const fromPath = remaining[0];
|
|
1173
|
+
ops.push({ from: fromPath, to: toPath, confidence: "medium" });
|
|
1174
|
+
opts.issues.push({
|
|
1175
|
+
analyzerId: "auto-rename-medium",
|
|
1176
|
+
severity: "warn",
|
|
1177
|
+
nodeIds: [toPath],
|
|
1178
|
+
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
1179
|
+
data: { from: fromPath, to: toPath, confidence: "medium" }
|
|
1180
|
+
});
|
|
1181
|
+
opts.claimedDeleted.add(fromPath);
|
|
1182
|
+
opts.claimedNew.add(toPath);
|
|
1177
1183
|
}
|
|
1178
1184
|
}
|
|
1185
|
+
return ops;
|
|
1179
1186
|
}
|
|
1180
|
-
function
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
+
function flagAmbiguousRenames(opts) {
|
|
1188
|
+
for (const toPath of opts.newPaths) {
|
|
1189
|
+
if (opts.claimedNew.has(toPath)) continue;
|
|
1190
|
+
const candidates = opts.candidatesByNew.get(toPath);
|
|
1191
|
+
if (!candidates) continue;
|
|
1192
|
+
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1193
|
+
if (remaining.length > 1) {
|
|
1194
|
+
opts.issues.push({
|
|
1195
|
+
analyzerId: "auto-rename-ambiguous",
|
|
1196
|
+
severity: "warn",
|
|
1197
|
+
nodeIds: [toPath],
|
|
1198
|
+
message: `Auto-rename ambiguous: ${toPath} matches ${remaining.length} prior frontmatters \u2014 pick one with \`sm orphans undo-rename ${toPath} --from <old.path>\`.`,
|
|
1199
|
+
data: { to: toPath, candidates: remaining }
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1187
1202
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1203
|
+
}
|
|
1204
|
+
function flagOrphans(opts) {
|
|
1205
|
+
for (const fromPath of opts.deletedPaths) {
|
|
1206
|
+
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1207
|
+
opts.issues.push({
|
|
1208
|
+
analyzerId: "orphan",
|
|
1209
|
+
severity: "info",
|
|
1210
|
+
nodeIds: [fromPath],
|
|
1211
|
+
message: `Orphan history: ${fromPath} was deleted; no rename match found.`,
|
|
1212
|
+
data: { path: fromPath }
|
|
1213
|
+
});
|
|
1191
1214
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1215
|
+
}
|
|
1216
|
+
function detectRenamesAndOrphans(prior, current, issues) {
|
|
1217
|
+
const priorByPath = /* @__PURE__ */ new Map();
|
|
1218
|
+
for (const n of prior.nodes) priorByPath.set(n.path, n);
|
|
1219
|
+
const currentByPath = /* @__PURE__ */ new Map();
|
|
1220
|
+
for (const n of current) currentByPath.set(n.path, n);
|
|
1221
|
+
const deletedPaths = [...priorByPath.keys()].filter((p) => !currentByPath.has(p)).sort();
|
|
1222
|
+
const newPaths = [...currentByPath.keys()].filter((p) => !priorByPath.has(p)).sort();
|
|
1223
|
+
const claimedDeleted = /* @__PURE__ */ new Set();
|
|
1224
|
+
const claimedNew = /* @__PURE__ */ new Set();
|
|
1225
|
+
const ops = [];
|
|
1226
|
+
ops.push(...findHighConfidenceRenames({
|
|
1227
|
+
deletedPaths,
|
|
1228
|
+
newPaths,
|
|
1229
|
+
priorByPath,
|
|
1230
|
+
currentByPath,
|
|
1231
|
+
claimedDeleted,
|
|
1232
|
+
claimedNew
|
|
1233
|
+
}));
|
|
1234
|
+
const candidatesByNew = buildFrontmatterRenameCandidates({
|
|
1235
|
+
deletedPaths,
|
|
1236
|
+
newPaths,
|
|
1237
|
+
priorByPath,
|
|
1238
|
+
currentByPath,
|
|
1239
|
+
claimedDeleted,
|
|
1240
|
+
claimedNew
|
|
1241
|
+
});
|
|
1242
|
+
ops.push(...claimSingletonRenames({
|
|
1243
|
+
newPaths,
|
|
1244
|
+
candidatesByNew,
|
|
1245
|
+
claimedDeleted,
|
|
1246
|
+
claimedNew,
|
|
1247
|
+
issues
|
|
1248
|
+
}));
|
|
1249
|
+
flagAmbiguousRenames({ newPaths, candidatesByNew, claimedDeleted, claimedNew, issues });
|
|
1250
|
+
flagOrphans({ deletedPaths, claimedDeleted, issues });
|
|
1251
|
+
return ops;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// kernel/scan/walk-content.ts
|
|
1255
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
1256
|
+
import { join as join2, relative as relative2, sep } from "path";
|
|
1257
|
+
|
|
1258
|
+
// kernel/scan/ignore.ts
|
|
1259
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
1260
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
1261
|
+
import { fileURLToPath } from "url";
|
|
1262
|
+
import ignoreFactory from "ignore";
|
|
1263
|
+
function buildIgnoreFilter(opts = {}) {
|
|
1264
|
+
const ig = ignoreFactory();
|
|
1265
|
+
if (opts.includeDefaults !== false) {
|
|
1266
|
+
ig.add(loadDefaultsText());
|
|
1197
1267
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
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]);
|
|
1268
|
+
if (opts.configIgnore && opts.configIgnore.length > 0) {
|
|
1269
|
+
ig.add(opts.configIgnore);
|
|
1205
1270
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const validators = loadSchemaValidators();
|
|
1214
|
-
for (const extractor of opts.extractors) {
|
|
1215
|
-
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
1216
|
-
const emitLink = (link) => {
|
|
1217
|
-
const validated = validateLink(extractor, link, opts.emitter);
|
|
1218
|
-
if (!validated) return;
|
|
1219
|
-
if (isExternalUrlLink(validated)) externalLinks.push(validated);
|
|
1220
|
-
else internalLinks.push(validated);
|
|
1221
|
-
};
|
|
1222
|
-
const enrichNode = (partial) => {
|
|
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
|
-
});
|
|
1271
|
+
if (opts.ignoreFileText && opts.ignoreFileText.length > 0) {
|
|
1272
|
+
ig.add(opts.ignoreFileText);
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
ignores(relativePath) {
|
|
1276
|
+
if (relativePath === "" || relativePath === "." || relativePath === "./") {
|
|
1277
|
+
return false;
|
|
1239
1278
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1279
|
+
const normalised = relativePath.replace(/^\.\//, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
1280
|
+
if (normalised === "") return false;
|
|
1281
|
+
return ig.ignores(normalised);
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
var cachedDefaults = null;
|
|
1286
|
+
function loadDefaultsText() {
|
|
1287
|
+
if (cachedDefaults !== null) return cachedDefaults;
|
|
1288
|
+
cachedDefaults = readDefaultsFromDisk();
|
|
1289
|
+
return cachedDefaults;
|
|
1290
|
+
}
|
|
1291
|
+
function readDefaultsFromDisk() {
|
|
1292
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
1293
|
+
const candidates = [
|
|
1294
|
+
resolve5(here, "../../config/defaults/skillmapignore"),
|
|
1295
|
+
// src/kernel/scan/ → src/config/defaults/
|
|
1296
|
+
resolve5(here, "../config/defaults/skillmapignore"),
|
|
1297
|
+
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
1298
|
+
resolve5(here, "config/defaults/skillmapignore")
|
|
1299
|
+
];
|
|
1300
|
+
for (const candidate of candidates) {
|
|
1301
|
+
if (existsSync2(candidate)) {
|
|
1302
|
+
try {
|
|
1303
|
+
return readFileSync4(candidate, "utf8");
|
|
1304
|
+
} catch {
|
|
1256
1305
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return "";
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// built-in-plugins/parsers/frontmatter-yaml/index.ts
|
|
1312
|
+
import yaml from "js-yaml";
|
|
1313
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1314
|
+
var FORBIDDEN_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1315
|
+
var frontmatterYamlParser = {
|
|
1316
|
+
id: "frontmatter-yaml",
|
|
1317
|
+
parse(raw, _path) {
|
|
1318
|
+
const match = FRONTMATTER_RE.exec(raw);
|
|
1319
|
+
if (!match) return { frontmatterRaw: "", frontmatter: {}, body: raw };
|
|
1320
|
+
const frontmatterRaw = match[1];
|
|
1321
|
+
const body = match[2];
|
|
1322
|
+
const parsed = {};
|
|
1323
|
+
try {
|
|
1324
|
+
const doc = yaml.load(frontmatterRaw, { schema: yaml.JSON_SCHEMA });
|
|
1325
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
1326
|
+
for (const [k, v] of Object.entries(doc)) {
|
|
1327
|
+
if (FORBIDDEN_FRONTMATTER_KEYS.has(k)) continue;
|
|
1328
|
+
parsed[k] = v;
|
|
1329
|
+
}
|
|
1273
1330
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
const ctx = buildExtractorContext(
|
|
1286
|
-
extractor,
|
|
1287
|
-
opts.node,
|
|
1288
|
-
opts.body,
|
|
1289
|
-
opts.frontmatter,
|
|
1290
|
-
emitLink,
|
|
1291
|
-
enrichNode,
|
|
1292
|
-
emitContribution,
|
|
1293
|
-
store
|
|
1294
|
-
);
|
|
1295
|
-
await extractor.extract(ctx);
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
return { frontmatterRaw, frontmatter: parsed, body };
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
// built-in-plugins/parsers/plain/index.ts
|
|
1338
|
+
var plainParser = {
|
|
1339
|
+
id: "plain",
|
|
1340
|
+
parse(raw, _path) {
|
|
1341
|
+
return { frontmatter: {}, frontmatterRaw: "", body: raw };
|
|
1296
1342
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
// kernel/scan/parsers/index.ts
|
|
1346
|
+
var REGISTRY = /* @__PURE__ */ new Map([
|
|
1347
|
+
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
1348
|
+
[plainParser.id, plainParser]
|
|
1349
|
+
]);
|
|
1350
|
+
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
1351
|
+
function getParser(id) {
|
|
1352
|
+
return REGISTRY.get(id);
|
|
1303
1353
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const slot = value.slot;
|
|
1311
|
-
if (typeof slot !== "string") continue;
|
|
1312
|
-
out.set(id, { slot });
|
|
1354
|
+
|
|
1355
|
+
// kernel/scan/walk-content.ts
|
|
1356
|
+
var UnknownParserError = class extends Error {
|
|
1357
|
+
constructor(parserId) {
|
|
1358
|
+
super(`Unknown parser id '${parserId}'. Built-in parsers: 'frontmatter-yaml', 'plain'.`);
|
|
1359
|
+
this.name = "UnknownParserError";
|
|
1313
1360
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
(ex) => ex.applicableKinds === void 0 || ex.applicableKinds.includes(opts.kind)
|
|
1329
|
-
);
|
|
1330
|
-
const applicableQualifiedIds = new Set(
|
|
1331
|
-
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
1332
|
-
);
|
|
1333
|
-
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1334
|
-
const missingExtractors = [];
|
|
1335
|
-
if (opts.priorExtractorRuns === void 0) {
|
|
1336
|
-
if (opts.nodeHashCacheEligible) {
|
|
1337
|
-
for (const id of applicableQualifiedIds) cachedQualifiedIds.add(id);
|
|
1338
|
-
} else {
|
|
1339
|
-
for (const ex of applicableExtractors) missingExtractors.push(ex);
|
|
1340
|
-
}
|
|
1341
|
-
} else {
|
|
1342
|
-
const priorRunsForNode = opts.priorExtractorRuns.get(opts.nodePath) ?? /* @__PURE__ */ new Map();
|
|
1343
|
-
for (const ex of applicableExtractors) {
|
|
1344
|
-
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1345
|
-
const prior = priorRunsForNode.get(qualified);
|
|
1346
|
-
const bodyMatch = prior !== void 0 && prior.bodyHash === opts.bodyHash;
|
|
1347
|
-
const sidecarOk = prior !== void 0 && prior.sidecarAnnotationsHash === opts.sidecarAnnotationsHash;
|
|
1348
|
-
if (opts.nodeHashCacheEligible && bodyMatch && sidecarOk) {
|
|
1349
|
-
cachedQualifiedIds.add(qualified);
|
|
1350
|
-
} else {
|
|
1351
|
-
missingExtractors.push(ex);
|
|
1361
|
+
};
|
|
1362
|
+
async function* walkContent(roots, options) {
|
|
1363
|
+
const parser = getParser(options.parser);
|
|
1364
|
+
if (!parser) throw new UnknownParserError(options.parser);
|
|
1365
|
+
const filter = options.ignoreFilter ?? buildIgnoreFilter();
|
|
1366
|
+
const extensions = options.extensions;
|
|
1367
|
+
for (const root of roots) {
|
|
1368
|
+
for await (const file of walkRoot(root, root, filter, extensions)) {
|
|
1369
|
+
const relPath = relative2(root, file).split(sep).join("/");
|
|
1370
|
+
let raw;
|
|
1371
|
+
try {
|
|
1372
|
+
raw = await readFile(file, "utf8");
|
|
1373
|
+
} catch {
|
|
1374
|
+
continue;
|
|
1352
1375
|
}
|
|
1376
|
+
const parsed = parser.parse(raw, relPath);
|
|
1377
|
+
yield {
|
|
1378
|
+
path: relPath,
|
|
1379
|
+
body: parsed.body,
|
|
1380
|
+
frontmatterRaw: parsed.frontmatterRaw,
|
|
1381
|
+
frontmatter: parsed.frontmatter
|
|
1382
|
+
};
|
|
1353
1383
|
}
|
|
1354
1384
|
}
|
|
1355
|
-
return {
|
|
1356
|
-
applicableExtractors,
|
|
1357
|
-
applicableQualifiedIds,
|
|
1358
|
-
cachedQualifiedIds,
|
|
1359
|
-
missingExtractors,
|
|
1360
|
-
fullCacheHit: opts.nodeHashCacheEligible && missingExtractors.length === 0
|
|
1361
|
-
};
|
|
1362
1385
|
}
|
|
1363
|
-
function
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
const reshaped = reuseCachedLink(
|
|
1370
|
-
link,
|
|
1371
|
-
opts.shortIdToQualified,
|
|
1372
|
-
opts.cachedQualifiedIds,
|
|
1373
|
-
opts.applicableQualifiedIds
|
|
1374
|
-
);
|
|
1375
|
-
if (reshaped) internalLinks.push(reshaped);
|
|
1386
|
+
async function* walkRoot(root, current, filter, extensions) {
|
|
1387
|
+
let entries;
|
|
1388
|
+
try {
|
|
1389
|
+
entries = await readdir(current, { withFileTypes: true, encoding: "utf8" });
|
|
1390
|
+
} catch {
|
|
1391
|
+
return;
|
|
1376
1392
|
}
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1393
|
+
for (const entry of entries) {
|
|
1394
|
+
const name = entry.name;
|
|
1395
|
+
const full = join2(current, name);
|
|
1396
|
+
const rel = relative2(root, full).split(sep).join("/");
|
|
1397
|
+
if (filter.ignores(rel)) continue;
|
|
1398
|
+
if (entry.isSymbolicLink()) continue;
|
|
1399
|
+
if (entry.isDirectory()) {
|
|
1400
|
+
yield* walkRoot(root, full, filter, extensions);
|
|
1401
|
+
} else if (entry.isFile() && hasMatchingExtension(name, extensions)) {
|
|
1402
|
+
try {
|
|
1403
|
+
const s = await stat(full);
|
|
1404
|
+
if (s.isFile()) yield full;
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1381
1408
|
}
|
|
1382
|
-
return { node, internalLinks, frontmatterIssues };
|
|
1383
1409
|
}
|
|
1384
|
-
function
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
const extractorRuns = [];
|
|
1388
|
-
for (const qualified of opts.cachedQualifiedIds) {
|
|
1389
|
-
extractorRuns.push({
|
|
1390
|
-
nodePath: opts.priorNode.path,
|
|
1391
|
-
extractorId: qualified,
|
|
1392
|
-
bodyHashAtRun: opts.bodyHash,
|
|
1393
|
-
ranAt,
|
|
1394
|
-
sidecarAnnotationsHashAtRun: opts.sidecarAnnotationsHash
|
|
1395
|
-
});
|
|
1410
|
+
function hasMatchingExtension(name, extensions) {
|
|
1411
|
+
for (const ext of extensions) {
|
|
1412
|
+
if (name.endsWith(ext)) return true;
|
|
1396
1413
|
}
|
|
1397
|
-
return
|
|
1414
|
+
return false;
|
|
1398
1415
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
encoder: opts.encoder
|
|
1410
|
-
});
|
|
1411
|
-
const frontmatterIssues = [];
|
|
1412
|
-
if (opts.raw.frontmatterRaw.length > 0) {
|
|
1413
|
-
const fmIssue = validateFrontmatter(
|
|
1414
|
-
opts.providerFrontmatter,
|
|
1415
|
-
opts.provider,
|
|
1416
|
-
opts.kind,
|
|
1417
|
-
opts.raw.frontmatter,
|
|
1418
|
-
opts.raw.path,
|
|
1419
|
-
opts.strict
|
|
1420
|
-
);
|
|
1421
|
-
if (fmIssue) frontmatterIssues.push(fmIssue);
|
|
1422
|
-
} else {
|
|
1423
|
-
const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
|
|
1424
|
-
if (malformed) frontmatterIssues.push(malformed);
|
|
1416
|
+
|
|
1417
|
+
// kernel/extensions/provider.ts
|
|
1418
|
+
var DEFAULT_READ_CONFIG = Object.freeze({
|
|
1419
|
+
extensions: Object.freeze([".md"]),
|
|
1420
|
+
parser: "frontmatter-yaml"
|
|
1421
|
+
});
|
|
1422
|
+
function resolveProviderWalk(provider) {
|
|
1423
|
+
if (provider.walk) {
|
|
1424
|
+
const walk2 = provider.walk.bind(provider);
|
|
1425
|
+
return walk2;
|
|
1425
1426
|
}
|
|
1426
|
-
|
|
1427
|
+
const read = provider.read ?? DEFAULT_READ_CONFIG;
|
|
1428
|
+
return (roots, options) => {
|
|
1429
|
+
const walkOptions = {
|
|
1430
|
+
extensions: read.extensions,
|
|
1431
|
+
parser: read.parser
|
|
1432
|
+
};
|
|
1433
|
+
if (options?.ignoreFilter) walkOptions.ignoreFilter = options.ignoreFilter;
|
|
1434
|
+
return walkContent(roots, walkOptions);
|
|
1435
|
+
};
|
|
1427
1436
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
priorIndex,
|
|
1440
|
-
priorExtractorRuns,
|
|
1441
|
-
providerFrontmatter,
|
|
1442
|
-
pluginStores
|
|
1443
|
-
} = opts;
|
|
1444
|
-
const { priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode } = priorIndex;
|
|
1445
|
-
const nodes = [];
|
|
1446
|
-
const internalLinks = [];
|
|
1447
|
-
const externalLinks = [];
|
|
1448
|
-
const cachedPaths = /* @__PURE__ */ new Set();
|
|
1449
|
-
const frontmatterIssues = [];
|
|
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]);
|
|
1437
|
+
|
|
1438
|
+
// kernel/sidecar/parse.ts
|
|
1439
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
1440
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
1441
|
+
import { createRequire as createRequire3 } from "module";
|
|
1442
|
+
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
1443
|
+
import yaml2 from "js-yaml";
|
|
1444
|
+
function readSidecarFor(mdAbsolutePath) {
|
|
1445
|
+
const sidecarPath = sidecarPathFor(mdAbsolutePath);
|
|
1446
|
+
if (!existsSync3(sidecarPath)) {
|
|
1447
|
+
return { parsed: null, present: false, issues: [] };
|
|
1464
1448
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
}
|
|
1499
|
-
|
|
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
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1449
|
+
let raw;
|
|
1450
|
+
try {
|
|
1451
|
+
raw = readFileSync5(sidecarPath, "utf8");
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
return {
|
|
1454
|
+
parsed: null,
|
|
1455
|
+
present: true,
|
|
1456
|
+
issues: [{ message: `cannot read ${sidecarPath}: ${err.message}` }]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
let parsedYaml;
|
|
1460
|
+
try {
|
|
1461
|
+
parsedYaml = yaml2.load(raw);
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
return {
|
|
1464
|
+
parsed: null,
|
|
1465
|
+
present: true,
|
|
1466
|
+
issues: [{ message: `malformed YAML in ${sidecarPath}: ${err.message}` }]
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
if (!isPlainObject(parsedYaml)) {
|
|
1470
|
+
return {
|
|
1471
|
+
parsed: null,
|
|
1472
|
+
present: true,
|
|
1473
|
+
issues: [{ message: `sidecar root must be a YAML mapping at ${sidecarPath}` }]
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
const sidecarValidator = getSidecarValidator();
|
|
1477
|
+
if (!sidecarValidator(parsedYaml)) {
|
|
1478
|
+
const errors = (sidecarValidator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
1479
|
+
return {
|
|
1480
|
+
parsed: null,
|
|
1481
|
+
present: true,
|
|
1482
|
+
issues: [{ message: `sidecar schema validation failed at ${sidecarPath}: ${errors}` }]
|
|
1483
|
+
};
|
|
1608
1484
|
}
|
|
1609
|
-
const
|
|
1485
|
+
const root = parsedYaml;
|
|
1486
|
+
const identityBlock = root["identity"];
|
|
1487
|
+
const annotationsRaw = root["annotations"];
|
|
1488
|
+
const annotations = isPlainObject(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
|
|
1610
1489
|
return {
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
orphanSidecars,
|
|
1622
|
-
sidecarRoots
|
|
1490
|
+
parsed: {
|
|
1491
|
+
filePath: sidecarPath,
|
|
1492
|
+
identityBodyHash: String(identityBlock["bodyHash"]),
|
|
1493
|
+
identityFrontmatterHash: String(identityBlock["frontmatterHash"]),
|
|
1494
|
+
identityPath: String(identityBlock["path"]),
|
|
1495
|
+
annotations,
|
|
1496
|
+
raw: root
|
|
1497
|
+
},
|
|
1498
|
+
present: true,
|
|
1499
|
+
issues: []
|
|
1623
1500
|
};
|
|
1624
1501
|
}
|
|
1625
|
-
function
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1628
|
-
const obsoleteSources = [];
|
|
1629
|
-
let hasMissing = false;
|
|
1630
|
-
for (const source of link.sources) {
|
|
1631
|
-
const candidates = shortIdToQualified.get(source);
|
|
1632
|
-
if (!candidates || candidates.length === 0) {
|
|
1633
|
-
obsoleteSources.push(source);
|
|
1634
|
-
continue;
|
|
1635
|
-
}
|
|
1636
|
-
if (candidates.some((q) => cachedQualifiedIds.has(q))) {
|
|
1637
|
-
cachedSources.push(source);
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
if (candidates.some((q) => applicableQualifiedIds.has(q))) {
|
|
1641
|
-
hasMissing = true;
|
|
1642
|
-
continue;
|
|
1643
|
-
}
|
|
1644
|
-
obsoleteSources.push(source);
|
|
1502
|
+
function sidecarPathFor(mdAbsolutePath) {
|
|
1503
|
+
if (mdAbsolutePath.endsWith(".md")) {
|
|
1504
|
+
return `${mdAbsolutePath.slice(0, -".md".length)}.sm`;
|
|
1645
1505
|
}
|
|
1646
|
-
|
|
1647
|
-
if (cachedSources.length === 0) return null;
|
|
1648
|
-
if (obsoleteSources.length === 0) return link;
|
|
1649
|
-
return { ...link, sources: cachedSources };
|
|
1506
|
+
return `${mdAbsolutePath}.sm`;
|
|
1650
1507
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
1680
|
-
phase: "emitContribution",
|
|
1681
|
-
contributionId,
|
|
1682
|
-
slot: declared.slot,
|
|
1683
|
-
reason: result.errors,
|
|
1684
|
-
message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
1685
|
-
extractorId: qualifiedId,
|
|
1686
|
-
contributionId,
|
|
1687
|
-
nodePath,
|
|
1688
|
-
slot: declared.slot,
|
|
1689
|
-
errors: result.errors
|
|
1690
|
-
})
|
|
1691
|
-
});
|
|
1692
|
-
return;
|
|
1693
|
-
}
|
|
1694
|
-
contributions.push({
|
|
1695
|
-
pluginId: analyzer.pluginId,
|
|
1696
|
-
extensionId: analyzer.id,
|
|
1697
|
-
nodePath,
|
|
1698
|
-
contributionId,
|
|
1699
|
-
slot: declared.slot,
|
|
1700
|
-
payload,
|
|
1701
|
-
emittedAt: Date.now()
|
|
1702
|
-
});
|
|
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
|
-
}
|
|
1720
|
-
const evt = makeEvent("analyzer.completed", { analyzerId: qualifiedId });
|
|
1721
|
-
emitter.emit(evt);
|
|
1722
|
-
await hookDispatcher.dispatch("analyzer.completed", evt);
|
|
1508
|
+
function isPlainObject(value) {
|
|
1509
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1510
|
+
}
|
|
1511
|
+
var cachedSidecarValidator = null;
|
|
1512
|
+
function getSidecarValidator() {
|
|
1513
|
+
if (cachedSidecarValidator) return cachedSidecarValidator;
|
|
1514
|
+
const ajv = new Ajv20204({ strict: false, allErrors: true, allowUnionTypes: true });
|
|
1515
|
+
applyAjvFormats(ajv);
|
|
1516
|
+
const specRoot = resolveSpecRoot2();
|
|
1517
|
+
const annotationsSchema = JSON.parse(
|
|
1518
|
+
readFileSync5(resolve6(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
1519
|
+
);
|
|
1520
|
+
const sidecarSchema = JSON.parse(
|
|
1521
|
+
readFileSync5(resolve6(specRoot, "schemas/sidecar.schema.json"), "utf8")
|
|
1522
|
+
);
|
|
1523
|
+
ajv.addSchema(annotationsSchema);
|
|
1524
|
+
cachedSidecarValidator = ajv.compile(sidecarSchema);
|
|
1525
|
+
return cachedSidecarValidator;
|
|
1526
|
+
}
|
|
1527
|
+
function resolveSpecRoot2() {
|
|
1528
|
+
const require2 = createRequire3(import.meta.url);
|
|
1529
|
+
try {
|
|
1530
|
+
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
1531
|
+
return dirname3(indexPath);
|
|
1532
|
+
} catch {
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
"@skill-map/spec not resolvable \u2014 sidecar reader cannot load schemas."
|
|
1535
|
+
);
|
|
1723
1536
|
}
|
|
1724
|
-
return { issues, contributions };
|
|
1725
1537
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1538
|
+
|
|
1539
|
+
// kernel/sidecar/drift.ts
|
|
1540
|
+
function computeDriftStatus(args) {
|
|
1541
|
+
const bodyDrift = args.storedBodyHash !== args.liveBodyHash;
|
|
1542
|
+
const fmDrift = args.storedFrontmatterHash !== args.liveFrontmatterHash;
|
|
1543
|
+
if (bodyDrift && fmDrift) return "stale-both";
|
|
1544
|
+
if (bodyDrift) return "stale-body";
|
|
1545
|
+
if (fmDrift) return "stale-frontmatter";
|
|
1546
|
+
return "fresh";
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// kernel/sidecar/discover-orphans.ts
|
|
1550
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
|
|
1551
|
+
import { join as join3, relative as relative3, sep as sep2 } from "path";
|
|
1552
|
+
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
1553
|
+
const out = [];
|
|
1554
|
+
for (const root of roots) {
|
|
1555
|
+
walk(root, root, shouldSkip ?? (() => false), out);
|
|
1729
1556
|
}
|
|
1730
|
-
return
|
|
1557
|
+
return out;
|
|
1731
1558
|
}
|
|
1732
|
-
function
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1559
|
+
function walk(root, current, shouldSkip, out) {
|
|
1560
|
+
let entries;
|
|
1561
|
+
try {
|
|
1562
|
+
entries = readdirSync2(current, { withFileTypes: true, encoding: "utf8" });
|
|
1563
|
+
} catch {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
for (const entry of entries) {
|
|
1567
|
+
const full = join3(current, entry.name);
|
|
1568
|
+
const rel = relative3(root, full).split(sep2).join("/");
|
|
1569
|
+
if (shouldSkip(rel)) continue;
|
|
1570
|
+
if (entry.isSymbolicLink()) continue;
|
|
1571
|
+
if (entry.isDirectory()) {
|
|
1572
|
+
walk(root, full, shouldSkip, out);
|
|
1573
|
+
continue;
|
|
1746
1574
|
}
|
|
1575
|
+
if (!entry.isFile()) continue;
|
|
1576
|
+
if (!entry.name.endsWith(".sm")) continue;
|
|
1577
|
+
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
1578
|
+
if (existsSync4(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
1579
|
+
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
1747
1580
|
}
|
|
1748
|
-
return ops;
|
|
1749
1581
|
}
|
|
1750
|
-
function
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
const matches = [];
|
|
1756
|
-
for (const fromPath of opts.deletedPaths) {
|
|
1757
|
-
if (opts.claimedDeleted.has(fromPath)) continue;
|
|
1758
|
-
const fromNode = opts.priorByPath.get(fromPath);
|
|
1759
|
-
if (toNode.frontmatterHash === fromNode.frontmatterHash) {
|
|
1760
|
-
matches.push(fromPath);
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
if (matches.length > 0) candidatesByNew.set(toPath, matches);
|
|
1582
|
+
function safeIsFile(path) {
|
|
1583
|
+
try {
|
|
1584
|
+
return statSync(path).isFile();
|
|
1585
|
+
} catch {
|
|
1586
|
+
return false;
|
|
1764
1587
|
}
|
|
1765
|
-
return candidatesByNew;
|
|
1766
1588
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1589
|
+
|
|
1590
|
+
// kernel/sidecar/store.ts
|
|
1591
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
1592
|
+
import { dirname as dirname5, resolve as resolve9 } from "path";
|
|
1593
|
+
import { createRequire as createRequire4 } from "module";
|
|
1594
|
+
import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
|
|
1595
|
+
import yaml3 from "js-yaml";
|
|
1596
|
+
|
|
1597
|
+
// core/config/helper.ts
|
|
1598
|
+
import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
|
|
1599
|
+
|
|
1600
|
+
// kernel/config/loader.ts
|
|
1601
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
1602
|
+
|
|
1603
|
+
// kernel/util/skill-map-paths.ts
|
|
1604
|
+
import { join as join5 } from "path";
|
|
1605
|
+
|
|
1606
|
+
// core/paths/db-path.ts
|
|
1607
|
+
import { join as join4, resolve as resolve7 } from "path";
|
|
1608
|
+
var SKILL_MAP_DIR = ".skill-map";
|
|
1609
|
+
var DB_FILENAME = "skill-map.db";
|
|
1610
|
+
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
1611
|
+
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
1612
|
+
var GITIGNORE_ENTRIES = [
|
|
1613
|
+
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
1614
|
+
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
1615
|
+
];
|
|
1616
|
+
|
|
1617
|
+
// core/config/atomic-write.ts
|
|
1618
|
+
import {
|
|
1619
|
+
existsSync as existsSync6,
|
|
1620
|
+
mkdirSync,
|
|
1621
|
+
readFileSync as readFileSync7,
|
|
1622
|
+
renameSync,
|
|
1623
|
+
unlinkSync,
|
|
1624
|
+
writeFileSync
|
|
1625
|
+
} from "fs";
|
|
1626
|
+
import { dirname as dirname4 } from "path";
|
|
1627
|
+
|
|
1628
|
+
// kernel/orchestrator/node-build.ts
|
|
1629
|
+
import { createHash } from "crypto";
|
|
1630
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1631
|
+
import { isAbsolute as isAbsolute3, resolve as resolvePath } from "path";
|
|
1632
|
+
import "js-tiktoken/lite";
|
|
1633
|
+
import yaml4 from "js-yaml";
|
|
1634
|
+
|
|
1635
|
+
// kernel/orchestrator/frontmatter.ts
|
|
1636
|
+
function validateFrontmatter(providerFrontmatter, provider, kind, frontmatter, path, strict) {
|
|
1637
|
+
const result = providerFrontmatter.validate(provider, kind, frontmatter);
|
|
1638
|
+
if (result.ok) return null;
|
|
1639
|
+
return {
|
|
1640
|
+
analyzerId: "frontmatter-invalid",
|
|
1641
|
+
severity: strict ? "error" : "warn",
|
|
1642
|
+
nodeIds: [path],
|
|
1643
|
+
message: tx(ORCHESTRATOR_TEXTS.frontmatterInvalid, { path, kind, errors: result.errors }),
|
|
1644
|
+
data: { kind, errors: result.errors }
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function detectMalformedFrontmatter(body, path, strict) {
|
|
1648
|
+
const hint = classifyMalformedFrontmatter(body);
|
|
1649
|
+
if (!hint) return null;
|
|
1650
|
+
return {
|
|
1651
|
+
analyzerId: "frontmatter-malformed",
|
|
1652
|
+
severity: strict ? "error" : "warn",
|
|
1653
|
+
nodeIds: [path],
|
|
1654
|
+
message: malformedMessage(hint, path),
|
|
1655
|
+
data: { hint }
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
function classifyMalformedFrontmatter(body) {
|
|
1659
|
+
if (body.startsWith("\uFEFF")) {
|
|
1660
|
+
if (/^---\r?\n[\s\S]*?[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1661
|
+
return "byte-order-mark";
|
|
1786
1662
|
}
|
|
1787
1663
|
}
|
|
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
|
-
});
|
|
1664
|
+
if (/^[ \t]+---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1665
|
+
return "paste-with-indent";
|
|
1666
|
+
}
|
|
1667
|
+
if (/^---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
1668
|
+
const hasCloseFence = /\r?\n---(?:\r?\n|$)/.test(body);
|
|
1669
|
+
if (!hasCloseFence) {
|
|
1670
|
+
return "missing-close";
|
|
1804
1671
|
}
|
|
1805
1672
|
}
|
|
1673
|
+
return null;
|
|
1806
1674
|
}
|
|
1807
|
-
function
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
data: { path: fromPath }
|
|
1816
|
-
});
|
|
1675
|
+
function malformedMessage(hint, path) {
|
|
1676
|
+
switch (hint) {
|
|
1677
|
+
case "paste-with-indent":
|
|
1678
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedPasteWithIndent, { path });
|
|
1679
|
+
case "byte-order-mark":
|
|
1680
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedByteOrderMark, { path });
|
|
1681
|
+
case "missing-close":
|
|
1682
|
+
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
|
|
1817
1683
|
}
|
|
1818
1684
|
}
|
|
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
|
-
}
|
|
1685
|
+
|
|
1686
|
+
// kernel/orchestrator/node-build.ts
|
|
1860
1687
|
function buildNode(args) {
|
|
1861
1688
|
const bytesFrontmatter = Buffer.byteLength(args.frontmatterRaw, "utf8");
|
|
1862
1689
|
const bytesBody = Buffer.byteLength(args.body, "utf8");
|
|
@@ -1943,171 +1770,72 @@ function resolveSidecarOverlay(relativePath, nodePathForIssue, roots, liveBodyHa
|
|
|
1943
1770
|
storedBodyHash: result.parsed.identityBodyHash,
|
|
1944
1771
|
storedFrontmatterHash: result.parsed.identityFrontmatterHash,
|
|
1945
1772
|
liveBodyHash,
|
|
1946
|
-
liveFrontmatterHash
|
|
1947
|
-
});
|
|
1948
|
-
return {
|
|
1949
|
-
// R15 closure (2026-05-07) — surface the full parsed root on the
|
|
1950
|
-
// overlay so BFF consumers (UI inspector audit / plugin-contributions
|
|
1951
|
-
// / debug panels) can read `for.*`, `audit.*`, `settings.*`, and
|
|
1952
|
-
// plugin-namespaced sub-keys without re-reading the file. The
|
|
1953
|
-
// `annotations` field above stays — it duplicates `root.annotations`
|
|
1954
|
-
// by design so existing consumers keep working unchanged.
|
|
1955
|
-
overlay: {
|
|
1956
|
-
present: true,
|
|
1957
|
-
status,
|
|
1958
|
-
annotations: result.parsed.annotations,
|
|
1959
|
-
root: result.parsed.raw
|
|
1960
|
-
},
|
|
1961
|
-
issues,
|
|
1962
|
-
parsedRoot: result.parsed.raw
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
function resolveAbsoluteMdPath(relativePath, roots) {
|
|
1966
|
-
if (isAbsolute3(relativePath)) {
|
|
1967
|
-
return existsSync8(relativePath) ? relativePath : null;
|
|
1968
|
-
}
|
|
1969
|
-
for (const root of roots) {
|
|
1970
|
-
const candidate = resolvePath(root, relativePath);
|
|
1971
|
-
if (existsSync8(candidate)) return candidate;
|
|
1972
|
-
}
|
|
1973
|
-
return null;
|
|
1974
|
-
}
|
|
1975
|
-
function relativePathFromRoots(absolutePath, roots) {
|
|
1976
|
-
for (const root of roots) {
|
|
1977
|
-
const abs = resolvePath(root);
|
|
1978
|
-
if (absolutePath.startsWith(`${abs}/`) || absolutePath.startsWith(`${abs}\\`)) {
|
|
1979
|
-
return absolutePath.slice(abs.length + 1).split(/[\\/]/).join("/");
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
return absolutePath;
|
|
1983
|
-
}
|
|
1984
|
-
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
1985
|
-
const scope = extractor.scope;
|
|
1986
|
-
return {
|
|
1987
|
-
node,
|
|
1988
|
-
body: scope === "frontmatter" ? "" : body,
|
|
1989
|
-
frontmatter: scope === "body" ? {} : frontmatter,
|
|
1990
|
-
emitLink,
|
|
1991
|
-
enrichNode,
|
|
1992
|
-
emitContribution,
|
|
1993
|
-
...store !== void 0 ? { store } : {}
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
|
-
function validateLink(extractor, link, emitter) {
|
|
1997
|
-
if (!extractor.emitsLinkKinds.includes(link.kind)) {
|
|
1998
|
-
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
1999
|
-
emitter.emit(
|
|
2000
|
-
makeEvent("extension.error", {
|
|
2001
|
-
kind: "link-kind-not-declared",
|
|
2002
|
-
extensionId: qualifiedId,
|
|
2003
|
-
linkKind: link.kind,
|
|
2004
|
-
declaredKinds: extractor.emitsLinkKinds,
|
|
2005
|
-
link: { source: link.source, target: link.target, kind: link.kind },
|
|
2006
|
-
message: tx(ORCHESTRATOR_TEXTS.extensionErrorLinkKindNotDeclared, {
|
|
2007
|
-
extractorId: qualifiedId,
|
|
2008
|
-
linkKind: link.kind,
|
|
2009
|
-
declaredKinds: extractor.emitsLinkKinds.join(", ")
|
|
2010
|
-
})
|
|
2011
|
-
})
|
|
2012
|
-
);
|
|
2013
|
-
return null;
|
|
2014
|
-
}
|
|
2015
|
-
const confidence = link.confidence ?? extractor.defaultConfidence;
|
|
2016
|
-
return { ...link, confidence };
|
|
2017
|
-
}
|
|
2018
|
-
function validateFrontmatter(providerFrontmatter, provider, kind, frontmatter, path, strict) {
|
|
2019
|
-
const result = providerFrontmatter.validate(provider, kind, frontmatter);
|
|
2020
|
-
if (result.ok) return null;
|
|
2021
|
-
return {
|
|
2022
|
-
analyzerId: "frontmatter-invalid",
|
|
2023
|
-
severity: strict ? "error" : "warn",
|
|
2024
|
-
nodeIds: [path],
|
|
2025
|
-
message: tx(ORCHESTRATOR_TEXTS.frontmatterInvalid, { path, kind, errors: result.errors }),
|
|
2026
|
-
data: { kind, errors: result.errors }
|
|
2027
|
-
};
|
|
2028
|
-
}
|
|
2029
|
-
function detectMalformedFrontmatter(body, path, strict) {
|
|
2030
|
-
const hint = classifyMalformedFrontmatter(body);
|
|
2031
|
-
if (!hint) return null;
|
|
2032
|
-
return {
|
|
2033
|
-
analyzerId: "frontmatter-malformed",
|
|
2034
|
-
severity: strict ? "error" : "warn",
|
|
2035
|
-
nodeIds: [path],
|
|
2036
|
-
message: malformedMessage(hint, path),
|
|
2037
|
-
data: { hint }
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
function classifyMalformedFrontmatter(body) {
|
|
2041
|
-
if (body.startsWith("\uFEFF")) {
|
|
2042
|
-
if (/^---\r?\n[\s\S]*?[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
2043
|
-
return "byte-order-mark";
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
if (/^[ \t]+---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
2047
|
-
return "paste-with-indent";
|
|
2048
|
-
}
|
|
2049
|
-
if (/^---\r?\n[ \t]*[A-Za-z0-9_-]+\s*:/.test(body)) {
|
|
2050
|
-
const hasCloseFence = /\r?\n---(?:\r?\n|$)/.test(body);
|
|
2051
|
-
if (!hasCloseFence) {
|
|
2052
|
-
return "missing-close";
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
return null;
|
|
2056
|
-
}
|
|
2057
|
-
function malformedMessage(hint, path) {
|
|
2058
|
-
switch (hint) {
|
|
2059
|
-
case "paste-with-indent":
|
|
2060
|
-
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedPasteWithIndent, { path });
|
|
2061
|
-
case "byte-order-mark":
|
|
2062
|
-
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedByteOrderMark, { path });
|
|
2063
|
-
case "missing-close":
|
|
2064
|
-
return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
function validateIssue(analyzer, issue, emitter) {
|
|
2068
|
-
const severity = issue.severity;
|
|
2069
|
-
if (severity !== "error" && severity !== "warn" && severity !== "info") {
|
|
2070
|
-
const qualifiedId = `${analyzer.pluginId}/${analyzer.id}`;
|
|
2071
|
-
emitter.emit(
|
|
2072
|
-
makeEvent("extension.error", {
|
|
2073
|
-
kind: "issue-invalid-severity",
|
|
2074
|
-
extensionId: qualifiedId,
|
|
2075
|
-
severity,
|
|
2076
|
-
issue: { analyzerId: issue.analyzerId || analyzer.id, message: issue.message, nodeIds: issue.nodeIds },
|
|
2077
|
-
message: tx(ORCHESTRATOR_TEXTS.extensionErrorIssueInvalidSeverity, {
|
|
2078
|
-
analyzerId: qualifiedId,
|
|
2079
|
-
severity: JSON.stringify(severity)
|
|
2080
|
-
})
|
|
2081
|
-
})
|
|
2082
|
-
);
|
|
2083
|
-
return null;
|
|
2084
|
-
}
|
|
2085
|
-
return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
|
|
1773
|
+
liveFrontmatterHash
|
|
1774
|
+
});
|
|
1775
|
+
return {
|
|
1776
|
+
// R15 closure (2026-05-07) — surface the full parsed root on the
|
|
1777
|
+
// overlay so BFF consumers (UI inspector audit / plugin-contributions
|
|
1778
|
+
// / debug panels) can read `for.*`, `audit.*`, `settings.*`, and
|
|
1779
|
+
// plugin-namespaced sub-keys without re-reading the file. The
|
|
1780
|
+
// `annotations` field above stays — it duplicates `root.annotations`
|
|
1781
|
+
// by design so existing consumers keep working unchanged.
|
|
1782
|
+
overlay: {
|
|
1783
|
+
present: true,
|
|
1784
|
+
status,
|
|
1785
|
+
annotations: result.parsed.annotations,
|
|
1786
|
+
root: result.parsed.raw
|
|
1787
|
+
},
|
|
1788
|
+
issues,
|
|
1789
|
+
parsedRoot: result.parsed.raw
|
|
1790
|
+
};
|
|
2086
1791
|
}
|
|
2087
|
-
function
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
node.linksOutCount = 0;
|
|
2091
|
-
node.linksInCount = 0;
|
|
2092
|
-
byPath2.set(node.path, node);
|
|
1792
|
+
function resolveAbsoluteMdPath(relativePath, roots) {
|
|
1793
|
+
if (isAbsolute3(relativePath)) {
|
|
1794
|
+
return existsSync8(relativePath) ? relativePath : null;
|
|
2093
1795
|
}
|
|
2094
|
-
for (const
|
|
2095
|
-
const
|
|
2096
|
-
if (
|
|
2097
|
-
const target = byPath2.get(link.target);
|
|
2098
|
-
if (target) target.linksInCount += 1;
|
|
1796
|
+
for (const root of roots) {
|
|
1797
|
+
const candidate = resolvePath(root, relativePath);
|
|
1798
|
+
if (existsSync8(candidate)) return candidate;
|
|
2099
1799
|
}
|
|
1800
|
+
return null;
|
|
2100
1801
|
}
|
|
2101
|
-
function
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2104
|
-
if (
|
|
2105
|
-
|
|
1802
|
+
function relativePathFromRoots(absolutePath, roots) {
|
|
1803
|
+
for (const root of roots) {
|
|
1804
|
+
const abs = resolvePath(root);
|
|
1805
|
+
if (absolutePath.startsWith(`${abs}/`) || absolutePath.startsWith(`${abs}\\`)) {
|
|
1806
|
+
return absolutePath.slice(abs.length + 1).split(/[\\/]/).join("/");
|
|
1807
|
+
}
|
|
2106
1808
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
1809
|
+
return absolutePath;
|
|
1810
|
+
}
|
|
1811
|
+
function buildFreshNodeAndValidateFrontmatter(opts) {
|
|
1812
|
+
const node = buildNode({
|
|
1813
|
+
path: opts.raw.path,
|
|
1814
|
+
kind: opts.kind,
|
|
1815
|
+
providerId: opts.provider.id,
|
|
1816
|
+
frontmatterRaw: opts.raw.frontmatterRaw,
|
|
1817
|
+
body: opts.raw.body,
|
|
1818
|
+
frontmatter: opts.raw.frontmatter,
|
|
1819
|
+
bodyHash: opts.bodyHash,
|
|
1820
|
+
frontmatterHash: opts.frontmatterHash,
|
|
1821
|
+
encoder: opts.encoder
|
|
1822
|
+
});
|
|
1823
|
+
const frontmatterIssues = [];
|
|
1824
|
+
if (opts.raw.frontmatterRaw.length > 0) {
|
|
1825
|
+
const fmIssue = validateFrontmatter(
|
|
1826
|
+
opts.providerFrontmatter,
|
|
1827
|
+
opts.provider,
|
|
1828
|
+
opts.kind,
|
|
1829
|
+
opts.raw.frontmatter,
|
|
1830
|
+
opts.raw.path,
|
|
1831
|
+
opts.strict
|
|
1832
|
+
);
|
|
1833
|
+
if (fmIssue) frontmatterIssues.push(fmIssue);
|
|
1834
|
+
} else {
|
|
1835
|
+
const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
|
|
1836
|
+
if (malformed) frontmatterIssues.push(malformed);
|
|
2110
1837
|
}
|
|
1838
|
+
return { node, frontmatterIssues };
|
|
2111
1839
|
}
|
|
2112
1840
|
function mergeNodeWithEnrichments(node, enrichments, opts = {}) {
|
|
2113
1841
|
const includeStale = opts.includeStale === true;
|
|
@@ -2127,11 +1855,400 @@ function assignSafe(target, source) {
|
|
|
2127
1855
|
}
|
|
2128
1856
|
}
|
|
2129
1857
|
|
|
1858
|
+
// kernel/orchestrator/walk.ts
|
|
1859
|
+
async function walkAndExtract(opts) {
|
|
1860
|
+
const accum = createWalkAccumulators();
|
|
1861
|
+
const wctx = buildWalkContext(opts);
|
|
1862
|
+
const claimedPaths = /* @__PURE__ */ new Set();
|
|
1863
|
+
const walkOptions = opts.ignoreFilter ? { ignoreFilter: opts.ignoreFilter } : {};
|
|
1864
|
+
let filesWalked = 0;
|
|
1865
|
+
let index = 0;
|
|
1866
|
+
for (const provider of opts.providers) {
|
|
1867
|
+
for await (const raw of resolveProviderWalk(provider)(opts.roots, walkOptions)) {
|
|
1868
|
+
filesWalked += 1;
|
|
1869
|
+
if (claimedPaths.has(raw.path)) continue;
|
|
1870
|
+
const advanced = await processRawNode(raw, provider, wctx, accum, claimedPaths, index + 1);
|
|
1871
|
+
if (advanced) index += 1;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const orphanSidecars = discoverOrphanSidecars(opts.roots);
|
|
1875
|
+
return {
|
|
1876
|
+
nodes: accum.nodes,
|
|
1877
|
+
internalLinks: accum.internalLinks,
|
|
1878
|
+
externalLinks: accum.externalLinks,
|
|
1879
|
+
cachedPaths: accum.cachedPaths,
|
|
1880
|
+
frontmatterIssues: accum.frontmatterIssues,
|
|
1881
|
+
filesWalked,
|
|
1882
|
+
enrichments: [...accum.enrichmentBuffer.values()],
|
|
1883
|
+
extractorRuns: accum.extractorRuns,
|
|
1884
|
+
contributions: accum.contributionsBuffer,
|
|
1885
|
+
freshlyRunTuples: accum.freshlyRunTuples,
|
|
1886
|
+
orphanSidecars,
|
|
1887
|
+
sidecarRoots: accum.sidecarRoots
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function createWalkAccumulators() {
|
|
1891
|
+
return {
|
|
1892
|
+
nodes: [],
|
|
1893
|
+
internalLinks: [],
|
|
1894
|
+
externalLinks: [],
|
|
1895
|
+
cachedPaths: /* @__PURE__ */ new Set(),
|
|
1896
|
+
frontmatterIssues: [],
|
|
1897
|
+
enrichmentBuffer: /* @__PURE__ */ new Map(),
|
|
1898
|
+
contributionsBuffer: [],
|
|
1899
|
+
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
1900
|
+
extractorRuns: [],
|
|
1901
|
+
sidecarRoots: /* @__PURE__ */ new Map()
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function buildWalkContext(opts) {
|
|
1905
|
+
const { priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode } = opts.priorIndex;
|
|
1906
|
+
const shortIdToQualified = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const ex of opts.extractors) {
|
|
1908
|
+
const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
|
|
1909
|
+
const list = shortIdToQualified.get(ex.id);
|
|
1910
|
+
if (list) list.push(qualified);
|
|
1911
|
+
else shortIdToQualified.set(ex.id, [qualified]);
|
|
1912
|
+
}
|
|
1913
|
+
return { opts, priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode, shortIdToQualified };
|
|
1914
|
+
}
|
|
1915
|
+
async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextIndex) {
|
|
1916
|
+
const bodyHash = sha256(raw.body);
|
|
1917
|
+
const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
|
|
1918
|
+
const kind = provider.classify(raw.path, raw.frontmatter);
|
|
1919
|
+
if (kind === null) {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
claimedPaths.add(raw.path);
|
|
1923
|
+
const priorNode = wctx.priorNodesByPath.get(raw.path);
|
|
1924
|
+
const nodeHashCacheEligible = wctx.opts.enableCache && wctx.opts.prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
|
|
1925
|
+
const sidecarResolution = resolveSidecarOverlay(
|
|
1926
|
+
raw.path,
|
|
1927
|
+
raw.path,
|
|
1928
|
+
wctx.opts.roots,
|
|
1929
|
+
bodyHash,
|
|
1930
|
+
frontmatterHash
|
|
1931
|
+
);
|
|
1932
|
+
const sidecarAnnotationsHash = sha256(
|
|
1933
|
+
canonicalSidecarAnnotations(sidecarResolution.overlay.annotations)
|
|
1934
|
+
);
|
|
1935
|
+
const cacheDecision = computeCacheDecision({
|
|
1936
|
+
extractors: wctx.opts.extractors,
|
|
1937
|
+
kind,
|
|
1938
|
+
nodePath: raw.path,
|
|
1939
|
+
bodyHash,
|
|
1940
|
+
sidecarAnnotationsHash,
|
|
1941
|
+
nodeHashCacheEligible,
|
|
1942
|
+
priorExtractorRuns: wctx.opts.priorExtractorRuns
|
|
1943
|
+
});
|
|
1944
|
+
const ctx = {
|
|
1945
|
+
raw,
|
|
1946
|
+
provider,
|
|
1947
|
+
kind,
|
|
1948
|
+
bodyHash,
|
|
1949
|
+
frontmatterHash,
|
|
1950
|
+
sidecarResolution,
|
|
1951
|
+
sidecarAnnotationsHash,
|
|
1952
|
+
nodeHashCacheEligible,
|
|
1953
|
+
cacheDecision,
|
|
1954
|
+
priorNode,
|
|
1955
|
+
index: nextIndex
|
|
1956
|
+
};
|
|
1957
|
+
if (cacheDecision.fullCacheHit && priorNode) {
|
|
1958
|
+
applyFullCacheHit(ctx, wctx, accum);
|
|
1959
|
+
} else {
|
|
1960
|
+
await applyExtractPath(ctx, wctx, accum);
|
|
1961
|
+
}
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
function attachSidecar(node, resolution, sidecarRoots) {
|
|
1965
|
+
node.sidecar = resolution.overlay;
|
|
1966
|
+
if (resolution.parsedRoot !== null) {
|
|
1967
|
+
sidecarRoots.set(node.path, resolution.parsedRoot);
|
|
1968
|
+
}
|
|
1969
|
+
return resolution.issues.map(
|
|
1970
|
+
(i) => i.nodeIds.length > 0 ? i : { ...i, nodeIds: [node.path] }
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
function applyFullCacheHit(ctx, wctx, accum) {
|
|
1974
|
+
const reused = reusePriorNode({
|
|
1975
|
+
priorNode: ctx.priorNode,
|
|
1976
|
+
bodyHash: ctx.bodyHash,
|
|
1977
|
+
sidecarAnnotationsHash: ctx.sidecarAnnotationsHash,
|
|
1978
|
+
strict: wctx.opts.strict,
|
|
1979
|
+
cachedQualifiedIds: ctx.cacheDecision.cachedQualifiedIds,
|
|
1980
|
+
applicableQualifiedIds: ctx.cacheDecision.applicableQualifiedIds,
|
|
1981
|
+
shortIdToQualified: wctx.shortIdToQualified,
|
|
1982
|
+
priorLinksByOriginating: wctx.priorLinksByOriginating,
|
|
1983
|
+
priorFrontmatterIssuesByNode: wctx.priorFrontmatterIssuesByNode
|
|
1984
|
+
});
|
|
1985
|
+
const reusedSidecarIssues = attachSidecar(reused.node, ctx.sidecarResolution, accum.sidecarRoots);
|
|
1986
|
+
accum.nodes.push(reused.node);
|
|
1987
|
+
accum.cachedPaths.add(reused.node.path);
|
|
1988
|
+
for (const link of reused.internalLinks) accum.internalLinks.push(link);
|
|
1989
|
+
for (const issue of reused.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
1990
|
+
for (const issue of reusedSidecarIssues) accum.frontmatterIssues.push(issue);
|
|
1991
|
+
for (const run of reused.extractorRuns) accum.extractorRuns.push(run);
|
|
1992
|
+
wctx.opts.emitter.emit(makeEvent("scan.progress", {
|
|
1993
|
+
index: ctx.index,
|
|
1994
|
+
path: ctx.raw.path,
|
|
1995
|
+
kind: ctx.kind,
|
|
1996
|
+
cached: true
|
|
1997
|
+
}));
|
|
1998
|
+
}
|
|
1999
|
+
async function applyExtractPath(ctx, wctx, accum) {
|
|
2000
|
+
const node = buildOrReuseNode(ctx, wctx, accum);
|
|
2001
|
+
const sidecarIssues = attachSidecar(node, ctx.sidecarResolution, accum.sidecarRoots);
|
|
2002
|
+
for (const issue of sidecarIssues) accum.frontmatterIssues.push(issue);
|
|
2003
|
+
const partialCacheHit = isPartialCacheHit(ctx);
|
|
2004
|
+
emitExtractProgress(ctx, wctx, partialCacheHit);
|
|
2005
|
+
const extractorsToRun = partialCacheHit ? ctx.cacheDecision.missingExtractors : ctx.cacheDecision.applicableExtractors;
|
|
2006
|
+
recordFreshlyRunTuples(extractorsToRun, node.path, accum);
|
|
2007
|
+
const extractResult = await runExtractorsForNode({
|
|
2008
|
+
extractors: extractorsToRun,
|
|
2009
|
+
node,
|
|
2010
|
+
body: ctx.raw.body,
|
|
2011
|
+
frontmatter: ctx.raw.frontmatter,
|
|
2012
|
+
bodyHash: ctx.bodyHash,
|
|
2013
|
+
emitter: wctx.opts.emitter,
|
|
2014
|
+
...wctx.opts.pluginStores ? { pluginStores: wctx.opts.pluginStores } : {}
|
|
2015
|
+
});
|
|
2016
|
+
mergeExtractResult(extractResult, accum);
|
|
2017
|
+
recordExtractorRuns(node.path, ctx, accum);
|
|
2018
|
+
}
|
|
2019
|
+
function emitExtractProgress(ctx, wctx, partialCacheHit) {
|
|
2020
|
+
wctx.opts.emitter.emit(makeEvent("scan.progress", {
|
|
2021
|
+
index: ctx.index,
|
|
2022
|
+
path: ctx.raw.path,
|
|
2023
|
+
kind: ctx.kind,
|
|
2024
|
+
cached: false,
|
|
2025
|
+
...partialCacheHit ? { partialCache: true } : {}
|
|
2026
|
+
}));
|
|
2027
|
+
}
|
|
2028
|
+
function recordFreshlyRunTuples(extractors, nodePath, accum) {
|
|
2029
|
+
for (const ex of extractors) {
|
|
2030
|
+
accum.freshlyRunTuples.add(`${ex.pluginId}\0${ex.id}\0${nodePath}`);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
function mergeExtractResult(extractResult, accum) {
|
|
2034
|
+
for (const link of extractResult.internalLinks) accum.internalLinks.push(link);
|
|
2035
|
+
for (const link of extractResult.externalLinks) accum.externalLinks.push(link);
|
|
2036
|
+
for (const enr of extractResult.enrichments) {
|
|
2037
|
+
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
2038
|
+
}
|
|
2039
|
+
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
2040
|
+
}
|
|
2041
|
+
function isPartialCacheHit(ctx) {
|
|
2042
|
+
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|
|
2043
|
+
}
|
|
2044
|
+
function buildOrReuseNode(ctx, wctx, accum) {
|
|
2045
|
+
if (isPartialCacheHit(ctx) && ctx.priorNode) {
|
|
2046
|
+
const partial = cloneNodeAndReshapeLinks({
|
|
2047
|
+
priorNode: ctx.priorNode,
|
|
2048
|
+
strict: wctx.opts.strict,
|
|
2049
|
+
cachedQualifiedIds: ctx.cacheDecision.cachedQualifiedIds,
|
|
2050
|
+
applicableQualifiedIds: ctx.cacheDecision.applicableQualifiedIds,
|
|
2051
|
+
shortIdToQualified: wctx.shortIdToQualified,
|
|
2052
|
+
priorLinksByOriginating: wctx.priorLinksByOriginating,
|
|
2053
|
+
priorFrontmatterIssuesByNode: wctx.priorFrontmatterIssuesByNode
|
|
2054
|
+
});
|
|
2055
|
+
for (const link of partial.internalLinks) accum.internalLinks.push(link);
|
|
2056
|
+
for (const issue of partial.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
2057
|
+
accum.nodes.push(partial.node);
|
|
2058
|
+
return partial.node;
|
|
2059
|
+
}
|
|
2060
|
+
const fresh = buildFreshNodeAndValidateFrontmatter({
|
|
2061
|
+
raw: ctx.raw,
|
|
2062
|
+
kind: ctx.kind,
|
|
2063
|
+
provider: ctx.provider,
|
|
2064
|
+
bodyHash: ctx.bodyHash,
|
|
2065
|
+
frontmatterHash: ctx.frontmatterHash,
|
|
2066
|
+
encoder: wctx.opts.encoder,
|
|
2067
|
+
providerFrontmatter: wctx.opts.providerFrontmatter,
|
|
2068
|
+
strict: wctx.opts.strict
|
|
2069
|
+
});
|
|
2070
|
+
accum.nodes.push(fresh.node);
|
|
2071
|
+
for (const issue of fresh.frontmatterIssues) accum.frontmatterIssues.push(issue);
|
|
2072
|
+
return fresh.node;
|
|
2073
|
+
}
|
|
2074
|
+
function recordExtractorRuns(nodePath, ctx, accum) {
|
|
2075
|
+
const ranAt = Date.now();
|
|
2076
|
+
for (const ex of ctx.cacheDecision.applicableExtractors) {
|
|
2077
|
+
accum.extractorRuns.push({
|
|
2078
|
+
nodePath,
|
|
2079
|
+
extractorId: qualifiedExtensionId(ex.pluginId, ex.id),
|
|
2080
|
+
bodyHashAtRun: ctx.bodyHash,
|
|
2081
|
+
ranAt,
|
|
2082
|
+
sidecarAnnotationsHashAtRun: ctx.sidecarAnnotationsHash
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// kernel/orchestrator/index.ts
|
|
2088
|
+
var SCANNED_BY = {
|
|
2089
|
+
name: "skill-map",
|
|
2090
|
+
version: package_default.version,
|
|
2091
|
+
specVersion: resolveSpecVersionSafe()
|
|
2092
|
+
};
|
|
2093
|
+
function resolveSpecVersionSafe() {
|
|
2094
|
+
try {
|
|
2095
|
+
return installedSpecVersion();
|
|
2096
|
+
} catch {
|
|
2097
|
+
return "unknown";
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
async function runScanWithRenames(_kernel, options) {
|
|
2101
|
+
return runScanInternal(_kernel, options);
|
|
2102
|
+
}
|
|
2103
|
+
async function runScan(_kernel, options) {
|
|
2104
|
+
const { result } = await runScanInternal(_kernel, options);
|
|
2105
|
+
return result;
|
|
2106
|
+
}
|
|
2107
|
+
async function runScanInternal(_kernel, options) {
|
|
2108
|
+
validateRoots(options.roots);
|
|
2109
|
+
const setup = buildScanSetup(options);
|
|
2110
|
+
const { emitter, exts, hookDispatcher, encoder, prior, start } = setup;
|
|
2111
|
+
const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
|
|
2112
|
+
emitter.emit(scanStartedEvent);
|
|
2113
|
+
await hookDispatcher.dispatch("scan.started", scanStartedEvent);
|
|
2114
|
+
const walked = await walkAndExtract({
|
|
2115
|
+
providers: exts.providers,
|
|
2116
|
+
extractors: exts.extractors,
|
|
2117
|
+
roots: options.roots,
|
|
2118
|
+
...options.ignoreFilter ? { ignoreFilter: options.ignoreFilter } : {},
|
|
2119
|
+
emitter,
|
|
2120
|
+
encoder,
|
|
2121
|
+
strict: setup.strict,
|
|
2122
|
+
enableCache: setup.enableCache,
|
|
2123
|
+
prior,
|
|
2124
|
+
priorIndex: setup.priorIndex,
|
|
2125
|
+
priorExtractorRuns: setup.priorExtractorRuns,
|
|
2126
|
+
providerFrontmatter: setup.providerFrontmatter,
|
|
2127
|
+
pluginStores: options.pluginStores
|
|
2128
|
+
});
|
|
2129
|
+
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
2130
|
+
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
2131
|
+
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
2132
|
+
const analyzerResult = await runAnalyzers(
|
|
2133
|
+
exts.analyzers,
|
|
2134
|
+
walked.nodes,
|
|
2135
|
+
walked.internalLinks,
|
|
2136
|
+
walked.orphanSidecars,
|
|
2137
|
+
walked.sidecarRoots,
|
|
2138
|
+
options.annotationContributions ?? [],
|
|
2139
|
+
options.viewContributions ?? [],
|
|
2140
|
+
options.orphanJobFiles ?? [],
|
|
2141
|
+
options.referenceablePaths,
|
|
2142
|
+
options.cwd,
|
|
2143
|
+
emitter,
|
|
2144
|
+
hookDispatcher
|
|
2145
|
+
);
|
|
2146
|
+
mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
|
|
2147
|
+
const issues = analyzerResult.issues;
|
|
2148
|
+
for (const issue of walked.frontmatterIssues) issues.push(issue);
|
|
2149
|
+
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
|
|
2150
|
+
const stats = buildScanStats(walked, issues, start);
|
|
2151
|
+
const scanCompletedEvent = makeEvent("scan.completed", { stats });
|
|
2152
|
+
emitter.emit(scanCompletedEvent);
|
|
2153
|
+
await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
|
|
2154
|
+
return buildScanReturn(walked, issues, renameOps, stats, options, setup);
|
|
2155
|
+
}
|
|
2156
|
+
function buildScanSetup(options) {
|
|
2157
|
+
const start = Date.now();
|
|
2158
|
+
const emitter = options.emitter ?? new InMemoryProgressEmitter();
|
|
2159
|
+
const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
|
|
2160
|
+
const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
|
|
2161
|
+
const tokenize = options.tokenize !== false;
|
|
2162
|
+
const encoder = tokenize ? new Tiktoken2(cl100k_base) : null;
|
|
2163
|
+
const prior = options.priorSnapshot ?? null;
|
|
2164
|
+
const priorIndex = indexPriorSnapshot(prior);
|
|
2165
|
+
const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
|
|
2166
|
+
return {
|
|
2167
|
+
start,
|
|
2168
|
+
scannedAt: start,
|
|
2169
|
+
emitter,
|
|
2170
|
+
exts,
|
|
2171
|
+
hookDispatcher,
|
|
2172
|
+
encoder,
|
|
2173
|
+
prior,
|
|
2174
|
+
priorIndex,
|
|
2175
|
+
priorExtractorRuns: options.priorExtractorRuns,
|
|
2176
|
+
providerFrontmatter,
|
|
2177
|
+
scope: options.scope ?? "project",
|
|
2178
|
+
strict: options.strict === true,
|
|
2179
|
+
enableCache: options.enableCache === true
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
async function dispatchExtractorCompleted(extractors, emitter, hookDispatcher) {
|
|
2183
|
+
for (const extractor of extractors) {
|
|
2184
|
+
const extractorId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
2185
|
+
const evt = makeEvent("extractor.completed", { extractorId });
|
|
2186
|
+
emitter.emit(evt);
|
|
2187
|
+
await hookDispatcher.dispatch("extractor.completed", evt);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function mergeAnalyzerEmissions(walked, analyzerResult, analyzers) {
|
|
2191
|
+
for (const c of analyzerResult.contributions) walked.contributions.push(c);
|
|
2192
|
+
for (const analyzer of analyzers ?? []) {
|
|
2193
|
+
if (analyzer.viewContributions === void 0) continue;
|
|
2194
|
+
for (const node of walked.nodes) {
|
|
2195
|
+
walked.freshlyRunTuples.add(`${analyzer.pluginId}\0${analyzer.id}\0${node.path}`);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function buildScanStats(walked, issues, start) {
|
|
2200
|
+
return {
|
|
2201
|
+
// `filesSkipped` is "files walked but not classified by any
|
|
2202
|
+
// Provider". Today every walked file IS classified by its Provider
|
|
2203
|
+
// (the `claude` Provider's `classify()` always returns a kind,
|
|
2204
|
+
// falling back to `'markdown'`), so this is always 0. Wired now
|
|
2205
|
+
// so the field shape is spec-conformant; meaningful once multiple
|
|
2206
|
+
// Providers compete.
|
|
2207
|
+
filesWalked: walked.filesWalked,
|
|
2208
|
+
filesSkipped: 0,
|
|
2209
|
+
nodesCount: walked.nodes.length,
|
|
2210
|
+
linksCount: walked.internalLinks.length,
|
|
2211
|
+
issuesCount: issues.length,
|
|
2212
|
+
durationMs: Date.now() - start
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
2216
|
+
return {
|
|
2217
|
+
result: {
|
|
2218
|
+
schemaVersion: 1,
|
|
2219
|
+
scannedAt: setup.scannedAt,
|
|
2220
|
+
scope: setup.scope,
|
|
2221
|
+
roots: options.roots,
|
|
2222
|
+
providers: setup.exts.providers.map((a) => a.id),
|
|
2223
|
+
scannedBy: SCANNED_BY,
|
|
2224
|
+
nodes: walked.nodes,
|
|
2225
|
+
links: walked.internalLinks,
|
|
2226
|
+
issues,
|
|
2227
|
+
stats
|
|
2228
|
+
},
|
|
2229
|
+
renameOps,
|
|
2230
|
+
extractorRuns: walked.extractorRuns,
|
|
2231
|
+
enrichments: walked.enrichments,
|
|
2232
|
+
contributions: walked.contributions,
|
|
2233
|
+
freshlyRunTuples: walked.freshlyRunTuples
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
function validateRoots(roots) {
|
|
2237
|
+
if (roots.length === 0) {
|
|
2238
|
+
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
2239
|
+
}
|
|
2240
|
+
for (const root of roots) {
|
|
2241
|
+
if (!existsSync9(root) || !statSync2(root).isDirectory()) {
|
|
2242
|
+
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2130
2247
|
// kernel/scan/watcher.ts
|
|
2131
|
-
import { resolve as
|
|
2248
|
+
import { resolve as resolve10, relative as relative4, sep as sep3 } from "path";
|
|
2132
2249
|
import chokidar from "chokidar";
|
|
2133
2250
|
function createChokidarWatcher(opts) {
|
|
2134
|
-
const absRoots = opts.roots.map((r) =>
|
|
2251
|
+
const absRoots = opts.roots.map((r) => resolve10(opts.cwd, r));
|
|
2135
2252
|
const ignoreFilterOpt = opts.ignoreFilter;
|
|
2136
2253
|
const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
|
|
2137
2254
|
const ignored = getFilter ? (path) => {
|