@skill-map/cli 0.20.1 → 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.
Files changed (37) hide show
  1. package/dist/cli/tutorial/sm-tutorial.md +93 -14
  2. package/dist/cli.js +7660 -6354
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +1244 -1065
  5. package/dist/index.js.map +1 -1
  6. package/dist/kernel/index.d.ts +300 -194
  7. package/dist/kernel/index.js +1244 -1065
  8. package/dist/kernel/index.js.map +1 -1
  9. package/dist/migrations/001_initial.sql +13 -0
  10. package/dist/ui/chunk-25AWRVIC.js +965 -0
  11. package/dist/ui/chunk-GETTEQ3S.js +123 -0
  12. package/dist/ui/{chunk-4NLC7QD2.js → chunk-GXRWH2VL.js} +1 -1
  13. package/dist/ui/chunk-HC6PNQMW.js +251 -0
  14. package/dist/ui/chunk-HJHWJTFH.js +1 -0
  15. package/dist/ui/chunk-MF2M6GYF.js +1 -0
  16. package/dist/ui/{chunk-EZZF5RL5.js → chunk-MPMBTIUR.js} +2 -2
  17. package/dist/ui/{chunk-6GUHSAP5.js → chunk-OPPQMCMQ.js} +1 -1
  18. package/dist/ui/chunk-V3SZQETX.js +61 -0
  19. package/dist/ui/{chunk-E4ALROJS.js → chunk-VVOEPDQD.js} +1 -1
  20. package/dist/ui/{chunk-6BZZQV42.js → chunk-W2EFGI3J.js} +1 -1
  21. package/dist/ui/index.html +2 -10
  22. package/dist/ui/main-Q2WC254P.js +2 -0
  23. package/dist/ui/media/fa-brands-400-AHOAZHCU.woff2 +0 -0
  24. package/dist/ui/media/fa-regular-400-VRZYIBIZ.woff2 +0 -0
  25. package/dist/ui/media/fa-solid-900-MDEYK55F.woff2 +0 -0
  26. package/dist/ui/media/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
  27. package/dist/ui/styles-M2FETVAG.css +1 -0
  28. package/migrations/001_initial.sql +13 -0
  29. package/package.json +6 -5
  30. package/dist/ui/chunk-FWX4RRDF.js +0 -125
  31. package/dist/ui/chunk-GGMXMGRJ.js +0 -1
  32. package/dist/ui/chunk-K5PULFK7.js +0 -1
  33. package/dist/ui/chunk-OJ6W6OIB.js +0 -61
  34. package/dist/ui/chunk-PTCD42GB.js +0 -247
  35. package/dist/ui/chunk-ZSRIBCAW.js +0 -965
  36. package/dist/ui/main-5FJWWH5I.js +0 -1
  37. package/dist/ui/styles-VJ5Q6D2X.css +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 { createHash } from "crypto";
97
- import { existsSync as existsSync6, statSync as statSync2 } from "fs";
98
- import { isAbsolute as isAbsolute2, 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.20.1",
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 typecheck && npm run lint && npm run build && npm run test:ci && npm run reference:check",
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": "tsc --noEmit && node --import tsx --test 'test/**/*.test.ts' 'built-in-plugins/**/*.test.ts' 'kernel/**/*.test.ts' 'server/**/*.test.ts'",
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.20.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,174 +204,6 @@ var package_default = {
206
204
  }
207
205
  };
208
206
 
