artifact-graph 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2668,11 +2668,194 @@ var init_versioned_traceability = __esm({
2668
2668
  }
2669
2669
  });
2670
2670
 
2671
+ // src/native-binding-diagnostics.ts
2672
+ import { existsSync as existsSync2, readdirSync } from "fs";
2673
+ import { createRequire } from "module";
2674
+ import { dirname as dirname2, join as join4 } from "path";
2675
+ import { fileURLToPath } from "url";
2676
+ function parsePnpmVersion(stdout) {
2677
+ const trimmed = stdout.trim();
2678
+ const segments = trimmed.split(".");
2679
+ if (!/^\d+$/.test(segments[0] ?? "")) return void 0;
2680
+ const major = Number(segments[0]);
2681
+ const minorSegment = segments.length > 1 ? segments[1] : void 0;
2682
+ const minor = minorSegment !== void 0 && /^\d+$/.test(minorSegment) ? Number(minorSegment) : void 0;
2683
+ return minor === void 0 ? { major } : { major, minor };
2684
+ }
2685
+ function pnpmUsesAllowBuilds(version) {
2686
+ if (!version) return false;
2687
+ return version.major > 10 || version.major === 10 && (version.minor ?? 0) >= 26;
2688
+ }
2689
+ function createInstallBoundBindingLoader(installBase) {
2690
+ return () => createRequire(installBase)("better-sqlite3");
2691
+ }
2692
+ function defaultBindingLoaderImpl() {
2693
+ const base = typeof __filename === "string" ? __filename : import.meta.url;
2694
+ return createRequire(base)("better-sqlite3");
2695
+ }
2696
+ function defaultBindingLoader() {
2697
+ return defaultBindingLoaderImpl();
2698
+ }
2699
+ function classifyNativeBindingError(error, context = {}) {
2700
+ const message = error instanceof Error ? error.message : String(error);
2701
+ const code = error?.code;
2702
+ if (/was compiled against a different Node\.js version|NODE_MODULE_VERSION|different ABI|Incompatible ABI/i.test(
2703
+ message
2704
+ )) {
2705
+ return { cause: "ABI_MISMATCH", detail: message };
2706
+ }
2707
+ if (code === "MODULE_NOT_FOUND" || /Cannot find module|Cannot find package/i.test(message)) {
2708
+ if (context.packagePresent) {
2709
+ return {
2710
+ cause: "BUILD_DISABLED",
2711
+ detail: `${message}\uFF08better-sqlite3 \u5305\u76EE\u5F55\u5B58\u5728\u4F46\u539F\u751F\u4EA7\u7269\u7F3A\u5931\uFF0C\u7B26\u5408 lifecycle scripts \u88AB\u7981\u7684\u7279\u5F81\uFF09`
2712
+ };
2713
+ }
2714
+ return { cause: "MISSING", detail: message };
2715
+ }
2716
+ if (/Could not locate the bindings file/i.test(message)) {
2717
+ return {
2718
+ cause: "BUILD_DISABLED",
2719
+ detail: `${message}\uFF08better-sqlite3 \u5305\u5B58\u5728\u4F46\u539F\u751F .node \u4EA7\u7269\u672A\u6784\u5EFA\uFF0C\u7B26\u5408 lifecycle scripts \u88AB\u7981\u6216\u672A\u6784\u5EFA\u7684\u7279\u5F81\uFF09`
2720
+ };
2721
+ }
2722
+ if (/invalid ELF|broken binary|failed to load native/i.test(message)) {
2723
+ return { cause: "LOAD_ERROR", detail: message };
2724
+ }
2725
+ return { cause: "LOAD_ERROR", detail: message };
2726
+ }
2727
+ function buildNativeBindingSuggestion(cause, pnpmVersion) {
2728
+ const allowlist = pnpmUsesAllowBuilds(pnpmVersion) ? `pnpm 10.26+ \u5728\u9879\u76EE\u6839 pnpm-workspace.yaml \u6DFB\u52A0 allowBuilds \u952E\uFF08${PNPM_ALLOW_BUILDS_SNIPPET.replace(/\n/g, "\uFF1B")}\uFF09` : `pnpm 10.0\u201310.25 \u5728 package.json \u914D\u7F6E pnpm.onlyBuiltDependencies: ["better-sqlite3"]\uFF08pnpm 10.26+ \u6539\u7528\u9879\u76EE\u6839 pnpm-workspace.yaml \u7684 allowBuilds \u952E\uFF09`;
2729
+ switch (cause) {
2730
+ case "MISSING":
2731
+ return `better-sqlite3 \u539F\u751F\u4EA7\u7269\u7F3A\u5931\u3002\u6309 INSTALL.md \u58F0\u660E\u6784\u5EFA\u5141\u8BB8\u6E05\u5355\u540E\u91CD\u65B0\u5B89\u88C5\uFF1A${allowlist}\uFF0C\u7136\u540E pnpm install\u3002`;
2732
+ case "ABI_MISMATCH":
2733
+ return `\u5F53\u524D Node ABI \u4E0E better-sqlite3 prebuilt \u76EE\u6807\u4E0D\u4E00\u81F4\uFF08\u5F53\u524D process.versions.modules=${process.versions.modules}\uFF09\u3002\u5207\u6362\u5230\u4E0E\u5DF2\u5B89\u88C5 prebuilt \u5339\u914D\u7684 Node \u7248\u672C\uFF0C\u6216\u5BF9\u5F53\u524D Node \u91CD\u5EFA\uFF1Apnpm rebuild better-sqlite3\u3002\u4E0D\u8981\u624B\u5DE5\u7F16\u8BD1\u3002`;
2734
+ case "BUILD_DISABLED":
2735
+ return `pnpm lifecycle scripts \u88AB\u7981\u5BFC\u81F4 better-sqlite3 \u672A\u6784\u5EFA\u3002${allowlist}\uFF0C\u7136\u540E pnpm install \u91CD\u5EFA\u539F\u751F\u4EA7\u7269\u3002\u4E0D\u8981\u624B\u5DE5\u7F16\u8BD1\u3002`;
2736
+ default:
2737
+ return "better-sqlite3 \u52A0\u8F7D\u5931\u8D25\uFF08\u672A\u77E5\u539F\u751F\u9519\u8BEF\uFF09\u3002\u5148\u8FD0\u884C artifact-graph doctor --format json \u67E5\u770B\u63A2\u9488\u8BE6\u60C5\uFF0C\u518D\u6309 INSTALL.md \u91CD\u5EFA\u5F53\u524D\u9009\u4E2D\u8FD0\u884C\u65F6\u7684\u539F\u751F\u4EA7\u7269\u3002";
2738
+ }
2739
+ }
2740
+ function probeNativeBinding(context = {}) {
2741
+ const loader = context.loader ?? defaultBindingLoader;
2742
+ const presenceRoot = context.installRoot ?? context.root;
2743
+ const packagePresent = presenceRoot ? betterSqlite3PackagePresent(presenceRoot) : void 0;
2744
+ const classify = (error) => classifyNativeBindingError(error, { packagePresent });
2745
+ const fail = (failedStage, error) => {
2746
+ const { cause, detail } = classify(error);
2747
+ return { ok: false, failedStage, cause, detail, suggestion: buildNativeBindingSuggestion(cause, context.pnpmVersion) };
2748
+ };
2749
+ let DatabaseCtor;
2750
+ try {
2751
+ DatabaseCtor = loader();
2752
+ } catch (error) {
2753
+ return fail("load", error);
2754
+ }
2755
+ const Database = DatabaseCtor;
2756
+ let db;
2757
+ try {
2758
+ db = new Database(":memory:");
2759
+ } catch (error) {
2760
+ return fail("open", error);
2761
+ }
2762
+ try {
2763
+ db.exec("CREATE TABLE probe (id INTEGER PRIMARY KEY, value TEXT)");
2764
+ db.prepare("INSERT INTO probe (id, value) VALUES (?, ?)").run(1, "ok");
2765
+ } catch (error) {
2766
+ safeClose(db);
2767
+ return fail("write", error);
2768
+ }
2769
+ try {
2770
+ const row = db.prepare("SELECT value FROM probe WHERE id = ?").get(1);
2771
+ if (!row || row.value !== "ok") {
2772
+ safeClose(db);
2773
+ return {
2774
+ ok: false,
2775
+ failedStage: "read",
2776
+ cause: "LOAD_ERROR",
2777
+ detail: "probe \u56DE\u8BFB\u65AD\u8A00\u5931\u8D25\uFF1ASELECT \u7ED3\u679C\u4E0E\u5199\u5165\u503C\u4E0D\u4E00\u81F4\u3002",
2778
+ suggestion: buildNativeBindingSuggestion("LOAD_ERROR", context.pnpmVersion)
2779
+ };
2780
+ }
2781
+ } catch (error) {
2782
+ safeClose(db);
2783
+ return fail("read", error);
2784
+ }
2785
+ try {
2786
+ db.close();
2787
+ } catch (error) {
2788
+ return fail("close", error);
2789
+ }
2790
+ return { ok: true };
2791
+ }
2792
+ function buildNativeBindingDiagnostic(failure, probeContext) {
2793
+ return {
2794
+ selectedCli: probeContext.selectedCli,
2795
+ runtime: `${process.execPath} (v${process.versions.node})`,
2796
+ abi: `process.versions.modules=${process.versions.modules}${abiPrebuiltHint(failure.detail)}`,
2797
+ installSource: probeContext.installSource,
2798
+ probedFrom: probeContext.probedFrom,
2799
+ failedStage: failure.failedStage,
2800
+ cause: failure.cause,
2801
+ suggestion: failure.suggestion
2802
+ };
2803
+ }
2804
+ function buildNativeBindingSuccessDiagnostic(probeContext) {
2805
+ return {
2806
+ selectedCli: probeContext.selectedCli,
2807
+ runtime: `${process.execPath} (v${process.versions.node})`,
2808
+ installSource: probeContext.installSource,
2809
+ probedFrom: probeContext.probedFrom
2810
+ };
2811
+ }
2812
+ function findPackageRoot(anchorFile) {
2813
+ let dir = dirname2(anchorFile);
2814
+ for (; ; ) {
2815
+ if (existsSync2(join4(dir, "package.json"))) return dir;
2816
+ const parent = dirname2(dir);
2817
+ if (parent === dir) return void 0;
2818
+ dir = parent;
2819
+ }
2820
+ }
2821
+ function safeClose(db) {
2822
+ try {
2823
+ db?.close();
2824
+ } catch {
2825
+ }
2826
+ }
2827
+ function abiPrebuiltHint(detail) {
2828
+ const match = /using NODE_MODULE_VERSION (\d+)/.exec(detail);
2829
+ return match ? `\uFF1Bprebuilt \u76EE\u6807 NODE_MODULE_VERSION=${match[1]}` : "";
2830
+ }
2831
+ function betterSqlite3PackagePresent(root) {
2832
+ const direct = join4(root, "node_modules", "better-sqlite3", "package.json");
2833
+ if (existsSync2(direct)) return true;
2834
+ const pnpmDir = join4(root, "node_modules", ".pnpm");
2835
+ let entries;
2836
+ try {
2837
+ entries = readdirSync(pnpmDir);
2838
+ } catch {
2839
+ return false;
2840
+ }
2841
+ return entries.some((entry) => {
2842
+ if (!entry.startsWith("better-sqlite3@")) return false;
2843
+ return existsSync2(join4(pnpmDir, entry, "node_modules", "better-sqlite3", "package.json"));
2844
+ });
2845
+ }
2846
+ var PNPM_ALLOW_BUILDS_SNIPPET;
2847
+ var init_native_binding_diagnostics = __esm({
2848
+ "src/native-binding-diagnostics.ts"() {
2849
+ "use strict";
2850
+ PNPM_ALLOW_BUILDS_SNIPPET = "pnpm-workspace.yaml:\nallowBuilds:\n better-sqlite3: true";
2851
+ }
2852
+ });
2853
+
2671
2854
  // src/cli-resolver.ts