209
- // kernel/sidecar/parse.ts
210
- import { existsSync, readFileSync } from "fs";
211
- import { dirname, resolve } from "path";
212
- import { createRequire } from "module";
213
- import { Ajv2020 } from "ajv/dist/2020.js";
214
- import yaml from "js-yaml";
215
-
216
- // kernel/util/ajv-interop.ts
217
- import addFormatsModule from "ajv-formats";
218
- var addFormats = addFormatsModule.default ?? addFormatsModule;
219
- function applyAjvFormats(ajv) {
220
- addFormats(ajv);
221
- }
222
-
223
- // kernel/sidecar/parse.ts
224
- function readSidecarFor(mdAbsolutePath) {
225
- const sidecarPath = sidecarPathFor(mdAbsolutePath);
226
- if (!existsSync(sidecarPath)) {
227
- return { parsed: null, present: false, issues: [] };
228
- }
229
- let raw;
230
- try {
231
- raw = readFileSync(sidecarPath, "utf8");
232
- } catch (err) {
233
- return {
234
- parsed: null,
235
- present: true,
236
- issues: [{ message: `cannot read ${sidecarPath}: ${err.message}` }]
237
- };
238
- }
239
- let parsedYaml;
240
- try {
241
- parsedYaml = yaml.load(raw);
242
- } catch (err) {
243
- return {
244
- parsed: null,
245
- present: true,
246
- issues: [{ message: `malformed YAML in ${sidecarPath}: ${err.message}` }]
247
- };
248
- }
249
- if (!isPlainObject(parsedYaml)) {
250
- return {
251
- parsed: null,
252
- present: true,
253
- issues: [{ message: `sidecar root must be a YAML mapping at ${sidecarPath}` }]
254
- };
255
- }
256
- const sidecarValidator = getSidecarValidator();
257
- if (!sidecarValidator(parsedYaml)) {
258
- const errors = (sidecarValidator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
259
- return {
260
- parsed: null,
261
- present: true,
262
- issues: [{ message: `sidecar schema validation failed at ${sidecarPath}: ${errors}` }]
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;
269
- return {
270
- parsed: {
271
- filePath: sidecarPath,
272
- identityBodyHash: String(identityBlock["bodyHash"]),
273
- identityFrontmatterHash: String(identityBlock["frontmatterHash"]),
274
- identityPath: String(identityBlock["path"]),
275
- annotations,
276
- raw: root
277
- },
278
- present: true,
279
- issues: []
280
- };
281
- }
282
- function sidecarPathFor(mdAbsolutePath) {
283
- if (mdAbsolutePath.endsWith(".md")) {
284
- return `${mdAbsolutePath.slice(0, -".md".length)}.sm`;
285
- }
286
- return `${mdAbsolutePath}.sm`;
287
- }
288
- function isPlainObject(value) {
289
- return value !== null && typeof value === "object" && !Array.isArray(value);
290
- }
291
- var cachedSidecarValidator = null;
292
- function getSidecarValidator() {
293
- if (cachedSidecarValidator) return cachedSidecarValidator;
294
- const ajv = new Ajv2020({ strict: false, allErrors: true, allowUnionTypes: true });
295
- applyAjvFormats(ajv);
296
- const specRoot = resolveSpecRoot();
297
- const annotationsSchema = JSON.parse(
298
- readFileSync(resolve(specRoot, "schemas/annotations.schema.json"), "utf8")
299
- );
300
- const sidecarSchema = JSON.parse(
301
- readFileSync(resolve(specRoot, "schemas/sidecar.schema.json"), "utf8")
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;
338
- }
339
- function walk(root, current, shouldSkip, out) {
340
- let entries;
341
- try {
342
- entries = readdirSync(current, { withFileTypes: true, encoding: "utf8" });
343
- } catch {
344
- return;
345
- }
346
- for (const entry of entries) {
347
- const full = join(current, entry.name);
348
- const rel = relative(root, full).split(sep).join("/");
349
- if (shouldSkip(rel)) continue;
350
- if (entry.isSymbolicLink()) continue;
351
- if (entry.isDirectory()) {
352
- walk(root, full, shouldSkip, out);
353
- continue;
354
- }
355
- if (!entry.isFile()) continue;
356
- if (!entry.name.endsWith(".sm")) continue;
357
- const expectedMd = `${full.slice(0, -".sm".length)}.md`;
358
- if (existsSync2(expectedMd) && safeIsFile(expectedMd)) continue;
359
- out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
360
- }
361
- }
362
- function safeIsFile(path) {
363
- try {
364
- return statSync(path).isFile();
365
- } catch {
366
- return false;
367
- }
368
- }
369
-
370
- // kernel/sidecar/store.ts
371
- import { existsSync as existsSync3, readFileSync as readFileSync2, renameSync, writeFileSync, unlinkSync } from "fs";
372
- import { dirname as dirname2, resolve as resolve2 } from "path";
373
- import { createRequire as createRequire2 } from "module";
374
- import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
375
- import yaml2 from "js-yaml";
376
-
377
207
  // kernel/adapters/in-memory-progress.ts
378
208
  var InMemoryProgressEmitter = class {
379
209
  #listeners = /* @__PURE__ */ new Set();
@@ -388,46 +218,56 @@ var InMemoryProgressEmitter = class {
388
218
  }
389
219
  };
390
220
 
391
- // kernel/adapters/silent-logger.ts
392
- var SilentLogger = class {
393
- trace() {
394
- }
395
- debug() {
396
- }
397
- info() {
398
- }
399
- warn() {
400
- }
401
- error() {
402
- }
403
- };
221
+ // kernel/adapters/plugin-loader/index.ts
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";
404
227
 
405
- // kernel/util/logger.ts
406
- var active = new SilentLogger();
407
- var log = {
408
- trace: (message, context) => active.trace(message, context),
409
- debug: (message, context) => active.debug(message, context),
410
- info: (message, context) => active.info(message, context),
411
- warn: (message, context) => active.warn(message, context),
412
- error: (message, context) => active.error(message, context)
413
- };
414
- function configureLogger(impl) {
415
- active = impl;
416
- }
417
- function resetLogger() {
418
- active = new SilentLogger();
419
- }
420
- function getActiveLogger() {
421
- return active;
228
+ // kernel/adapters/plugin-loader/id-utils.ts
229
+ import { isAbsolute, relative, resolve } from "path";
230
+
231
+ // kernel/adapters/plugin-loader/validation.ts
232
+ import { Ajv2020 } from "ajv/dist/2020.js";
233
+
234
+ // kernel/util/ajv-interop.ts
235
+ import addFormatsModule from "ajv-formats";
236
+ var addFormats = addFormatsModule.default ?? addFormatsModule;
237
+ function applyAjvFormats(ajv) {
238
+ addFormats(ajv);
422
239
  }
423
240
 
424
- // kernel/adapters/plugin-loader.ts
425
- import { createRequire as createRequire3 } from "module";
426
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
427
- import { isAbsolute, join as join2, relative as relative2, resolve as resolve3 } from "path";
428
- import { pathToFileURL } from "url";
429
- import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
430
- import semver from "semver";
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";
431
271
 
432
272
  // kernel/i18n/plugin-store.texts.ts
433
273
  var PLUGIN_STORE_TEXTS = {
@@ -506,37 +346,20 @@ function formatAjvErrors(errors) {
506
346
  return errors.map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
507
347
  }
508
348
 
509
- // kernel/extensions/hook.ts
510
- var HOOK_TRIGGERS = Object.freeze([
511
- "boot",
512
- "scan.started",
513
- "scan.completed",
514
- "extractor.completed",
515
- "analyzer.completed",
516
- "action.completed",
517
- "job.spawning",
518
- "job.completed",
519
- "job.failed",
520
- "shutdown"
521
- ]);
522
-
523
- // kernel/adapters/plugin-loader.ts
524
- var KNOWN_KINDS = /* @__PURE__ */ new Set(["provider", "extractor", "analyzer", "action", "formatter", "hook"]);
525
- var KNOWN_KINDS_LIST = [...KNOWN_KINDS].join(" / ");
526
- var HOOKABLE_TRIGGERS_LIST = HOOK_TRIGGERS.join(", ");
349
+ // kernel/adapters/plugin-loader/index.ts
527
350
  function installedSpecVersion() {
528
- const require2 = createRequire3(import.meta.url);
351
+ const require2 = createRequire(import.meta.url);
529
352
  const indexPath = require2.resolve("@skill-map/spec/index.json");
530
353
  const pkgPath = resolve3(indexPath, "..", "package.json");
531
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
354
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
532
355
  return pkg.version;
533
356
  }
534
357
 
535
358
  // kernel/adapters/schema-validators.ts
536
- import { readFileSync as readFileSync4 } from "fs";
537
- import { dirname as dirname3, resolve as resolve4 } from "path";
538
- import { createRequire as createRequire4 } from "module";
539
- import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
359
+ import { readFileSync as readFileSync3 } from "fs";
360
+ import { dirname, resolve as resolve4 } from "path";
361
+ import { createRequire as createRequire2 } from "module";
362
+ import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
540
363
  var SCHEMA_FILES = {
541
364
  node: "schemas/node.schema.json",
542
365
  link: "schemas/link.schema.json",
@@ -571,8 +394,8 @@ function loadSchemaValidators() {
571
394
  return cachedValidators;
572
395
  }
573
396
  function buildSchemaValidators() {
574
- const specRoot = resolveSpecRoot2();
575
- const ajv = new Ajv20204({
397
+ const specRoot = resolveSpecRoot();
398
+ const ajv = new Ajv20203({
576
399
  strict: false,
577
400
  allErrors: true,
578
401
  allowUnionTypes: true
@@ -581,13 +404,13 @@ function buildSchemaValidators() {
581
404
  for (const rel of SUPPORTING_SCHEMAS) {
582
405
  const file = resolve4(specRoot, rel);
583
406
  if (!existsSyncSafe(file)) continue;
584
- const schema = JSON.parse(readFileSync4(file, "utf8"));
407
+ const schema = JSON.parse(readFileSync3(file, "utf8"));
585
408
  ajv.addSchema(schema);
586
409
  }
587
410
  const validators = /* @__PURE__ */ new Map();
588
411
  for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
589
412
  const file = resolve4(specRoot, rel);
590
- const schema = JSON.parse(readFileSync4(file, "utf8"));
413
+ const schema = JSON.parse(readFileSync3(file, "utf8"));
591
414
  const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
592
415
  validators.set(name, byId ?? ajv.compile(schema));
593
416
  }
@@ -607,7 +430,7 @@ function buildSchemaValidators() {
607
430
  const KNOWN_SLOTS = /* @__PURE__ */ new Set([
608
431
  "card.title.right",
609
432
  "card.subtitle.left",
610
- "card.footer.left.counter",
433
+ "card.footer.left",
611
434
  "card.footer.right",
612
435
  "graph.node.alert",
613
436
  "inspector.header.badge.counter",
@@ -618,7 +441,7 @@ function buildSchemaValidators() {
618
441
  "inspector.body.panel.key-values",
619
442
  "inspector.body.panel.link-list",
620
443
  "inspector.body.panel.markdown",
621
- "topbar.actions.indicator"
444
+ "topbar.nav.start"
622
445
  ]);
623
446
  function getContributionValidator(slot) {
624
447
  if (!KNOWN_SLOTS.has(slot)) return null;
@@ -667,15 +490,15 @@ function buildSchemaValidators() {
667
490
  };
668
491
  }
669
492
  function buildProviderFrontmatterValidator(providers) {
670
- const specRoot = resolveSpecRoot2();
671
- const ajv = new Ajv20204({
493
+ const specRoot = resolveSpecRoot();
494
+ const ajv = new Ajv20203({
672
495
  strict: false,
673
496
  allErrors: true,
674
497
  allowUnionTypes: true
675
498
  });
676
499
  applyAjvFormats(ajv);
677
500
  const baseFile = resolve4(specRoot, "schemas/frontmatter/base.schema.json");
678
- const baseSchema = JSON.parse(readFileSync4(baseFile, "utf8"));
501
+ const baseSchema = JSON.parse(readFileSync3(baseFile, "utf8"));
679
502
  ajv.addSchema(baseSchema);
680
503
  registerProviderAuxiliarySchemas(ajv, providers);
681
504
  const compiled = /* @__PURE__ */ new Map();
@@ -712,11 +535,11 @@ function registerProviderAuxiliarySchemas(ajv, providers) {
712
535
  }
713
536
  }
714
537
  }
715
- function resolveSpecRoot2() {
716
- const require2 = createRequire4(import.meta.url);
538
+ function resolveSpecRoot() {
539
+ const require2 = createRequire2(import.meta.url);
717
540
  try {
718
541
  const indexPath = require2.resolve("@skill-map/spec/index.json");
719
- return dirname3(indexPath);
542
+ return dirname(indexPath);
720
543
  } catch {
721
544
  throw new Error(
722
545
  "@skill-map/spec not resolvable \u2014 ensure the workspace is linked or the package is installed."
@@ -725,214 +548,49 @@ function resolveSpecRoot2() {
725
548
  }
726
549
  function existsSyncSafe(path) {
727
550
  try {
728
- readFileSync4(path, "utf8");
551
+ readFileSync3(path, "utf8");
729
552
  return true;
730
553
  } catch {
731
554
  return false;
732
555
  }
733
556
  }
734
557
 
735
- // kernel/i18n/orchestrator.texts.ts
736
- var ORCHESTRATOR_TEXTS = {
737
- frontmatterInvalid: "Frontmatter for {{path}} ({{kind}}) failed schema validation: {{errors}}",
738
- frontmatterMalformedPasteWithIndent: "Frontmatter fence in {{path}} appears indented; YAML frontmatter MUST start with `---` at column 0. The file was scanned as body-only \u2014 the metadata block was silently lost. Move the `---` lines to the start of the line.",
739
- frontmatterMalformedByteOrderMark: "Frontmatter fence in {{path}} is preceded by a UTF-8 byte-order mark (BOM); the file was scanned as body-only. Re-save the file as UTF-8 without BOM. The metadata block was silently lost.",
740
- frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes \u2014 no matching `---` line at column 0 was found. The file was scanned as body-only and every metadata field was silently lost. Add a closing `---` line below the metadata block.",
741
- extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
742
- extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
743
- extensionErrorContributionUnknownId: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}} but did not declare it in its `viewContributions` map. Contribution dropped.',
744
- extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{slot}}" schema: {{errors}}. Contribution dropped.',
745
- runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
746
- runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
747
- };
748
-
749
558
  // kernel/util/format-error.ts
750
559
  function formatErrorMessage(err) {
751
560
  return err instanceof Error ? err.message : String(err);
752
561
  }
753
562
 
754
- // kernel/scan/walk-content.ts
755
- import { readFile, readdir, stat } from "fs/promises";
756
- import { join as join3, relative as relative3, sep as sep2 } from "path";
757
-
758
- // kernel/scan/ignore.ts
759
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
760
- import { dirname as dirname4, resolve as resolve5 } from "path";
761
- import { fileURLToPath } from "url";
762
- import ignoreFactory from "ignore";
763
- function buildIgnoreFilter(opts = {}) {
764
- const ig = ignoreFactory();
765
- if (opts.includeDefaults !== false) {
766
- ig.add(loadDefaultsText());
767
- }
768
- if (opts.configIgnore && opts.configIgnore.length > 0) {
769
- ig.add(opts.configIgnore);
563
+ // kernel/adapters/silent-logger.ts
564
+ var SilentLogger = class {
565
+ trace() {
770
566
  }
771
- if (opts.ignoreFileText && opts.ignoreFileText.length > 0) {
772
- ig.add(opts.ignoreFileText);
567
+ debug() {
773
568
  }
774
- return {
775
- ignores(relativePath) {
776
- if (relativePath === "" || relativePath === "." || relativePath === "./") {
777
- return false;
778
- }
779
- const normalised = relativePath.replace(/^\.\//, "").replace(/\\/g, "/").replace(/^\//, "");
780
- if (normalised === "") return false;
781
- return ig.ignores(normalised);
782
- }
783
- };
784
- }
785
- var cachedDefaults = null;
786
- function loadDefaultsText() {
787
- if (cachedDefaults !== null) return cachedDefaults;
788
- cachedDefaults = readDefaultsFromDisk();
789
- return cachedDefaults;
790
- }
791
- function readDefaultsFromDisk() {
792
- const here = dirname4(fileURLToPath(import.meta.url));
793
- const candidates = [
794
- resolve5(here, "../../config/defaults/skillmapignore"),
795
- // src/kernel/scan/ → src/config/defaults/
796
- resolve5(here, "../config/defaults/skillmapignore"),
797
- // dist/cli.js → dist/config/defaults/ (siblings)
798
- resolve5(here, "config/defaults/skillmapignore")
799
- ];
800
- for (const candidate of candidates) {
801
- if (existsSync5(candidate)) {
802
- try {
803
- return readFileSync5(candidate, "utf8");
804
- } catch {
805
- }
806
- }
569
+ info() {
807
570
  }
808
- return "";
809
- }
810
-
811
- // built-in-plugins/parsers/frontmatter-yaml/index.ts
812
- import yaml3 from "js-yaml";
813
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
814
- var FORBIDDEN_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
815
- var frontmatterYamlParser = {
816
- id: "frontmatter-yaml",
817
- parse(raw, _path) {
818
- const match = FRONTMATTER_RE.exec(raw);
819
- if (!match) return { frontmatterRaw: "", frontmatter: {}, body: raw };
820
- const frontmatterRaw = match[1];
821
- const body = match[2];
822
- const parsed = {};
823
- try {
824
- const doc = yaml3.load(frontmatterRaw, { schema: yaml3.JSON_SCHEMA });
825
- if (doc && typeof doc === "object" && !Array.isArray(doc)) {
826
- for (const [k, v] of Object.entries(doc)) {
827
- if (FORBIDDEN_FRONTMATTER_KEYS.has(k)) continue;
828
- parsed[k] = v;
829
- }
830
- }
831
- } catch {
832
- }
833
- return { frontmatterRaw, frontmatter: parsed, body };
571
+ warn() {
834
572
  }
835
- };
836
-
837
- // built-in-plugins/parsers/plain/index.ts
838
- var plainParser = {
839
- id: "plain",
840
- parse(raw, _path) {
841
- return { frontmatter: {}, frontmatterRaw: "", body: raw };
573
+ error() {
842
574
  }
843
575
  };
844
576
 
845
- // kernel/scan/parsers/index.ts
846
- var REGISTRY = /* @__PURE__ */ new Map([
847
- [frontmatterYamlParser.id, frontmatterYamlParser],
848
- [plainParser.id, plainParser]
849
- ]);
850
- var FROZEN_IDS = new Set(REGISTRY.keys());
851
- function getParser(id) {
852
- return REGISTRY.get(id);
853
- }
854
-
855
- // kernel/scan/walk-content.ts
856
- var UnknownParserError = class extends Error {
857
- constructor(parserId) {
858
- super(`Unknown parser id '${parserId}'. Built-in parsers: 'frontmatter-yaml', 'plain'.`);
859
- this.name = "UnknownParserError";
860
- }
577
+ // kernel/util/logger.ts
578
+ var active = new SilentLogger();
579
+ var log = {
580
+ trace: (message, context) => active.trace(message, context),
581
+ debug: (message, context) => active.debug(message, context),
582
+ info: (message, context) => active.info(message, context),
583
+ warn: (message, context) => active.warn(message, context),
584
+ error: (message, context) => active.error(message, context)
861
585
  };
862
- async function* walkContent(roots, options) {
863
- const parser = getParser(options.parser);
864
- if (!parser) throw new UnknownParserError(options.parser);
865
- const filter = options.ignoreFilter ?? buildIgnoreFilter();
866
- const extensions = options.extensions;
867
- for (const root of roots) {
868
- for await (const file of walkRoot(root, root, filter, extensions)) {
869
- const relPath = relative3(root, file).split(sep2).join("/");
870
- let raw;
871
- try {
872
- raw = await readFile(file, "utf8");
873
- } catch {
874
- continue;
875
- }
876
- const parsed = parser.parse(raw, relPath);
877
- yield {
878
- path: relPath,
879
- body: parsed.body,
880
- frontmatterRaw: parsed.frontmatterRaw,
881
- frontmatter: parsed.frontmatter
882
- };
883
- }
884
- }
885
- }
886
- async function* walkRoot(root, current, filter, extensions) {
887
- let entries;
888
- try {
889
- entries = await readdir(current, { withFileTypes: true, encoding: "utf8" });
890
- } catch {
891
- return;
892
- }
893
- for (const entry of entries) {
894
- const name = entry.name;
895
- const full = join3(current, name);
896
- const rel = relative3(root, full).split(sep2).join("/");
897
- if (filter.ignores(rel)) continue;
898
- if (entry.isSymbolicLink()) continue;
899
- if (entry.isDirectory()) {
900
- yield* walkRoot(root, full, filter, extensions);
901
- } else if (entry.isFile() && hasMatchingExtension(name, extensions)) {
902
- try {
903
- const s = await stat(full);
904
- if (s.isFile()) yield full;
905
- } catch {
906
- }
907
- }
908
- }
586
+ function configureLogger(impl) {
587
+ active = impl;
909
588
  }
910
- function hasMatchingExtension(name, extensions) {
911
- for (const ext of extensions) {
912
- if (name.endsWith(ext)) return true;
913
- }
914
- return false;
589
+ function resetLogger() {
590
+ active = new SilentLogger();
915
591
  }
916
-
917
- // kernel/extensions/provider.ts
918
- var DEFAULT_READ_CONFIG = Object.freeze({
919
- extensions: Object.freeze([".md"]),
920
- parser: "frontmatter-yaml"
921
- });
922
- function resolveProviderWalk(provider) {
923
- if (provider.walk) {
924
- const walk2 = provider.walk.bind(provider);
925
- return walk2;
926
- }
927
- const read = provider.read ?? DEFAULT_READ_CONFIG;
928
- return (roots, options) => {
929
- const walkOptions = {
930
- extensions: read.extensions,
931
- parser: read.parser
932
- };
933
- if (options?.ignoreFilter) walkOptions.ignoreFilter = options.ignoreFilter;
934
- return walkContent(roots, walkOptions);
935
- };
592
+ function getActiveLogger() {
593
+ return active;
936
594
  }
937
595
 
938
596
  // kernel/extensions/hook-dispatcher.ts
@@ -1014,166 +672,21 @@ function buildHookContext(_hook, trigger, event) {
1014
672
  return ctx;
1015
673
  }
1016
674
 
1017
- // kernel/orchestrator.ts
1018
- var SCANNED_BY = {
1019
- name: "skill-map",
1020
- version: package_default.version,
1021
- specVersion: resolveSpecVersionSafe()
675
+ // kernel/i18n/orchestrator.texts.ts
676
+ var ORCHESTRATOR_TEXTS = {
677
+ frontmatterInvalid: "Frontmatter for {{path}} ({{kind}}) failed schema validation: {{errors}}",
678
+ frontmatterMalformedPasteWithIndent: "Frontmatter fence in {{path}} appears indented; YAML frontmatter MUST start with `---` at column 0. The file was scanned as body-only \u2014 the metadata block was silently lost. Move the `---` lines to the start of the line.",
679
+ frontmatterMalformedByteOrderMark: "Frontmatter fence in {{path}} is preceded by a UTF-8 byte-order mark (BOM); the file was scanned as body-only. Re-save the file as UTF-8 without BOM. The metadata block was silently lost.",
680
+ frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes \u2014 no matching `---` line at column 0 was found. The file was scanned as body-only and every metadata field was silently lost. Add a closing `---` line below the metadata block.",
681
+ extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
682
+ extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
683
+ extensionErrorContributionUnknownId: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}} but did not declare it in its `viewContributions` map. Contribution dropped.',
684
+ extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{slot}}" schema: {{errors}}. Contribution dropped.',
685
+ runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
686
+ runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
1022
687
  };
1023
- function resolveSpecVersionSafe() {
1024
- try {
1025
- return installedSpecVersion();
1026
- } catch {
1027
- return "unknown";
1028
- }
1029
- }
1030
- async function runScanWithRenames(_kernel, options) {
1031
- return runScanInternal(_kernel, options);
1032
- }
1033
- async function runScan(_kernel, options) {
1034
- const { result } = await runScanInternal(_kernel, options);
1035
- return result;
1036
- }
1037
- async function runScanInternal(_kernel, options) {
1038
- validateRoots(options.roots);
1039
- const start = Date.now();
1040
- const scannedAt = start;
1041
- const emitter = options.emitter ?? new InMemoryProgressEmitter();
1042
- const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
1043
- const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
1044
- const tokenize = options.tokenize !== false;
1045
- const scope = options.scope ?? "project";
1046
- const strict = options.strict === true;
1047
- const encoder = tokenize ? new Tiktoken(cl100k_base) : null;
1048
- const prior = options.priorSnapshot ?? null;
1049
- const enableCache = options.enableCache === true;
1050
- const priorExtractorRuns = options.priorExtractorRuns;
1051
- const priorIndex = indexPriorSnapshot(prior);
1052
- const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
1053
- const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
1054
- emitter.emit(scanStartedEvent);
1055
- await hookDispatcher.dispatch("scan.started", scanStartedEvent);
1056
- const walked = await walkAndExtract({
1057
- providers: exts.providers,
1058
- extractors: exts.extractors,
1059
- roots: options.roots,
1060
- ...options.ignoreFilter ? { ignoreFilter: options.ignoreFilter } : {},
1061
- emitter,
1062
- encoder,
1063
- strict,
1064
- enableCache,
1065
- prior,
1066
- priorIndex,
1067
- priorExtractorRuns,
1068
- providerFrontmatter,
1069
- pluginStores: options.pluginStores
1070
- });
1071
- recomputeLinkCounts(walked.nodes, walked.internalLinks);
1072
- recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
1073
- for (const extractor of exts.extractors) {
1074
- const extractorId = qualifiedExtensionId(extractor.pluginId, extractor.id);
1075
- const evt = makeEvent("extractor.completed", { extractorId });
1076
- emitter.emit(evt);
1077
- await hookDispatcher.dispatch("extractor.completed", evt);
1078
- }
1079
- const analyzerResult = await runAnalyzers(
1080
- exts.analyzers,
1081
- walked.nodes,
1082
- walked.internalLinks,
1083
- walked.orphanSidecars,
1084
- walked.sidecarRoots,
1085
- options.annotationContributions ?? [],
1086
- options.viewContributions ?? [],
1087
- options.orphanJobFiles ?? [],
1088
- options.referenceablePaths,
1089
- options.cwd,
1090
- emitter,
1091
- hookDispatcher
1092
- );
1093
- const issues = analyzerResult.issues;
1094
- for (const c of analyzerResult.contributions) walked.contributions.push(c);
1095
- for (const analyzer of exts.analyzers ?? []) {
1096
- if (analyzer.viewContributions === void 0) continue;
1097
- for (const node of walked.nodes) {
1098
- walked.freshlyRunTuples.add(`${analyzer.pluginId}/${analyzer.id}/${node.path}`);
1099
- }
1100
- }
1101
- for (const issue of walked.frontmatterIssues) issues.push(issue);
1102
- const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
1103
- const stats = {
1104
- // `filesSkipped` is "files walked but not classified by any Provider".
1105
- // Today every walked file IS classified by its Provider (the `claude`
1106
- // Provider's `classify()` always returns a kind, falling back to
1107
- // `'markdown'`), so this is always 0. Wired now so the field shape is
1108
- // spec-conformant; meaningful once multiple Providers compete.
1109
- filesWalked: walked.filesWalked,
1110
- filesSkipped: 0,
1111
- nodesCount: walked.nodes.length,
1112
- linksCount: walked.internalLinks.length,
1113
- issuesCount: issues.length,
1114
- durationMs: Date.now() - start
1115
- };
1116
- const scanCompletedEvent = makeEvent("scan.completed", { stats });
1117
- emitter.emit(scanCompletedEvent);
1118
- await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
1119
- return {
1120
- result: {
1121
- schemaVersion: 1,
1122
- scannedAt,
1123
- scope,
1124
- roots: options.roots,
1125
- providers: exts.providers.map((a) => a.id),
1126
- scannedBy: SCANNED_BY,
1127
- nodes: walked.nodes,
1128
- links: walked.internalLinks,
1129
- issues,
1130
- stats
1131
- },
1132
- renameOps,
1133
- extractorRuns: walked.extractorRuns,
1134
- enrichments: walked.enrichments,
1135
- contributions: walked.contributions,
1136
- freshlyRunTuples: walked.freshlyRunTuples
1137
- };
1138
- }
1139
- function validateRoots(roots) {
1140
- if (roots.length === 0) {
1141
- throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
1142
- }
1143
- for (const root of roots) {
1144
- if (!existsSync6(root) || !statSync2(root).isDirectory()) {
1145
- throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
1146
- }
1147
- }
1148
- }
1149
- function indexPriorSnapshot(prior) {
1150
- const priorNodesByPath = /* @__PURE__ */ new Map();
1151
- const priorNodePaths = /* @__PURE__ */ new Set();
1152
- const priorLinksByOriginating = /* @__PURE__ */ new Map();
1153
- const priorFrontmatterIssuesByNode = /* @__PURE__ */ new Map();
1154
- if (!prior) {
1155
- return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
1156
- }
1157
- for (const node of prior.nodes) {
1158
- priorNodesByPath.set(node.path, node);
1159
- priorNodePaths.add(node.path);
1160
- }
1161
- for (const link of prior.links) {
1162
- const key = originatingNodeOf(link, priorNodePaths);
1163
- const list = priorLinksByOriginating.get(key);
1164
- if (list) list.push(link);
1165
- else priorLinksByOriginating.set(key, [link]);
1166
- }
1167
- for (const issue of prior.issues) {
1168
- if (issue.analyzerId !== "frontmatter-invalid" && issue.analyzerId !== "frontmatter-malformed") continue;
1169
- if (issue.nodeIds.length !== 1) continue;
1170
- const path = issue.nodeIds[0];
1171
- const list = priorFrontmatterIssuesByNode.get(path);
1172
- if (list) list.push(issue);
1173
- else priorFrontmatterIssuesByNode.set(path, [issue]);
1174
- }
1175
- return { priorNodesByPath, priorNodePaths, priorLinksByOriginating, priorFrontmatterIssuesByNode };
1176
- }
688
+
689
+ // kernel/orchestrator/extractors.ts
1177
690
  async function runExtractorsForNode(opts) {
1178
691
  const internalLinks = [];
1179
692
  const externalLinks = [];
@@ -1292,320 +805,71 @@ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
1292
805
  })
1293
806
  );
1294
807
  }
1295
- function computeCacheDecision(opts) {
1296
- const applicableExtractors = opts.extractors.filter(
1297
- (ex) => ex.applicableKinds === void 0 || ex.applicableKinds.includes(opts.kind)
1298
- );
1299
- const applicableQualifiedIds = new Set(
1300
- applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
1301
- );
1302
- const cachedQualifiedIds = /* @__PURE__ */ new Set();
1303
- const missingExtractors = [];
1304
- if (opts.priorExtractorRuns === void 0) {
1305
- if (opts.nodeHashCacheEligible) {
1306
- for (const id of applicableQualifiedIds) cachedQualifiedIds.add(id);
1307
- } else {
1308
- for (const ex of applicableExtractors) missingExtractors.push(ex);
1309
- }
1310
- } else {
1311
- const priorRunsForNode = opts.priorExtractorRuns.get(opts.nodePath) ?? /* @__PURE__ */ new Map();
1312
- for (const ex of applicableExtractors) {
1313
- const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
1314
- const priorBody = priorRunsForNode.get(qualified);
1315
- if (opts.nodeHashCacheEligible && priorBody === opts.bodyHash) {
1316
- cachedQualifiedIds.add(qualified);
1317
- } else {
1318
- missingExtractors.push(ex);
1319
- }
1320
- }
1321
- }
808
+ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
809
+ const scope = extractor.scope;
1322
810
  return {
1323
- applicableExtractors,
1324
- applicableQualifiedIds,
1325
- cachedQualifiedIds,
1326
- missingExtractors,
1327
- fullCacheHit: opts.nodeHashCacheEligible && missingExtractors.length === 0
811
+ node,
812
+ body: scope === "frontmatter" ? "" : body,
813
+ frontmatter: scope === "body" ? {} : frontmatter,
814
+ emitLink,
815
+ enrichNode,
816
+ emitContribution,
817
+ ...store !== void 0 ? { store } : {}
1328
818
  };
1329
819
  }
1330
- function cloneNodeAndReshapeLinks(opts) {
1331
- const node = { ...opts.priorNode, bytes: { ...opts.priorNode.bytes } };
1332
- if (opts.priorNode.tokens) node.tokens = { ...opts.priorNode.tokens };
1333
- const internalLinks = [];
1334
- const reusedLinks = opts.priorLinksByOriginating.get(opts.priorNode.path) ?? [];
1335
- for (const link of reusedLinks) {
1336
- const reshaped = reuseCachedLink(
1337
- link,
1338
- opts.shortIdToQualified,
1339
- opts.cachedQualifiedIds,
1340
- opts.applicableQualifiedIds
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
+ })
1341
836
  );
1342
- if (reshaped) internalLinks.push(reshaped);
1343
- }
1344
- const frontmatterIssues = [];
1345
- const reusedFm = opts.priorFrontmatterIssuesByNode.get(opts.priorNode.path) ?? [];
1346
- for (const issue of reusedFm) {
1347
- frontmatterIssues.push({ ...issue, severity: opts.strict ? "error" : "warn" });
837
+ return null;
1348
838
  }
1349
- return { node, internalLinks, frontmatterIssues };
839
+ const confidence = link.confidence ?? extractor.defaultConfidence;
840
+ return { ...link, confidence };
1350
841
  }
1351
- function reusePriorNode(opts) {
1352
- const base = cloneNodeAndReshapeLinks(opts);
1353
- const ranAt = Date.now();
1354
- const extractorRuns = [];
1355
- for (const qualified of opts.cachedQualifiedIds) {
1356
- extractorRuns.push({
1357
- nodePath: opts.priorNode.path,
1358
- extractorId: qualified,
1359
- bodyHashAtRun: opts.bodyHash,
1360
- ranAt
1361
- });
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);
1362
848
  }
1363
- return { ...base, extractorRuns };
1364
- }
1365
- function buildFreshNodeAndValidateFrontmatter(opts) {
1366
- const node = buildNode({
1367
- path: opts.raw.path,
1368
- kind: opts.kind,
1369
- providerId: opts.provider.id,
1370
- frontmatterRaw: opts.raw.frontmatterRaw,
1371
- body: opts.raw.body,
1372
- frontmatter: opts.raw.frontmatter,
1373
- bodyHash: opts.bodyHash,
1374
- frontmatterHash: opts.frontmatterHash,
1375
- encoder: opts.encoder
1376
- });
1377
- const frontmatterIssues = [];
1378
- if (opts.raw.frontmatterRaw.length > 0) {
1379
- const fmIssue = validateFrontmatter(
1380
- opts.providerFrontmatter,
1381
- opts.provider,
1382
- opts.kind,
1383
- opts.raw.frontmatter,
1384
- opts.raw.path,
1385
- opts.strict
1386
- );
1387
- if (fmIssue) frontmatterIssues.push(fmIssue);
1388
- } else {
1389
- const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
1390
- if (malformed) frontmatterIssues.push(malformed);
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;
1391
854
  }
1392
- return { node, frontmatterIssues };
1393
855
  }
1394
- async function walkAndExtract(opts) {
1395
- const {
1396
- providers,
1397
- extractors,
1398
- roots,
1399
- ignoreFilter,
1400
- emitter,
1401
- encoder,
1402
- strict,
1403
- enableCache,
1404
- prior,
1405
- priorIndex,
1406
- priorExtractorRuns,
1407
- providerFrontmatter,
1408
- pluginStores
1409
- } = opts;
1410
- const { priorNodesByPath, priorLinksByOriginating, priorFrontmatterIssuesByNode } = priorIndex;
1411
- const nodes = [];
1412
- const internalLinks = [];
1413
- const externalLinks = [];
1414
- const cachedPaths = /* @__PURE__ */ new Set();
1415
- const frontmatterIssues = [];
1416
- const enrichmentBuffer = /* @__PURE__ */ new Map();
1417
- const contributionsBuffer = [];
1418
- const freshlyRunTuples = /* @__PURE__ */ new Set();
1419
- const extractorRuns = [];
1420
- const sidecarRoots = /* @__PURE__ */ new Map();
1421
- let filesWalked = 0;
1422
- let index = 0;
1423
- const walkOptions = ignoreFilter ? { ignoreFilter } : {};
1424
- const shortIdToQualified = /* @__PURE__ */ new Map();
1425
- for (const ex of extractors) {
1426
- const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
1427
- const list = shortIdToQualified.get(ex.id);
1428
- if (list) list.push(qualified);
1429
- else shortIdToQualified.set(ex.id, [qualified]);
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);
1430
861
  }
1431
- const claimedPaths = /* @__PURE__ */ new Set();
1432
- for (const provider of providers) {
1433
- for await (const raw of resolveProviderWalk(provider)(roots, walkOptions)) {
1434
- filesWalked += 1;
1435
- if (claimedPaths.has(raw.path)) continue;
1436
- const bodyHash = sha256(raw.body);
1437
- const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
1438
- const priorNode = priorNodesByPath.get(raw.path);
1439
- const nodeHashCacheEligible = enableCache && prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
1440
- const kind = provider.classify(raw.path, raw.frontmatter);
1441
- if (kind === null) {
1442
- continue;
1443
- }
1444
- claimedPaths.add(raw.path);
1445
- index += 1;
1446
- const cacheDecision = computeCacheDecision({
1447
- extractors,
1448
- kind,
1449
- nodePath: raw.path,
1450
- bodyHash,
1451
- nodeHashCacheEligible,
1452
- priorExtractorRuns
1453
- });
1454
- const {
1455
- applicableExtractors,
1456
- applicableQualifiedIds,
1457
- cachedQualifiedIds,
1458
- missingExtractors,
1459
- fullCacheHit
1460
- } = cacheDecision;
1461
- if (fullCacheHit && priorNode) {
1462
- const reused = reusePriorNode({
1463
- priorNode,
1464
- bodyHash,
1465
- strict,
1466
- cachedQualifiedIds,
1467
- applicableQualifiedIds,
1468
- shortIdToQualified,
1469
- priorLinksByOriginating,
1470
- priorFrontmatterIssuesByNode
1471
- });
1472
- const reusedSidecarIssues = resolveAndApplySidecar(
1473
- reused.node,
1474
- raw.path,
1475
- roots,
1476
- bodyHash,
1477
- frontmatterHash,
1478
- sidecarRoots
1479
- );
1480
- nodes.push(reused.node);
1481
- cachedPaths.add(reused.node.path);
1482
- for (const link of reused.internalLinks) internalLinks.push(link);
1483
- for (const issue of reused.frontmatterIssues) frontmatterIssues.push(issue);
1484
- for (const issue of reusedSidecarIssues) frontmatterIssues.push(issue);
1485
- for (const run of reused.extractorRuns) extractorRuns.push(run);
1486
- emitter.emit(makeEvent("scan.progress", { index, path: raw.path, kind, cached: true }));
1487
- continue;
1488
- }
1489
- let node;
1490
- const partialCacheHit = nodeHashCacheEligible && cachedQualifiedIds.size > 0 && priorNode !== void 0;
1491
- if (partialCacheHit && priorNode) {
1492
- const partial = cloneNodeAndReshapeLinks({
1493
- priorNode,
1494
- strict,
1495
- cachedQualifiedIds,
1496
- applicableQualifiedIds,
1497
- shortIdToQualified,
1498
- priorLinksByOriginating,
1499
- priorFrontmatterIssuesByNode
1500
- });
1501
- node = partial.node;
1502
- for (const link of partial.internalLinks) internalLinks.push(link);
1503
- for (const issue of partial.frontmatterIssues) frontmatterIssues.push(issue);
1504
- nodes.push(node);
1505
- } else {
1506
- const fresh = buildFreshNodeAndValidateFrontmatter({
1507
- raw,
1508
- kind,
1509
- provider,
1510
- bodyHash,
1511
- frontmatterHash,
1512
- encoder,
1513
- providerFrontmatter,
1514
- strict
1515
- });
1516
- node = fresh.node;
1517
- nodes.push(node);
1518
- for (const issue of fresh.frontmatterIssues) frontmatterIssues.push(issue);
1519
- }
1520
- const sidecarIssues = resolveAndApplySidecar(
1521
- node,
1522
- raw.path,
1523
- roots,
1524
- bodyHash,
1525
- frontmatterHash,
1526
- sidecarRoots
1527
- );
1528
- for (const issue of sidecarIssues) frontmatterIssues.push(issue);
1529
- emitter.emit(makeEvent("scan.progress", {
1530
- index,
1531
- path: raw.path,
1532
- kind,
1533
- cached: false,
1534
- ...partialCacheHit ? { partialCache: true } : {}
1535
- }));
1536
- const extractorsToRun = partialCacheHit ? missingExtractors : applicableExtractors;
1537
- for (const ex of extractorsToRun) {
1538
- freshlyRunTuples.add(`${ex.pluginId}/${ex.id}/${node.path}`);
1539
- }
1540
- const extractResult = await runExtractorsForNode({
1541
- extractors: extractorsToRun,
1542
- node,
1543
- body: raw.body,
1544
- frontmatter: raw.frontmatter,
1545
- bodyHash,
1546
- emitter,
1547
- ...pluginStores ? { pluginStores } : {}
1548
- });
1549
- for (const link of extractResult.internalLinks) internalLinks.push(link);
1550
- for (const link of extractResult.externalLinks) externalLinks.push(link);
1551
- for (const enr of extractResult.enrichments) {
1552
- enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
1553
- }
1554
- for (const c of extractResult.contributions) contributionsBuffer.push(c);
1555
- const ranAt = Date.now();
1556
- for (const ex of applicableExtractors) {
1557
- const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
1558
- extractorRuns.push({
1559
- nodePath: node.path,
1560
- extractorId: qualified,
1561
- bodyHashAtRun: bodyHash,
1562
- ranAt
1563
- });
1564
- }
1565
- }
862
+ for (const link of externalLinks) {
863
+ const source = byPath2.get(link.source);
864
+ if (source && !cachedPaths.has(source.path)) source.externalRefsCount += 1;
1566
865
  }
1567
- const orphanSidecars = discoverOrphanSidecars(roots);
1568
- return {
1569
- nodes,
1570
- internalLinks,
1571
- externalLinks,
1572
- cachedPaths,
1573
- frontmatterIssues,
1574
- filesWalked,
1575
- enrichments: [...enrichmentBuffer.values()],
1576
- extractorRuns,
1577
- contributions: contributionsBuffer,
1578
- freshlyRunTuples,
1579
- orphanSidecars,
1580
- sidecarRoots
1581
- };
1582
866
  }
1583
- function reuseCachedLink(link, shortIdToQualified, cachedQualifiedIds, applicableQualifiedIds) {
1584
- if (!Array.isArray(link.sources) || link.sources.length === 0) return null;
1585
- const cachedSources = [];
1586
- const obsoleteSources = [];
1587
- let hasMissing = false;
1588
- for (const source of link.sources) {
1589
- const candidates = shortIdToQualified.get(source);
1590
- if (!candidates || candidates.length === 0) {
1591
- obsoleteSources.push(source);
1592
- continue;
1593
- }
1594
- if (candidates.some((q) => cachedQualifiedIds.has(q))) {
1595
- cachedSources.push(source);
1596
- continue;
1597
- }
1598
- if (candidates.some((q) => applicableQualifiedIds.has(q))) {
1599
- hasMissing = true;
1600
- continue;
1601
- }
1602
- obsoleteSources.push(source);
1603
- }
1604
- if (hasMissing) return null;
1605
- if (cachedSources.length === 0) return null;
1606
- if (obsoleteSources.length === 0) return link;
1607
- return { ...link, sources: cachedSources };
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);
1608
870
  }
871
+
872
+ // kernel/orchestrator/analyzers.ts
1609
873
  async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, emitter, hookDispatcher) {
1610
874
  const issues = [];
1611
875
  const contributions = [];
@@ -1681,12 +945,187 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1681
945
  }
1682
946
  return { issues, contributions };
1683
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
+ }
968
+
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 };
977
+ }
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);
987
+ }
988
+ }
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
+ }
996
+ }
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]);
1005
+ }
1006
+ }
1684
1007
  function originatingNodeOf(link, priorNodePaths) {
1685
1008
  if (link.kind === "supersedes" && !priorNodePaths.has(link.source)) {
1686
1009
  return link.target;
1687
1010
  }
1688
1011
  return link.source;
1689
1012
  }
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);
1036
+ }
1037
+ return { cachedQualifiedIds, missingExtractors };
1038
+ }
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 };
1053
+ }
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 };
1074
+ }
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
+ });
1087
+ }
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
1097
+ );
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
1690
1129
  function findHighConfidenceRenames(opts) {
1691
1130
  const ops = [];
1692
1131
  for (const fromPath of opts.deletedPaths) {
@@ -1811,148 +1250,389 @@ function detectRenamesAndOrphans(prior, current, issues) {
1811
1250
  flagOrphans({ deletedPaths, claimedDeleted, issues });
1812
1251
  return ops;
1813
1252
  }
1814
- var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
1815
- function isExternalUrlLink(link) {
1816
- return EXTERNAL_URL_SCHEME_RE.test(link.target);
1817
- }
1818
- function buildNode(args) {
1819
- const bytesFrontmatter = Buffer.byteLength(args.frontmatterRaw, "utf8");
1820
- const bytesBody = Buffer.byteLength(args.body, "utf8");
1821
- const node = {
1822
- path: args.path,
1823
- kind: args.kind,
1824
- provider: args.providerId,
1825
- bodyHash: args.bodyHash,
1826
- frontmatterHash: args.frontmatterHash,
1827
- bytes: {
1828
- frontmatter: bytesFrontmatter,
1829
- body: bytesBody,
1830
- total: bytesFrontmatter + bytesBody
1831
- },
1832
- linksOutCount: 0,
1833
- linksInCount: 0,
1834
- externalRefsCount: 0,
1835
- frontmatter: args.frontmatter
1836
- };
1837
- if (args.encoder) {
1838
- node.tokens = countTokens(args.encoder, args.frontmatterRaw, args.body);
1839
- }
1840
- return node;
1841
- }
1842
- function countTokens(encoder, frontmatterRaw, body) {
1843
- const frontmatter = frontmatterRaw.length > 0 ? encoder.encode(frontmatterRaw).length : 0;
1844
- const bodyTokens = body.length > 0 ? encoder.encode(body).length : 0;
1845
- return { frontmatter, body: bodyTokens, total: frontmatter + bodyTokens };
1846
- }
1847
- function sha256(input) {
1848
- return createHash("sha256").update(input, "utf8").digest("hex");
1849
- }
1850
- function canonicalFrontmatter(parsed, raw) {
1851
- const hasParsedKeys = Object.keys(parsed).length > 0;
1852
- const hasRawText = raw.length > 0;
1853
- if (!hasParsedKeys && hasRawText) {
1854
- return raw;
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());
1855
1267
  }
1856
- return yaml4.dump(parsed, {
1857
- sortKeys: true,
1858
- lineWidth: -1,
1859
- noRefs: true,
1860
- noCompatMode: true
1861
- });
1862
- }
1863
- function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFrontmatterHash, sidecarRoots) {
1864
- const issues = [];
1865
- const mdAbs = resolveAbsoluteMdPath(relativePath, roots);
1866
- if (mdAbs === null) {
1867
- node.sidecar = { present: false };
1868
- return issues;
1268
+ if (opts.configIgnore && opts.configIgnore.length > 0) {
1269
+ ig.add(opts.configIgnore);
1869
1270
  }
1870
- const result = readSidecarFor(mdAbs);
1871
- if (!result.present) {
1872
- node.sidecar = { present: false };
1873
- return issues;
1271
+ if (opts.ignoreFileText && opts.ignoreFileText.length > 0) {
1272
+ ig.add(opts.ignoreFileText);
1874
1273
  }
1875
- if (result.parsed === null) {
1876
- node.sidecar = { present: true, status: null, annotations: null, root: null };
1877
- for (const parseIssue of result.issues) {
1878
- issues.push({
1879
- analyzerId: "invalid-sidecar",
1880
- severity: "warn",
1881
- nodeIds: [node.path],
1882
- message: parseIssue.message,
1883
- data: { sidecarPath: relativePathFromRoots(mdAbs, roots) }
1884
- });
1274
+ return {
1275
+ ignores(relativePath) {
1276
+ if (relativePath === "" || relativePath === "." || relativePath === "./") {
1277
+ return false;
1278
+ }
1279
+ const normalised = relativePath.replace(/^\.\//, "").replace(/\\/g, "/").replace(/^\//, "");
1280
+ if (normalised === "") return false;
1281
+ return ig.ignores(normalised);
1885
1282
  }
1886
- return issues;
1887
- }
1888
- const status = computeDriftStatus({
1889
- storedBodyHash: result.parsed.identityBodyHash,
1890
- storedFrontmatterHash: result.parsed.identityFrontmatterHash,
1891
- liveBodyHash,
1892
- liveFrontmatterHash
1893
- });
1894
- node.sidecar = {
1895
- present: true,
1896
- status,
1897
- annotations: result.parsed.annotations,
1898
- root: result.parsed.raw
1899
1283
  };
1900
- sidecarRoots.set(node.path, result.parsed.raw);
1901
- return issues;
1902
1284
  }
1903
- function resolveAbsoluteMdPath(relativePath, roots) {
1904
- if (isAbsolute2(relativePath)) {
1905
- return existsSync6(relativePath) ? relativePath : null;
1906
- }
1907
- for (const root of roots) {
1908
- const candidate = resolvePath(root, relativePath);
1909
- if (existsSync6(candidate)) return candidate;
1910
- }
1911
- return null;
1285
+ var cachedDefaults = null;
1286
+ function loadDefaultsText() {
1287
+ if (cachedDefaults !== null) return cachedDefaults;
1288
+ cachedDefaults = readDefaultsFromDisk();
1289
+ return cachedDefaults;
1912
1290
  }
1913
- function relativePathFromRoots(absolutePath, roots) {
1914
- for (const root of roots) {
1915
- const abs = resolvePath(root);
1916
- if (absolutePath.startsWith(`${abs}/`) || absolutePath.startsWith(`${abs}\\`)) {
1917
- return absolutePath.slice(abs.length + 1).split(/[\\/]/).join("/");
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 {
1305
+ }
1918
1306
  }
1919
1307
  }
1920
- return absolutePath;
1308
+ return "";
1921
1309
  }
1922
- function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
1923
- const scope = extractor.scope;
1924
- return {
1925
- node,
1926
- body: scope === "frontmatter" ? "" : body,
1927
- frontmatter: scope === "body" ? {} : frontmatter,
1928
- emitLink,
1929
- enrichNode,
1930
- emitContribution,
1931
- ...store !== void 0 ? { store } : {}
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
+ }
1330
+ }
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 };
1342
+ }
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);
1353
+ }
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";
1360
+ }
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;
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
+ };
1383
+ }
1384
+ }
1385
+ }
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;
1392
+ }
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
+ }
1408
+ }
1409
+ }
1410
+ function hasMatchingExtension(name, extensions) {
1411
+ for (const ext of extensions) {
1412
+ if (name.endsWith(ext)) return true;
1413
+ }
1414
+ return false;
1415
+ }
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;
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);
1932
1435
  };
1933
1436
  }
1934
- function validateLink(extractor, link, emitter) {
1935
- if (!extractor.emitsLinkKinds.includes(link.kind)) {
1936
- const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
1937
- emitter.emit(
1938
- makeEvent("extension.error", {
1939
- kind: "link-kind-not-declared",
1940
- extensionId: qualifiedId,
1941
- linkKind: link.kind,
1942
- declaredKinds: extractor.emitsLinkKinds,
1943
- link: { source: link.source, target: link.target, kind: link.kind },
1944
- message: tx(ORCHESTRATOR_TEXTS.extensionErrorLinkKindNotDeclared, {
1945
- extractorId: qualifiedId,
1946
- linkKind: link.kind,
1947
- declaredKinds: extractor.emitsLinkKinds.join(", ")
1948
- })
1949
- })
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: [] };
1448
+ }
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
+ };
1484
+ }
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;
1489
+ return {
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: []
1500
+ };
1501
+ }
1502
+ function sidecarPathFor(mdAbsolutePath) {
1503
+ if (mdAbsolutePath.endsWith(".md")) {
1504
+ return `${mdAbsolutePath.slice(0, -".md".length)}.sm`;
1505
+ }
1506
+ return `${mdAbsolutePath}.sm`;
1507
+ }
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."
1950
1535
  );