2672
2855
  import { execFile } from "child_process";
2673
- import { constants } from "fs";
2856
+ import { constants, readFileSync, realpathSync } from "fs";
2674
2857
  import { access } from "fs/promises";
2675
- import { isAbsolute, join as join4, resolve } from "path";
2858
+ import { basename, dirname as dirname3, isAbsolute, join as join5, resolve } from "path";
2676
2859
  import { promisify } from "util";
2677
2860
  async function resolveArtifactGraphCli(root, options = {}) {
2678
2861
  const pathCli = await findCommandOnPath("artifact-graph");
@@ -2680,7 +2863,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
2680
2863
  const candidates = [
2681
2864
  {
2682
2865
  source: "node_modules",
2683
- path: join4(root, "node_modules/.bin/artifact-graph"),
2866
+ path: join5(root, "node_modules/.bin/artifact-graph"),
2684
2867
  exists: false
2685
2868
  },
2686
2869
  {
@@ -2713,13 +2896,33 @@ async function resolveArtifactGraphCli(root, options = {}) {
2713
2896
  }
2714
2897
  async function doctorArtifactChain(root, options = {}) {
2715
2898
  const cli = await resolveArtifactGraphCli(root, options);
2716
- const configPath = join4(root, "artifact-graph.config.yaml");
2717
- const lockPath = join4(root, VERSION_LOCK_PATH);
2899
+ const configPath = join5(root, "artifact-graph.config.yaml");
2900
+ const lockPath = join5(root, VERSION_LOCK_PATH);
2718
2901
  const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
2719
2902
  const nodeCompatible = isNodeCompatible(process.versions.node);
2903
+ const pnpmVersion = await detectPnpmVersion();
2904
+ const installBase = bindingInstallBase(cli, root);
2905
+ const installRoot = bindingInstallRoot(cli, root);
2906
+ const loader = options.bindingLoader ?? (installBase ? createInstallBoundBindingLoader(installBase) : void 0);
2907
+ const probe = probeNativeBinding({
2908
+ root,
2909
+ ...installRoot ? { installRoot } : {},
2910
+ pnpmVersion,
2911
+ ...loader ? { loader } : {}
2912
+ });
2913
+ const probedFrom = installBase ?? "(current artifact-graph package)";
2914
+ const probeContext = {
2915
+ selectedCli: cli.path ? `${cli.path} (${cli.source})` : "not found",
2916
+ installSource: cli.source ?? "unknown",
2917
+ probedFrom
2918
+ };
2919
+ const nativeBinding = probe.ok ? { ok: true, ...buildNativeBindingSuccessDiagnostic(probeContext) } : { ok: false, ...buildNativeBindingDiagnostic(probe, probeContext) };
2720
2920
  const warnings = [
2721
2921
  ...cli.warnings,
2722
- ...nodeCompatible ? [] : [`Node.js ${process.versions.node} does not satisfy >=22.0.0.`]
2922
+ ...nodeCompatible ? [] : [`Node.js ${process.versions.node} does not satisfy >=22.0.0.`],
2923
+ ...probe.ok ? [] : [
2924
+ `better-sqlite3 binding probe FAILED at stage "${probe.failedStage}" (cause ${probe.cause}): ${probe.suggestion}`
2925
+ ]
2723
2926
  ];
2724
2927
  return {
2725
2928
  schemaVersion: "1.0",
@@ -2738,6 +2941,7 @@ async function doctorArtifactChain(root, options = {}) {
2738
2941
  path: lockPath,
2739
2942
  exists: await pathExists(lockPath)
2740
2943
  },
2944
+ nativeBinding,
2741
2945
  supportedCommands,
2742
2946
  warnings
2743
2947
  };
@@ -2753,6 +2957,31 @@ function renderDoctorMarkdown(report) {
2753
2957
  `Lock: \`${report.lock.path}\` ${report.lock.exists ? "found" : "missing"}`,
2754
2958
  ""
2755
2959
  ];
2960
+ lines.push("## Native Binding Probe");
2961
+ if (report.nativeBinding.ok) {
2962
+ const binding = report.nativeBinding;
2963
+ lines.push(
2964
+ "- better-sqlite3 probe: PASS",
2965
+ ` - selectedCli: \`${binding.selectedCli}\``,
2966
+ ` - runtime: \`${binding.runtime}\``,
2967
+ ` - installSource: \`${binding.installSource}\``,
2968
+ ` - probedFrom: \`${binding.probedFrom}\``
2969
+ );
2970
+ } else {
2971
+ const binding = report.nativeBinding;
2972
+ lines.push(
2973
+ "- better-sqlite3 probe: **FAIL**",
2974
+ ` - failedStage: \`${binding.failedStage}\``,
2975
+ ` - cause: \`${binding.cause}\``,
2976
+ ` - selectedCli: \`${binding.selectedCli}\``,
2977
+ ` - runtime: \`${binding.runtime}\``,
2978
+ ` - abi: \`${binding.abi}\``,
2979
+ ` - installSource: \`${binding.installSource}\``,
2980
+ ` - probedFrom: \`${binding.probedFrom}\``,
2981
+ ` - suggestion: ${binding.suggestion}`
2982
+ );
2983
+ }
2984
+ lines.push("");
2756
2985
  if (report.supportedCommands.length > 0) {
2757
2986
  lines.push("## Supported Commands");
2758
2987
  for (const command of report.supportedCommands) {
@@ -2809,11 +3038,71 @@ function isNodeCompatible(version) {
2809
3038
  const major = Number(version.split(".")[0]);
2810
3039
  return Number.isFinite(major) && major >= 22;
2811
3040
  }
3041
+ async function detectPnpmVersion() {
3042
+ try {
3043
+ const result = await execFileAsync("pnpm", ["--version"]);
3044
+ return parsePnpmVersion(result.stdout);
3045
+ } catch {
3046
+ return void 0;
3047
+ }
3048
+ }
3049
+ function bindingInstallBase(cli, root) {
3050
+ if (!cli.path || !cli.source) return void 0;
3051
+ switch (cli.source) {
3052
+ case "node_modules":
3053
+ return join5(root, "package.json");
3054
+ case "plugin-bundled":
3055
+ case "path":
3056
+ case "legacy":
3057
+ return resolveCliInstallAnchor(cli.path);
3058
+ default:
3059
+ return void 0;
3060
+ }
3061
+ }
3062
+ function bindingInstallRoot(cli, root) {
3063
+ if (!cli.path || !cli.source) return void 0;
3064
+ switch (cli.source) {
3065
+ case "node_modules":
3066
+ return root;
3067
+ case "plugin-bundled":
3068
+ case "path":
3069
+ case "legacy":
3070
+ return findPackageRoot(resolveCliInstallAnchor(cli.path));
3071
+ default:
3072
+ return void 0;
3073
+ }
3074
+ }
3075
+ function resolveCliInstallAnchor(cliPath) {
3076
+ const real = safeRealpath(cliPath);
3077
+ if (basename(dirname3(real)) !== ".bin") return real;
3078
+ const shimTarget = readBinShimTarget(real);
3079
+ return shimTarget ? safeRealpath(shimTarget) : real;
3080
+ }
3081
+ function readBinShimTarget(shimPath) {
3082
+ let content;
3083
+ try {
3084
+ content = readFileSync(shimPath, "utf-8");
3085
+ } catch {
3086
+ return void 0;
3087
+ }
3088
+ if (!content.startsWith("#!")) return void 0;
3089
+ const match = /\$basedir\/(\.\.(?:\/[^"'\s]+)+\.js)/.exec(content);
3090
+ if (!match) return void 0;
3091
+ return resolve(dirname3(shimPath), match[1]);
3092
+ }
3093
+ function safeRealpath(path) {
3094
+ try {
3095
+ return realpathSync(path);
3096
+ } catch {
3097
+ return path;
3098
+ }
3099
+ }
2812
3100
  var execFileAsync, KNOWN_COMMANDS;
2813
3101
  var init_cli_resolver = __esm({
2814
3102
  "src/cli-resolver.ts"() {
2815
3103
  "use strict";
2816
3104
  init_versioned_traceability();
3105
+ init_native_binding_diagnostics();
2817
3106
  execFileAsync = promisify(execFile);
2818
3107
  KNOWN_COMMANDS = [
2819
3108
  "init",
@@ -2832,6 +3121,7 @@ var init_cli_resolver = __esm({
2832
3121
  "version-lock refresh",
2833
3122
  "trace-version",
2834
3123
  "next-id",
3124
+ "refactor-id",
2835
3125
  "render",
2836
3126
  "doctor"
2837
3127
  ];
@@ -2939,7 +3229,7 @@ var init_git_hook_path = __esm({
2939
3229
  import { constants as constants2 } from "fs";
2940
3230
  import { randomUUID } from "crypto";
2941
3231
  import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
2942
- import { basename, dirname as dirname2, join as join5 } from "path";
3232
+ import { basename as basename2, dirname as dirname4, join as join6 } from "path";
2943
3233
  function detectHookInterpreter(content) {
2944
3234
  if (content.trim().length === 0) {
2945
3235
  return "empty";
@@ -3250,8 +3540,8 @@ async function removeHookAtomically(hookPath, snapshot) {
3250
3540
  }
3251
3541
  }
3252
3542
  async function writeHookAtomically(hookPath, content, snapshot, mode) {
3253
- await mkdir3(dirname2(hookPath), { recursive: true });
3254
- const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3543
+ await mkdir3(dirname4(hookPath), { recursive: true });
3544
+ const temporaryPath = join6(dirname4(hookPath), `.${basename2(hookPath)}.${randomUUID()}.tmp`);
3255
3545
  let temporaryExists = false;
3256
3546
  try {
3257
3547
  const temporary = await open(temporaryPath, "wx", mode);
@@ -3690,7 +3980,7 @@ var init_review_result_validator = __esm({
3690
3980
  // src/contract-kernel.ts
3691
3981
  import { createHash as createHash2 } from "crypto";
3692
3982
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
3693
- import { join as join6 } from "path";
3983
+ import { join as join7 } from "path";
3694
3984
  import _AjvModule from "ajv";
3695
3985
  function isOfficialNamespace(namespace) {
3696
3986
  return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
@@ -4022,7 +4312,7 @@ async function loadContractsFromDirectory(contractsDir, options) {
4022
4312
  const entries = await readdir2(contractsDir, { withFileTypes: true });
4023
4313
  for (const entry of entries) {
4024
4314
  if (entry.isDirectory()) {
4025
- const schemaPath = join6(contractsDir, entry.name, "schema.json");
4315
+ const schemaPath = join7(contractsDir, entry.name, "schema.json");
4026
4316
  const contract = await loadContract(schemaPath, options);
4027
4317
  contracts.push(contract);
4028
4318
  }
@@ -4463,12 +4753,11 @@ var init_contract_kernel = __esm({
4463
4753
  });
4464
4754
 
4465
4755
  // src/index.ts
4466
- import Database from "better-sqlite3";
4467
4756
  import matter from "gray-matter";
4468
4757
  import yaml from "js-yaml";
4469
- import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
4758
+ import { accessSync, constants as fsConstants, existsSync as existsSync3, statSync } from "fs";
4470
4759
  import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
4471
- import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
4760
+ import { basename as basename4, dirname as dirname5, extname, isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve3 } from "path";
4472
4761
  function isTargetArtifactType(type) {
4473
4762
  return isPacketTargetType(type);
4474
4763
  }
@@ -4510,7 +4799,7 @@ function resolveArtifactTypeName(schema, token) {
4510
4799
  return void 0;
4511
4800
  }
4512
4801
  async function loadConfig(root) {
4513
- const configPath = join7(root, "artifact-graph.config.yaml");
4802
+ const configPath = join8(root, "artifact-graph.config.yaml");
4514
4803
  let parsed = {};
4515
4804
  try {
4516
4805
  const raw = await readFile3(configPath, "utf-8");
@@ -4687,7 +4976,7 @@ async function scanArtifacts(root, schema) {
4687
4976
  continue;
4688
4977
  }
4689
4978
  scannedFiles.set(file, type);
4690
- const raw = await readFile3(join7(root, file), "utf-8");
4979
+ const raw = await readFile3(join8(root, file), "utf-8");
4691
4980
  const parsed = parseFile(type, file, raw, config);
4692
4981
  nodes.push(...parsed.nodes);
4693
4982
  edges.push(...parsed.edges);
@@ -5003,7 +5292,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
5003
5292
  const indexPath = "artifacts/prd/feature-index.md";
5004
5293
  let raw = "";
5005
5294
  try {
5006
- raw = await readFile3(join7(root, indexPath), "utf-8");
5295
+ raw = await readFile3(join8(root, indexPath), "utf-8");
5007
5296
  } catch (error) {
5008
5297
  if (error.code === "ENOENT") {
5009
5298
  return [];
@@ -5193,11 +5482,12 @@ function nextId(graph, schema, type, rangeName) {
5193
5482
  throw new Error(`ID range ${type}.${rangeName} is exhausted`);
5194
5483
  }
5195
5484
  async function writeGraphCache(root, graph) {
5196
- const cacheDir = join7(root, ".artifact-graph");
5485
+ const cacheDir = join8(root, ".artifact-graph");
5197
5486
  await mkdir4(cacheDir, { recursive: true });
5198
- await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5487
+ await writeFile3(join8(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5199
5488
  `);
5200
- const db = new Database(join7(cacheDir, "graph.sqlite"));
5489
+ const { default: Database } = await import("better-sqlite3");
5490
+ const db = new Database(join8(cacheDir, "graph.sqlite"));
5201
5491
  try {
5202
5492
  db.exec(`
5203
5493
  DROP TABLE IF EXISTS nodes;
@@ -5551,7 +5841,7 @@ function parseDecisions(path, raw) {
5551
5841
  }
5552
5842
  function isTestFile(filePath) {
5553
5843
  const normalized = filePath.replace(/\\/g, "/");
5554
- const name = basename3(normalized);
5844
+ const name = basename4(normalized);
5555
5845
  if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
5556
5846
  if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
5557
5847
  if (/\w+Tests?\.java$/.test(name)) return true;
@@ -5872,7 +6162,7 @@ function parseE2eTest(path, raw) {
5872
6162
  });
5873
6163
  const nodes = [];
5874
6164
  const edges = [];
5875
- const batch = String(data.test_batch ?? basename3(path, extname(path))).trim();
6165
+ const batch = String(data.test_batch ?? basename4(path, extname(path))).trim();
5876
6166
  const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
5877
6167
  const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
5878
6168
  const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
@@ -5945,7 +6235,7 @@ function parseE2eRegistry(path, raw) {
5945
6235
  data = { parseError: error.message };
5946
6236
  }
5947
6237
  return {
5948
- nodes: [{ type: "e2e_registry", code: basename3(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
6238
+ nodes: [{ type: "e2e_registry", code: basename4(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
5949
6239
  edges: []
5950
6240
  };
5951
6241
  }
@@ -6971,10 +7261,10 @@ function validateE2eRegistry(graph) {
6971
7261
  async function validateExecutableTraceability(root, config) {
6972
7262
  const issues = [];
6973
7263
  const schema = config ?? await loadConfig(root);
6974
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7264
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
6975
7265
  let e2eFiles;
6976
7266
  try {
6977
- e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7267
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join8(e2eDir, name));
6978
7268
  } catch {
6979
7269
  return [];
6980
7270
  }
@@ -6987,7 +7277,7 @@ async function validateExecutableTraceability(root, config) {
6987
7277
  const relPath = relative3(root, filePath).split("\\").join("/");
6988
7278
  const parsed = matter(raw);
6989
7279
  const data = parsed.data;
6990
- const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
7280
+ const batch = String(data.test_batch ?? basename4(filePath, extname(filePath))).trim();
6991
7281
  const lines = raw.split(/\r?\n/);
6992
7282
  const tcStarts = [];
6993
7283
  lines.forEach((line, index) => {
@@ -7032,7 +7322,7 @@ async function validateExecutableTraceability(root, config) {
7032
7322
  const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
7033
7323
  const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
7034
7324
  for (const specFile of specFiles) {
7035
- const fullSpecPath = join7(root, specFile);
7325
+ const fullSpecPath = join8(root, specFile);
7036
7326
  let content;
7037
7327
  try {
7038
7328
  content = await readFile3(fullSpecPath, "utf-8");
@@ -7114,7 +7404,7 @@ async function validateExecutableTraceability(root, config) {
7114
7404
  if (entry.testId) {
7115
7405
  let content;
7116
7406
  try {
7117
- content = await readFile3(join7(root, normalizedRefFile), "utf-8");
7407
+ content = await readFile3(join8(root, normalizedRefFile), "utf-8");
7118
7408
  } catch {
7119
7409
  continue;
7120
7410
  }
@@ -7291,11 +7581,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7291
7581
  let withExecutableRef = 0;
7292
7582
  const statusBreakdown = {};
7293
7583
  const chainTypeBreakdown = {};
7294
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7584
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
7295
7585
  const tcFieldsMap = /* @__PURE__ */ new Map();
7296
7586
  let e2eFiles;
7297
7587
  try {
7298
- e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7588
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join8(e2eDir, name));
7299
7589
  } catch {
7300
7590
  e2eFiles = [];
7301
7591
  }
@@ -7310,7 +7600,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7310
7600
  }
7311
7601
  });
7312
7602
  const parsed = matter(raw);
7313
- const batch = String(parsed.data.test_batch ?? basename3(filePath, extname(filePath))).trim();
7603
+ const batch = String(parsed.data.test_batch ?? basename4(filePath, extname(filePath))).trim();
7314
7604
  for (let i = 0; i < tcStarts.length; i++) {
7315
7605
  const start = tcStarts[i];
7316
7606
  const end = tcStarts[i + 1]?.index ?? lines.length;
@@ -7373,7 +7663,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7373
7663
  let hasActiveE2eRef = false;
7374
7664
  for (const entry of parseExecutableRefLines(execRef)) {
7375
7665
  const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
7376
- if (!normalized || !existsSync2(join7(root, normalized))) continue;
7666
+ if (!normalized || !existsSync3(join8(root, normalized))) continue;
7377
7667
  const accepting = await getAcceptingRunners(root, normalized, runners);
7378
7668
  if (accepting.some((runner) => runner.kind === "e2e")) {
7379
7669
  hasActiveE2eRef = true;
@@ -7432,7 +7722,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7432
7722
  const acCoverageRateByFeature = {};
7433
7723
  const featureAcMap = /* @__PURE__ */ new Map();
7434
7724
  for (const node of featureNodes) {
7435
- const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
7725
+ const acs = parseAcceptanceCriteria(await readFile3(join8(root, node.path), "utf-8"));
7436
7726
  featureAcMap.set(node.code, new Set(acs));
7437
7727
  }
7438
7728
  const coveredAcByFeature = /* @__PURE__ */ new Map();
@@ -7474,7 +7764,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7474
7764
  };
7475
7765
  }
7476
7766
  async function generateE2eRegistry(root, opts) {
7477
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7767
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
7478
7768
  let files;
7479
7769
  try {
7480
7770
  files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
@@ -7490,11 +7780,11 @@ async function generateE2eRegistry(root, opts) {
7490
7780
  const batches = [];
7491
7781
  let totalTestCases = 0;
7492
7782
  for (const file of files) {
7493
- const filePath = join7(e2eDir, file);
7783
+ const filePath = join8(e2eDir, file);
7494
7784
  const raw = await readFile3(filePath, "utf-8");
7495
7785
  const parsed = matter(raw);
7496
7786
  const data = parsed.data;
7497
- const batch = String(data.test_batch ?? basename3(file, extname(file))).trim();
7787
+ const batch = String(data.test_batch ?? basename4(file, extname(file))).trim();
7498
7788
  const relPath = `artifacts/tests/e2e/${file}`;
7499
7789
  const scope = String(data.scope ?? "").trim();
7500
7790
  const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
@@ -7618,7 +7908,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
7618
7908
  if (!normalizedPath) {
7619
7909
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7620
7910
  }
7621
- const fullPath = join7(root, normalizedPath);
7911
+ const fullPath = join8(root, normalizedPath);
7622
7912
  let content;
7623
7913
  try {
7624
7914
  content = await readFile3(fullPath, "utf-8");
@@ -7730,7 +8020,7 @@ function escapeRegExp(value) {
7730
8020
  }
7731
8021
  async function hasMarkdownTc(tcKey, e2eDir) {
7732
8022
  const [batch, tcId] = tcKey.split(":");
7733
- const filePath = join7(e2eDir, `${batch}.md`);
8023
+ const filePath = join8(e2eDir, `${batch}.md`);
7734
8024
  try {
7735
8025
  const raw = await readFile3(filePath, "utf-8");
7736
8026
  const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
@@ -7963,7 +8253,7 @@ function normalizeDesignCode(value) {
7963
8253
  if (!raw) {
7964
8254
  return "";
7965
8255
  }
7966
- return basename3(raw, extname(raw));
8256
+ return basename4(raw, extname(raw));
7967
8257
  }
7968
8258
  function toUid(type, code) {
7969
8259
  return `${type}:${code}`;
@@ -8228,7 +8518,7 @@ function resolveArtifactContext(graph, opts) {
8228
8518
  }
8229
8519
  if (root) {
8230
8520
  for (const ap of ALWAYS_PRESENT_ITEMS) {
8231
- const fullPath = join7(root, ap.path);
8521
+ const fullPath = join8(root, ap.path);
8232
8522
  let stat;
8233
8523
  try {
8234
8524
  stat = statSync(fullPath);
@@ -8596,7 +8886,7 @@ export {
8596
8886
  collectChangedPaths,
8597
8887
  computeE2eCoverageStats,
8598
8888
  computeRevisionDigest,
8599
- dirname3 as dirname,
8889
+ dirname5 as dirname,
8600
8890
  discoverAndAuditPackets,
8601
8891
  discoverTargets,
8602
8892
  doctorArtifactChain,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifact-graph",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "Git-native Markdown artifact graph scanner and validator",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",