1951
- return null;
1952
1536
  }
1953
- const confidence = link.confidence ?? extractor.defaultConfidence;
1954
- return { ...link, confidence };
1955
1537
  }
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);
1556
+ }
1557
+ return out;
1558
+ }
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;
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 });
1580
+ }
1581
+ }
1582
+ function safeIsFile(path) {
1583
+ try {
1584
+ return statSync(path).isFile();
1585
+ } catch {
1586
+ return false;
1587
+ }
1588
+ }
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
1956
1636
  function validateFrontmatter(providerFrontmatter, provider, kind, frontmatter, path, strict) {
1957
1637
  const result = providerFrontmatter.validate(provider, kind, frontmatter);
1958
1638
  if (result.ok) return null;
@@ -2002,50 +1682,160 @@ function malformedMessage(hint, path) {
2002
1682
  return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
2003
1683
  }
2004
1684
  }
2005
- function validateIssue(analyzer, issue, emitter) {
2006
- const severity = issue.severity;
2007
- if (severity !== "error" && severity !== "warn" && severity !== "info") {
2008
- const qualifiedId = `${analyzer.pluginId}/${analyzer.id}`;
2009
- emitter.emit(
2010
- makeEvent("extension.error", {
2011
- kind: "issue-invalid-severity",
2012
- extensionId: qualifiedId,
2013
- severity,
2014
- issue: { analyzerId: issue.analyzerId || analyzer.id, message: issue.message, nodeIds: issue.nodeIds },
2015
- message: tx(ORCHESTRATOR_TEXTS.extensionErrorIssueInvalidSeverity, {
2016
- analyzerId: qualifiedId,
2017
- severity: JSON.stringify(severity)
2018
- })
2019
- })
2020
- );
2021
- return null;
1685
+
1686
+ // kernel/orchestrator/node-build.ts
1687
+ function buildNode(args) {
1688
+ const bytesFrontmatter = Buffer.byteLength(args.frontmatterRaw, "utf8");
1689
+ const bytesBody = Buffer.byteLength(args.body, "utf8");
1690
+ const node = {
1691
+ path: args.path,
1692
+ kind: args.kind,
1693
+ provider: args.providerId,
1694
+ bodyHash: args.bodyHash,
1695
+ frontmatterHash: args.frontmatterHash,
1696
+ bytes: {
1697
+ frontmatter: bytesFrontmatter,
1698
+ body: bytesBody,
1699
+ total: bytesFrontmatter + bytesBody
1700
+ },
1701
+ linksOutCount: 0,
1702
+ linksInCount: 0,
1703
+ externalRefsCount: 0,
1704
+ frontmatter: args.frontmatter
1705
+ };
1706
+ if (args.encoder) {
1707
+ node.tokens = countTokens(args.encoder, args.frontmatterRaw, args.body);
1708
+ }
1709
+ return node;
1710
+ }
1711
+ function countTokens(encoder, frontmatterRaw, body) {
1712
+ const frontmatter = frontmatterRaw.length > 0 ? encoder.encode(frontmatterRaw).length : 0;
1713
+ const bodyTokens = body.length > 0 ? encoder.encode(body).length : 0;
1714
+ return { frontmatter, body: bodyTokens, total: frontmatter + bodyTokens };
1715
+ }
1716
+ function sha256(input) {
1717
+ return createHash("sha256").update(input, "utf8").digest("hex");
1718
+ }
1719
+ function canonicalFrontmatter(parsed, raw) {
1720
+ const hasParsedKeys = Object.keys(parsed).length > 0;
1721
+ const hasRawText = raw.length > 0;
1722
+ if (!hasParsedKeys && hasRawText) {
1723
+ return raw;
1724
+ }
1725
+ return yaml4.dump(parsed, {
1726
+ sortKeys: true,
1727
+ lineWidth: -1,
1728
+ noRefs: true,
1729
+ noCompatMode: true
1730
+ });
1731
+ }
1732
+ function canonicalSidecarAnnotations(annotations) {
1733
+ if (!annotations || typeof annotations !== "object" || Array.isArray(annotations)) {
1734
+ return yaml4.dump({}, { sortKeys: true, lineWidth: -1, noRefs: true, noCompatMode: true });
1735
+ }
1736
+ return yaml4.dump(annotations, {
1737
+ sortKeys: true,
1738
+ lineWidth: -1,
1739
+ noRefs: true,
1740
+ noCompatMode: true
1741
+ });
1742
+ }
1743
+ function resolveSidecarOverlay(relativePath, nodePathForIssue, roots, liveBodyHash, liveFrontmatterHash) {
1744
+ const issues = [];
1745
+ const mdAbs = resolveAbsoluteMdPath(relativePath, roots);
1746
+ if (mdAbs === null) {
1747
+ return { overlay: { present: false }, issues, parsedRoot: null };
1748
+ }
1749
+ const result = readSidecarFor(mdAbs);
1750
+ if (!result.present) {
1751
+ return { overlay: { present: false }, issues, parsedRoot: null };
1752
+ }
1753
+ if (result.parsed === null) {
1754
+ for (const parseIssue of result.issues) {
1755
+ issues.push({
1756
+ analyzerId: "invalid-sidecar",
1757
+ severity: "warn",
1758
+ nodeIds: [nodePathForIssue],
1759
+ message: parseIssue.message,
1760
+ data: { sidecarPath: relativePathFromRoots(mdAbs, roots) }
1761
+ });
1762
+ }
1763
+ return {
1764
+ overlay: { present: true, status: null, annotations: null, root: null },
1765
+ issues,
1766
+ parsedRoot: null
1767
+ };
2022
1768
  }
2023
- return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
1769
+ const status = computeDriftStatus({
1770
+ storedBodyHash: result.parsed.identityBodyHash,
1771
+ storedFrontmatterHash: result.parsed.identityFrontmatterHash,
1772
+ liveBodyHash,
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
+ };
2024
1791
  }
2025
- function recomputeLinkCounts(nodes, links) {
2026
- const byPath2 = /* @__PURE__ */ new Map();
2027
- for (const node of nodes) {
2028
- node.linksOutCount = 0;
2029
- node.linksInCount = 0;
2030
- byPath2.set(node.path, node);
1792
+ function resolveAbsoluteMdPath(relativePath, roots) {
1793
+ if (isAbsolute3(relativePath)) {
1794
+ return existsSync8(relativePath) ? relativePath : null;
2031
1795
  }
2032
- for (const link of links) {
2033
- const source = byPath2.get(link.source);
2034
- if (source) source.linksOutCount += 1;
2035
- const target = byPath2.get(link.target);
2036
- if (target) target.linksInCount += 1;
1796
+ for (const root of roots) {
1797
+ const candidate = resolvePath(root, relativePath);
1798
+ if (existsSync8(candidate)) return candidate;
2037
1799
  }
1800
+ return null;
2038
1801
  }
2039
- function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
2040
- const byPath2 = /* @__PURE__ */ new Map();
2041
- for (const node of nodes) {
2042
- if (!cachedPaths.has(node.path)) node.externalRefsCount = 0;
2043
- byPath2.set(node.path, node);
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
+ }
2044
1808
  }
2045
- for (const link of externalLinks) {
2046
- const source = byPath2.get(link.source);
2047
- if (source && !cachedPaths.has(source.path)) source.externalRefsCount += 1;
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);
2048
1837
  }
1838
+ return { node, frontmatterIssues };
2049
1839
  }
2050
1840
  function mergeNodeWithEnrichments(node, enrichments, opts = {}) {
2051
1841
  const includeStale = opts.includeStale === true;
@@ -2065,11 +1855,400 @@ function assignSafe(target, source) {
2065
1855
  }
2066
1856
  }
2067
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
+
2068
2247
  // kernel/scan/watcher.ts
2069
- import { resolve as resolve6, relative as relative4, sep as sep3 } from "path";
2248
+ import { resolve as resolve10, relative as relative4, sep as sep3 } from "path";
2070
2249
  import chokidar from "chokidar";
2071
2250
  function createChokidarWatcher(opts) {
2072
- const absRoots = opts.roots.map((r) => resolve6(opts.cwd, r));
2251
+ const absRoots = opts.roots.map((r) => resolve10(opts.cwd, r));
2073
2252
  const ignoreFilterOpt = opts.ignoreFilter;
2074
2253
  const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
2075
2254
  const ignored = getFilter ? (path) => {