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/cli.js CHANGED
@@ -2649,11 +2649,215 @@ var init_versioned_traceability = __esm({
2649
2649
  }
2650
2650
  });
2651
2651
 
2652
+ // src/native-binding-diagnostics.ts
2653
+ import { existsSync as existsSync2, readdirSync } from "fs";
2654
+ import { createRequire } from "module";
2655
+ import { dirname as dirname2, join as join4 } from "path";
2656
+ import { fileURLToPath } from "url";
2657
+ function parsePnpmVersion(stdout) {
2658
+ const trimmed = stdout.trim();
2659
+ const segments = trimmed.split(".");
2660
+ if (!/^\d+$/.test(segments[0] ?? "")) return void 0;
2661
+ const major = Number(segments[0]);
2662
+ const minorSegment = segments.length > 1 ? segments[1] : void 0;
2663
+ const minor = minorSegment !== void 0 && /^\d+$/.test(minorSegment) ? Number(minorSegment) : void 0;
2664
+ return minor === void 0 ? { major } : { major, minor };
2665
+ }
2666
+ function pnpmUsesAllowBuilds(version) {
2667
+ if (!version) return false;
2668
+ return version.major > 10 || version.major === 10 && (version.minor ?? 0) >= 26;
2669
+ }
2670
+ function createInstallBoundBindingLoader(installBase) {
2671
+ return () => createRequire(installBase)("better-sqlite3");
2672
+ }
2673
+ function defaultBindingLoaderImpl() {
2674
+ const base = typeof __filename === "string" ? __filename : import.meta.url;
2675
+ return createRequire(base)("better-sqlite3");
2676
+ }
2677
+ function defaultBindingLoader() {
2678
+ return defaultBindingLoaderImpl();
2679
+ }
2680
+ function classifyNativeBindingError(error, context = {}) {
2681
+ const message = error instanceof Error ? error.message : String(error);
2682
+ const code = error?.code;
2683
+ if (/was compiled against a different Node\.js version|NODE_MODULE_VERSION|different ABI|Incompatible ABI/i.test(
2684
+ message
2685
+ )) {
2686
+ return { cause: "ABI_MISMATCH", detail: message };
2687
+ }
2688
+ if (code === "MODULE_NOT_FOUND" || /Cannot find module|Cannot find package/i.test(message)) {
2689
+ if (context.packagePresent) {
2690
+ return {
2691
+ cause: "BUILD_DISABLED",
2692
+ 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`
2693
+ };
2694
+ }
2695
+ return { cause: "MISSING", detail: message };
2696
+ }
2697
+ if (/Could not locate the bindings file/i.test(message)) {
2698
+ return {
2699
+ cause: "BUILD_DISABLED",
2700
+ 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`
2701
+ };
2702
+ }
2703
+ if (/invalid ELF|broken binary|failed to load native/i.test(message)) {
2704
+ return { cause: "LOAD_ERROR", detail: message };
2705
+ }
2706
+ return { cause: "LOAD_ERROR", detail: message };
2707
+ }
2708
+ function buildNativeBindingSuggestion(cause, pnpmVersion) {
2709
+ 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`;
2710
+ switch (cause) {
2711
+ case "MISSING":
2712
+ 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`;
2713
+ case "ABI_MISMATCH":
2714
+ 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`;
2715
+ case "BUILD_DISABLED":
2716
+ 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`;
2717
+ default:
2718
+ 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";
2719
+ }
2720
+ }
2721
+ function probeNativeBinding(context = {}) {
2722
+ const loader = context.loader ?? defaultBindingLoader;
2723
+ const presenceRoot = context.installRoot ?? context.root;
2724
+ const packagePresent = presenceRoot ? betterSqlite3PackagePresent(presenceRoot) : void 0;
2725
+ const classify = (error) => classifyNativeBindingError(error, { packagePresent });
2726
+ const fail = (failedStage, error) => {
2727
+ const { cause, detail } = classify(error);
2728
+ return { ok: false, failedStage, cause, detail, suggestion: buildNativeBindingSuggestion(cause, context.pnpmVersion) };
2729
+ };
2730
+ let DatabaseCtor;
2731
+ try {
2732
+ DatabaseCtor = loader();
2733
+ } catch (error) {
2734
+ return fail("load", error);
2735
+ }
2736
+ const Database = DatabaseCtor;
2737
+ let db;
2738
+ try {
2739
+ db = new Database(":memory:");
2740
+ } catch (error) {
2741
+ return fail("open", error);
2742
+ }
2743
+ try {
2744
+ db.exec("CREATE TABLE probe (id INTEGER PRIMARY KEY, value TEXT)");
2745
+ db.prepare("INSERT INTO probe (id, value) VALUES (?, ?)").run(1, "ok");
2746
+ } catch (error) {
2747
+ safeClose(db);
2748
+ return fail("write", error);
2749
+ }
2750
+ try {
2751
+ const row = db.prepare("SELECT value FROM probe WHERE id = ?").get(1);
2752
+ if (!row || row.value !== "ok") {
2753
+ safeClose(db);
2754
+ return {
2755
+ ok: false,
2756
+ failedStage: "read",
2757
+ cause: "LOAD_ERROR",
2758
+ detail: "probe \u56DE\u8BFB\u65AD\u8A00\u5931\u8D25\uFF1ASELECT \u7ED3\u679C\u4E0E\u5199\u5165\u503C\u4E0D\u4E00\u81F4\u3002",
2759
+ suggestion: buildNativeBindingSuggestion("LOAD_ERROR", context.pnpmVersion)
2760
+ };
2761
+ }
2762
+ } catch (error) {
2763
+ safeClose(db);
2764
+ return fail("read", error);
2765
+ }
2766
+ try {
2767
+ db.close();
2768
+ } catch (error) {
2769
+ return fail("close", error);
2770
+ }
2771
+ return { ok: true };
2772
+ }
2773
+ function buildNativeBindingDiagnostic(failure, probeContext) {
2774
+ return {
2775
+ selectedCli: probeContext.selectedCli,
2776
+ runtime: `${process.execPath} (v${process.versions.node})`,
2777
+ abi: `process.versions.modules=${process.versions.modules}${abiPrebuiltHint(failure.detail)}`,
2778
+ installSource: probeContext.installSource,
2779
+ probedFrom: probeContext.probedFrom,
2780
+ failedStage: failure.failedStage,
2781
+ cause: failure.cause,
2782
+ suggestion: failure.suggestion
2783
+ };
2784
+ }
2785
+ function buildNativeBindingSuccessDiagnostic(probeContext) {
2786
+ return {
2787
+ selectedCli: probeContext.selectedCli,
2788
+ runtime: `${process.execPath} (v${process.versions.node})`,
2789
+ installSource: probeContext.installSource,
2790
+ probedFrom: probeContext.probedFrom
2791
+ };
2792
+ }
2793
+ function findPackageRoot(anchorFile) {
2794
+ let dir = dirname2(anchorFile);
2795
+ for (; ; ) {
2796
+ if (existsSync2(join4(dir, "package.json"))) return dir;
2797
+ const parent = dirname2(dir);
2798
+ if (parent === dir) return void 0;
2799
+ dir = parent;
2800
+ }
2801
+ }
2802
+ function runningArtifactGraphInstallRoot() {
2803
+ const anchor = typeof __filename === "string" ? __filename : fileURLToPath(import.meta.url);
2804
+ return findPackageRoot(anchor);
2805
+ }
2806
+ function structuredNativeBindingError(failure, context = {}) {
2807
+ return {
2808
+ error: "native_binding_unavailable",
2809
+ stage: failure.failedStage,
2810
+ cause: failure.cause,
2811
+ detail: failure.detail,
2812
+ suggestion: failure.suggestion,
2813
+ doctorHint: "artifact-graph doctor --format json",
2814
+ // F-10-P01/F-10-P03:机器读取错误包络也暴露实际探测来源,使 scan/query
2815
+ // 与 doctor 的分类依据(同一运行安装根)可被自动测试断言。
2816
+ probedFrom: context.probedFrom ?? "(current artifact-graph package)"
2817
+ };
2818
+ }
2819
+ function renderStructuredNativeBindingError(failure, context = {}) {
2820
+ return `${JSON.stringify(structuredNativeBindingError(failure, context), null, 2)}
2821
+ `;
2822
+ }
2823
+ function safeClose(db) {
2824
+ try {
2825
+ db?.close();
2826
+ } catch {
2827
+ }
2828
+ }
2829
+ function abiPrebuiltHint(detail) {
2830
+ const match = /using NODE_MODULE_VERSION (\d+)/.exec(detail);
2831
+ return match ? `\uFF1Bprebuilt \u76EE\u6807 NODE_MODULE_VERSION=${match[1]}` : "";
2832
+ }
2833
+ function betterSqlite3PackagePresent(root) {
2834
+ const direct = join4(root, "node_modules", "better-sqlite3", "package.json");
2835
+ if (existsSync2(direct)) return true;
2836
+ const pnpmDir = join4(root, "node_modules", ".pnpm");
2837
+ let entries;
2838
+ try {
2839
+ entries = readdirSync(pnpmDir);
2840
+ } catch {
2841
+ return false;
2842
+ }
2843
+ return entries.some((entry) => {
2844
+ if (!entry.startsWith("better-sqlite3@")) return false;
2845
+ return existsSync2(join4(pnpmDir, entry, "node_modules", "better-sqlite3", "package.json"));
2846
+ });
2847
+ }
2848
+ var PNPM_ALLOW_BUILDS_SNIPPET;
2849
+ var init_native_binding_diagnostics = __esm({
2850
+ "src/native-binding-diagnostics.ts"() {
2851
+ "use strict";
2852
+ PNPM_ALLOW_BUILDS_SNIPPET = "pnpm-workspace.yaml:\nallowBuilds:\n better-sqlite3: true";
2853
+ }
2854
+ });
2855
+
2652
2856
  // src/cli-resolver.ts
2653
2857
  import { execFile } from "child_process";
2654
- import { constants } from "fs";
2858
+ import { constants, readFileSync, realpathSync } from "fs";
2655
2859
  import { access } from "fs/promises";
2656
- import { isAbsolute, join as join4, resolve } from "path";
2860
+ import { basename, dirname as dirname3, isAbsolute, join as join5, resolve } from "path";
2657
2861
  import { promisify } from "util";
2658
2862
  async function resolveArtifactGraphCli(root, options = {}) {
2659
2863
  const pathCli = await findCommandOnPath("artifact-graph");
@@ -2661,7 +2865,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
2661
2865
  const candidates = [
2662
2866
  {
2663
2867
  source: "node_modules",
2664
- path: join4(root, "node_modules/.bin/artifact-graph"),
2868
+ path: join5(root, "node_modules/.bin/artifact-graph"),
2665
2869
  exists: false
2666
2870
  },
2667
2871
  {
@@ -2694,13 +2898,33 @@ async function resolveArtifactGraphCli(root, options = {}) {
2694
2898
  }
2695
2899
  async function doctorArtifactChain(root, options = {}) {
2696
2900
  const cli = await resolveArtifactGraphCli(root, options);
2697
- const configPath = join4(root, "artifact-graph.config.yaml");
2698
- const lockPath = join4(root, VERSION_LOCK_PATH);
2901
+ const configPath = join5(root, "artifact-graph.config.yaml");
2902
+ const lockPath = join5(root, VERSION_LOCK_PATH);
2699
2903
  const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
2700
2904
  const nodeCompatible = isNodeCompatible(process.versions.node);
2905
+ const pnpmVersion = await detectPnpmVersion();
2906
+ const installBase = bindingInstallBase(cli, root);
2907
+ const installRoot = bindingInstallRoot(cli, root);
2908
+ const loader = options.bindingLoader ?? (installBase ? createInstallBoundBindingLoader(installBase) : void 0);
2909
+ const probe = probeNativeBinding({
2910
+ root,
2911
+ ...installRoot ? { installRoot } : {},
2912
+ pnpmVersion,
2913
+ ...loader ? { loader } : {}
2914
+ });
2915
+ const probedFrom = installBase ?? "(current artifact-graph package)";
2916
+ const probeContext = {
2917
+ selectedCli: cli.path ? `${cli.path} (${cli.source})` : "not found",
2918
+ installSource: cli.source ?? "unknown",
2919
+ probedFrom
2920
+ };
2921
+ const nativeBinding = probe.ok ? { ok: true, ...buildNativeBindingSuccessDiagnostic(probeContext) } : { ok: false, ...buildNativeBindingDiagnostic(probe, probeContext) };
2701
2922
  const warnings = [
2702
2923
  ...cli.warnings,
2703
- ...nodeCompatible ? [] : [`Node.js ${process.versions.node} does not satisfy >=22.0.0.`]
2924
+ ...nodeCompatible ? [] : [`Node.js ${process.versions.node} does not satisfy >=22.0.0.`],
2925
+ ...probe.ok ? [] : [
2926
+ `better-sqlite3 binding probe FAILED at stage "${probe.failedStage}" (cause ${probe.cause}): ${probe.suggestion}`
2927
+ ]
2704
2928
  ];
2705
2929
  return {
2706
2930
  schemaVersion: "1.0",
@@ -2719,6 +2943,7 @@ async function doctorArtifactChain(root, options = {}) {
2719
2943
  path: lockPath,
2720
2944
  exists: await pathExists(lockPath)
2721
2945
  },
2946
+ nativeBinding,
2722
2947
  supportedCommands,
2723
2948
  warnings
2724
2949
  };
@@ -2734,6 +2959,31 @@ function renderDoctorMarkdown(report) {
2734
2959
  `Lock: \`${report.lock.path}\` ${report.lock.exists ? "found" : "missing"}`,
2735
2960
  ""
2736
2961
  ];
2962
+ lines.push("## Native Binding Probe");
2963
+ if (report.nativeBinding.ok) {
2964
+ const binding = report.nativeBinding;
2965
+ lines.push(
2966
+ "- better-sqlite3 probe: PASS",
2967
+ ` - selectedCli: \`${binding.selectedCli}\``,
2968
+ ` - runtime: \`${binding.runtime}\``,
2969
+ ` - installSource: \`${binding.installSource}\``,
2970
+ ` - probedFrom: \`${binding.probedFrom}\``
2971
+ );
2972
+ } else {
2973
+ const binding = report.nativeBinding;
2974
+ lines.push(
2975
+ "- better-sqlite3 probe: **FAIL**",
2976
+ ` - failedStage: \`${binding.failedStage}\``,
2977
+ ` - cause: \`${binding.cause}\``,
2978
+ ` - selectedCli: \`${binding.selectedCli}\``,
2979
+ ` - runtime: \`${binding.runtime}\``,
2980
+ ` - abi: \`${binding.abi}\``,
2981
+ ` - installSource: \`${binding.installSource}\``,
2982
+ ` - probedFrom: \`${binding.probedFrom}\``,
2983
+ ` - suggestion: ${binding.suggestion}`
2984
+ );
2985
+ }
2986
+ lines.push("");
2737
2987
  if (report.supportedCommands.length > 0) {
2738
2988
  lines.push("## Supported Commands");
2739
2989
  for (const command of report.supportedCommands) {
@@ -2790,11 +3040,71 @@ function isNodeCompatible(version) {
2790
3040
  const major = Number(version.split(".")[0]);
2791
3041
  return Number.isFinite(major) && major >= 22;
2792
3042
  }
3043
+ async function detectPnpmVersion() {
3044
+ try {
3045
+ const result = await execFileAsync("pnpm", ["--version"]);
3046
+ return parsePnpmVersion(result.stdout);
3047
+ } catch {
3048
+ return void 0;
3049
+ }
3050
+ }
3051
+ function bindingInstallBase(cli, root) {
3052
+ if (!cli.path || !cli.source) return void 0;
3053
+ switch (cli.source) {
3054
+ case "node_modules":
3055
+ return join5(root, "package.json");
3056
+ case "plugin-bundled":
3057
+ case "path":
3058
+ case "legacy":
3059
+ return resolveCliInstallAnchor(cli.path);
3060
+ default:
3061
+ return void 0;
3062
+ }
3063
+ }
3064
+ function bindingInstallRoot(cli, root) {
3065
+ if (!cli.path || !cli.source) return void 0;
3066
+ switch (cli.source) {
3067
+ case "node_modules":
3068
+ return root;
3069
+ case "plugin-bundled":
3070
+ case "path":
3071
+ case "legacy":
3072
+ return findPackageRoot(resolveCliInstallAnchor(cli.path));
3073
+ default:
3074
+ return void 0;
3075
+ }
3076
+ }
3077
+ function resolveCliInstallAnchor(cliPath) {
3078
+ const real = safeRealpath(cliPath);
3079
+ if (basename(dirname3(real)) !== ".bin") return real;
3080
+ const shimTarget = readBinShimTarget(real);
3081
+ return shimTarget ? safeRealpath(shimTarget) : real;
3082
+ }
3083
+ function readBinShimTarget(shimPath) {
3084
+ let content;
3085
+ try {
3086
+ content = readFileSync(shimPath, "utf-8");
3087
+ } catch {
3088
+ return void 0;
3089
+ }
3090
+ if (!content.startsWith("#!")) return void 0;
3091
+ const match = /\$basedir\/(\.\.(?:\/[^"'\s]+)+\.js)/.exec(content);
3092
+ if (!match) return void 0;
3093
+ return resolve(dirname3(shimPath), match[1]);
3094
+ }
3095
+ function safeRealpath(path) {
3096
+ try {
3097
+ return realpathSync(path);
3098
+ } catch {
3099
+ return path;
3100
+ }
3101
+ }
2793
3102
  var execFileAsync, KNOWN_COMMANDS;
2794
3103
  var init_cli_resolver = __esm({
2795
3104
  "src/cli-resolver.ts"() {
2796
3105
  "use strict";
2797
3106
  init_versioned_traceability();
3107
+ init_native_binding_diagnostics();
2798
3108
  execFileAsync = promisify(execFile);
2799
3109
  KNOWN_COMMANDS = [
2800
3110
  "init",
@@ -2813,6 +3123,7 @@ var init_cli_resolver = __esm({
2813
3123
  "version-lock refresh",
2814
3124
  "trace-version",
2815
3125
  "next-id",
3126
+ "refactor-id",
2816
3127
  "render",
2817
3128
  "doctor"
2818
3129
  ];
@@ -2920,7 +3231,7 @@ var init_git_hook_path = __esm({
2920
3231
  import { constants as constants2 } from "fs";
2921
3232
  import { randomUUID } from "crypto";
2922
3233
  import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
2923
- import { basename, dirname as dirname2, join as join5 } from "path";
3234
+ import { basename as basename2, dirname as dirname4, join as join6 } from "path";
2924
3235
  function detectHookInterpreter(content) {
2925
3236
  if (content.trim().length === 0) {
2926
3237
  return "empty";
@@ -3227,8 +3538,8 @@ async function removeHookAtomically(hookPath, snapshot) {
3227
3538
  }
3228
3539
  }
3229
3540
  async function writeHookAtomically(hookPath, content, snapshot, mode) {
3230
- await mkdir3(dirname2(hookPath), { recursive: true });
3231
- const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3541
+ await mkdir3(dirname4(hookPath), { recursive: true });
3542
+ const temporaryPath = join6(dirname4(hookPath), `.${basename2(hookPath)}.${randomUUID()}.tmp`);
3232
3543
  let temporaryExists = false;
3233
3544
  try {
3234
3545
  const temporary = await open(temporaryPath, "wx", mode);
@@ -3694,7 +4005,7 @@ __export(contract_kernel_exports, {
3694
4005
  });
3695
4006
  import { createHash as createHash2 } from "crypto";
3696
4007
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
3697
- import { join as join6 } from "path";
4008
+ import { join as join7 } from "path";
3698
4009
  import _AjvModule from "ajv";
3699
4010
  function isOfficialNamespace(namespace) {
3700
4011
  return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
@@ -4107,7 +4418,7 @@ async function loadContractsFromDirectory(contractsDir, options) {
4107
4418
  const entries = await readdir2(contractsDir, { withFileTypes: true });
4108
4419
  for (const entry of entries) {
4109
4420
  if (entry.isDirectory()) {
4110
- const schemaPath = join6(contractsDir, entry.name, "schema.json");
4421
+ const schemaPath = join7(contractsDir, entry.name, "schema.json");
4111
4422
  const contract = await loadContract(schemaPath, options);
4112
4423
  contracts.push(contract);
4113
4424
  }
@@ -4595,12 +4906,11 @@ var init_contract_kernel = __esm({
4595
4906
  });
4596
4907
 
4597
4908
  // src/index.ts
4598
- import Database from "better-sqlite3";
4599
4909
  import matter from "gray-matter";
4600
4910
  import yaml from "js-yaml";
4601
- import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
4911
+ import { accessSync, constants as fsConstants, existsSync as existsSync3, statSync } from "fs";
4602
4912
  import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
4603
- import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
4913
+ import { basename as basename4, dirname as dirname5, extname, isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve3 } from "path";
4604
4914
  function isTargetArtifactType(type) {
4605
4915
  return isPacketTargetType(type);
4606
4916
  }
@@ -4642,7 +4952,7 @@ function resolveArtifactTypeName(schema, token) {
4642
4952
  return void 0;
4643
4953
  }
4644
4954
  async function loadConfig(root) {
4645
- const configPath = join7(root, "artifact-graph.config.yaml");
4955
+ const configPath = join8(root, "artifact-graph.config.yaml");
4646
4956
  let parsed = {};
4647
4957
  try {
4648
4958
  const raw = await readFile3(configPath, "utf-8");
@@ -4819,7 +5129,7 @@ async function scanArtifacts(root, schema) {
4819
5129
  continue;
4820
5130
  }
4821
5131
  scannedFiles.set(file, type);
4822
- const raw = await readFile3(join7(root, file), "utf-8");
5132
+ const raw = await readFile3(join8(root, file), "utf-8");
4823
5133
  const parsed = parseFile(type, file, raw, config);
4824
5134
  nodes.push(...parsed.nodes);
4825
5135
  edges.push(...parsed.edges);
@@ -5132,7 +5442,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
5132
5442
  const indexPath = "artifacts/prd/feature-index.md";
5133
5443
  let raw = "";
5134
5444
  try {
5135
- raw = await readFile3(join7(root, indexPath), "utf-8");
5445
+ raw = await readFile3(join8(root, indexPath), "utf-8");
5136
5446
  } catch (error) {
5137
5447
  if (error.code === "ENOENT") {
5138
5448
  return [];
@@ -5322,11 +5632,12 @@ function nextId(graph, schema, type, rangeName) {
5322
5632
  throw new Error(`ID range ${type}.${rangeName} is exhausted`);
5323
5633
  }
5324
5634
  async function writeGraphCache(root, graph) {
5325
- const cacheDir = join7(root, ".artifact-graph");
5635
+ const cacheDir = join8(root, ".artifact-graph");
5326
5636
  await mkdir4(cacheDir, { recursive: true });
5327
- await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5637
+ await writeFile3(join8(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5328
5638
  `);
5329
- const db = new Database(join7(cacheDir, "graph.sqlite"));
5639
+ const { default: Database } = await import("better-sqlite3");
5640
+ const db = new Database(join8(cacheDir, "graph.sqlite"));
5330
5641
  try {
5331
5642
  db.exec(`
5332
5643
  DROP TABLE IF EXISTS nodes;
@@ -5680,7 +5991,7 @@ function parseDecisions(path, raw) {
5680
5991
  }
5681
5992
  function isTestFile(filePath) {
5682
5993
  const normalized = filePath.replace(/\\/g, "/");
5683
- const name = basename3(normalized);
5994
+ const name = basename4(normalized);
5684
5995
  if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
5685
5996
  if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
5686
5997
  if (/\w+Tests?\.java$/.test(name)) return true;
@@ -6001,7 +6312,7 @@ function parseE2eTest(path, raw) {
6001
6312
  });
6002
6313
  const nodes = [];
6003
6314
  const edges = [];
6004
- const batch = String(data.test_batch ?? basename3(path, extname(path))).trim();
6315
+ const batch = String(data.test_batch ?? basename4(path, extname(path))).trim();
6005
6316
  const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
6006
6317
  const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
6007
6318
  const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
@@ -6074,7 +6385,7 @@ function parseE2eRegistry(path, raw) {
6074
6385
  data = { parseError: error.message };
6075
6386
  }
6076
6387
  return {
6077
- nodes: [{ type: "e2e_registry", code: basename3(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
6388
+ nodes: [{ type: "e2e_registry", code: basename4(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
6078
6389
  edges: []
6079
6390
  };
6080
6391
  }
@@ -7100,10 +7411,10 @@ function validateE2eRegistry(graph) {
7100
7411
  async function validateExecutableTraceability(root, config) {
7101
7412
  const issues = [];
7102
7413
  const schema = config ?? await loadConfig(root);
7103
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7414
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
7104
7415
  let e2eFiles;
7105
7416
  try {
7106
- e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7417
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join8(e2eDir, name));
7107
7418
  } catch {
7108
7419
  return [];
7109
7420
  }
@@ -7116,7 +7427,7 @@ async function validateExecutableTraceability(root, config) {
7116
7427
  const relPath = relative3(root, filePath).split("\\").join("/");
7117
7428
  const parsed = matter(raw);
7118
7429
  const data = parsed.data;
7119
- const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
7430
+ const batch = String(data.test_batch ?? basename4(filePath, extname(filePath))).trim();
7120
7431
  const lines = raw.split(/\r?\n/);
7121
7432
  const tcStarts = [];
7122
7433
  lines.forEach((line, index) => {
@@ -7161,7 +7472,7 @@ async function validateExecutableTraceability(root, config) {
7161
7472
  const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
7162
7473
  const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
7163
7474
  for (const specFile of specFiles) {
7164
- const fullSpecPath = join7(root, specFile);
7475
+ const fullSpecPath = join8(root, specFile);
7165
7476
  let content;
7166
7477
  try {
7167
7478
  content = await readFile3(fullSpecPath, "utf-8");
@@ -7243,7 +7554,7 @@ async function validateExecutableTraceability(root, config) {
7243
7554
  if (entry.testId) {
7244
7555
  let content;
7245
7556
  try {
7246
- content = await readFile3(join7(root, normalizedRefFile), "utf-8");
7557
+ content = await readFile3(join8(root, normalizedRefFile), "utf-8");
7247
7558
  } catch {
7248
7559
  continue;
7249
7560
  }
@@ -7420,11 +7731,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7420
7731
  let withExecutableRef = 0;
7421
7732
  const statusBreakdown = {};
7422
7733
  const chainTypeBreakdown = {};
7423
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7734
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
7424
7735
  const tcFieldsMap = /* @__PURE__ */ new Map();
7425
7736
  let e2eFiles;
7426
7737
  try {
7427
- e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7738
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join8(e2eDir, name));
7428
7739
  } catch {
7429
7740
  e2eFiles = [];
7430
7741
  }
@@ -7439,7 +7750,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7439
7750
  }
7440
7751
  });
7441
7752
  const parsed = matter(raw);
7442
- const batch = String(parsed.data.test_batch ?? basename3(filePath, extname(filePath))).trim();
7753
+ const batch = String(parsed.data.test_batch ?? basename4(filePath, extname(filePath))).trim();
7443
7754
  for (let i = 0; i < tcStarts.length; i++) {
7444
7755
  const start = tcStarts[i];
7445
7756
  const end = tcStarts[i + 1]?.index ?? lines.length;
@@ -7502,7 +7813,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7502
7813
  let hasActiveE2eRef = false;
7503
7814
  for (const entry of parseExecutableRefLines(execRef)) {
7504
7815
  const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
7505
- if (!normalized || !existsSync2(join7(root, normalized))) continue;
7816
+ if (!normalized || !existsSync3(join8(root, normalized))) continue;
7506
7817
  const accepting = await getAcceptingRunners(root, normalized, runners);
7507
7818
  if (accepting.some((runner) => runner.kind === "e2e")) {
7508
7819
  hasActiveE2eRef = true;
@@ -7561,7 +7872,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7561
7872
  const acCoverageRateByFeature = {};
7562
7873
  const featureAcMap = /* @__PURE__ */ new Map();
7563
7874
  for (const node of featureNodes) {
7564
- const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
7875
+ const acs = parseAcceptanceCriteria(await readFile3(join8(root, node.path), "utf-8"));
7565
7876
  featureAcMap.set(node.code, new Set(acs));
7566
7877
  }
7567
7878
  const coveredAcByFeature = /* @__PURE__ */ new Map();
@@ -7603,7 +7914,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7603
7914
  };
7604
7915
  }
7605
7916
  async function generateE2eRegistry(root, opts) {
7606
- const e2eDir = join7(root, "artifacts", "tests", "e2e");
7917
+ const e2eDir = join8(root, "artifacts", "tests", "e2e");
7607
7918
  let files;
7608
7919
  try {
7609
7920
  files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
@@ -7619,11 +7930,11 @@ async function generateE2eRegistry(root, opts) {
7619
7930
  const batches = [];
7620
7931
  let totalTestCases = 0;
7621
7932
  for (const file of files) {
7622
- const filePath = join7(e2eDir, file);
7933
+ const filePath = join8(e2eDir, file);
7623
7934
  const raw = await readFile3(filePath, "utf-8");
7624
7935
  const parsed = matter(raw);
7625
7936
  const data = parsed.data;
7626
- const batch = String(data.test_batch ?? basename3(file, extname(file))).trim();
7937
+ const batch = String(data.test_batch ?? basename4(file, extname(file))).trim();
7627
7938
  const relPath = `artifacts/tests/e2e/${file}`;
7628
7939
  const scope = String(data.scope ?? "").trim();
7629
7940
  const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
@@ -7747,7 +8058,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
7747
8058
  if (!normalizedPath) {
7748
8059
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7749
8060
  }
7750
- const fullPath = join7(root, normalizedPath);
8061
+ const fullPath = join8(root, normalizedPath);
7751
8062
  let content;
7752
8063
  try {
7753
8064
  content = await readFile3(fullPath, "utf-8");
@@ -7859,7 +8170,7 @@ function escapeRegExp(value) {
7859
8170
  }
7860
8171
  async function hasMarkdownTc(tcKey, e2eDir) {
7861
8172
  const [batch, tcId] = tcKey.split(":");
7862
- const filePath = join7(e2eDir, `${batch}.md`);
8173
+ const filePath = join8(e2eDir, `${batch}.md`);
7863
8174
  try {
7864
8175
  const raw = await readFile3(filePath, "utf-8");
7865
8176
  const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
@@ -8092,7 +8403,7 @@ function normalizeDesignCode(value) {
8092
8403
  if (!raw) {
8093
8404
  return "";
8094
8405
  }
8095
- return basename3(raw, extname(raw));
8406
+ return basename4(raw, extname(raw));
8096
8407
  }
8097
8408
  function toUid(type, code) {
8098
8409
  return `${type}:${code}`;
@@ -8357,14 +8668,14 @@ function resolveArtifactContext(graph, opts) {
8357
8668
  }
8358
8669
  if (root) {
8359
8670
  for (const ap of ALWAYS_PRESENT_ITEMS) {
8360
- const fullPath = join7(root, ap.path);
8361
- let stat;
8671
+ const fullPath = join8(root, ap.path);
8672
+ let stat2;
8362
8673
  try {
8363
- stat = statSync(fullPath);
8674
+ stat2 = statSync(fullPath);
8364
8675
  } catch {
8365
- stat = null;
8676
+ stat2 = null;
8366
8677
  }
8367
- if (!stat) {
8678
+ if (!stat2) {
8368
8679
  const msg = `Required baseline artifact not found: ${ap.path}`;
8369
8680
  if (!missing.includes(msg)) {
8370
8681
  missing.push(msg);
@@ -8376,7 +8687,7 @@ function resolveArtifactContext(graph, opts) {
8376
8687
  suggestedAction: `\u521B\u5EFA\u6587\u4EF6 ${ap.path} \u6216\u914D\u7F6E\u8DF3\u8FC7 universal baseline`
8377
8688
  });
8378
8689
  }
8379
- } else if (!stat.isFile()) {
8690
+ } else if (!stat2.isFile()) {
8380
8691
  const msg = `Required baseline artifact is not a regular file: ${ap.path}`;
8381
8692
  if (!missing.includes(msg)) {
8382
8693
  missing.push(msg);
@@ -8700,7 +9011,7 @@ var init_index = __esm({
8700
9011
 
8701
9012
  // src/packet-prompt-audit.ts
8702
9013
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
8703
- import { join as join8 } from "path";
9014
+ import { join as join9 } from "path";
8704
9015
  function promptFilename(target) {
8705
9016
  return `prompt-${target.type}-${target.id}.md`;
8706
9017
  }
@@ -8752,7 +9063,7 @@ async function auditSinglePromptTarget(target, graph, options) {
8752
9063
  }
8753
9064
  if (options.outDir) {
8754
9065
  const filename = promptFilename(target);
8755
- const outPath = join8(options.outDir, filename);
9066
+ const outPath = join9(options.outDir, filename);
8756
9067
  await writeFile4(outPath, prompt, "utf-8");
8757
9068
  entry.outputPath = outPath;
8758
9069
  }
@@ -8858,9 +9169,9 @@ async function auditPromptBatch(root, targets, options, graph) {
8858
9169
  };
8859
9170
  if (options.outDir) {
8860
9171
  await mkdir5(options.outDir, { recursive: true });
8861
- const jsonPath = join8(options.outDir, "prompt-audit-summary.json");
9172
+ const jsonPath = join9(options.outDir, "prompt-audit-summary.json");
8862
9173
  await writeFile4(jsonPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
8863
- const mdPath = join8(options.outDir, "prompt-audit-summary.md");
9174
+ const mdPath = join9(options.outDir, "prompt-audit-summary.md");
8864
9175
  await writeFile4(mdPath, renderPromptAuditSummaryMarkdown(summary), "utf-8");
8865
9176
  }
8866
9177
  return summary;
@@ -8894,22 +9205,649 @@ var init_packet_prompt_audit = __esm({
8894
9205
  }
8895
9206
  });
8896
9207
 
9208
+ // src/refactor-id.ts
9209
+ var refactor_id_exports = {};
9210
+ __export(refactor_id_exports, {
9211
+ refactorId: () => refactorId,
9212
+ renderRefactorIdMarkdown: () => renderRefactorIdMarkdown
9213
+ });
9214
+ import { chmod, readFile as readFile4, rename as rename2, rm, readdir as readdir4, stat, writeFile as writeFile5 } from "fs/promises";
9215
+ import { randomBytes } from "crypto";
9216
+ import { basename as basename5, dirname as dirname6, join as join10, relative as relative4 } from "path";
9217
+ import yaml2 from "js-yaml";
9218
+ function escapeRegExp2(value) {
9219
+ return value.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
9220
+ }
9221
+ function idTokenPattern(id) {
9222
+ return new RegExp(`(?<![\\w-])${escapeRegExp2(id)}(?![\\w-])`, "g");
9223
+ }
9224
+ function replaceIdToken(text, oldId, newId) {
9225
+ return text.replace(idTokenPattern(oldId), newId);
9226
+ }
9227
+ function containsIdToken(text, id) {
9228
+ return idTokenPattern(id).test(text);
9229
+ }
9230
+ function lineEndingOf(line) {
9231
+ if (line.endsWith("\r\n")) return "\r\n";
9232
+ if (line.endsWith("\n")) return "\n";
9233
+ if (line.endsWith("\r")) return "\r";
9234
+ return "";
9235
+ }
9236
+ function splitLinesPreserveEndings(text) {
9237
+ const lines = [];
9238
+ const length = text.length;
9239
+ let index = 0;
9240
+ while (index < length) {
9241
+ let cursor = index;
9242
+ while (cursor < length && text[cursor] !== "\r" && text[cursor] !== "\n") cursor += 1;
9243
+ let end = cursor;
9244
+ if (cursor < length) {
9245
+ end = text[cursor] === "\r" && text[cursor + 1] === "\n" ? cursor + 2 : cursor + 1;
9246
+ }
9247
+ lines.push(text.slice(index, end));
9248
+ index = end;
9249
+ if (cursor >= length) break;
9250
+ }
9251
+ return lines;
9252
+ }
9253
+ function sortEdges(edges) {
9254
+ return edges.map((edge2) => ({ from: edge2.from, to: edge2.to, kind: edge2.kind, source: edge2.source, sourcePath: edge2.sourcePath, sourceLine: edge2.sourceLine })).sort((a, b) => a.sourcePath.localeCompare(b.sourcePath) || a.sourceLine - b.sourceLine || a.from.localeCompare(b.from) || a.kind.localeCompare(b.kind));
9255
+ }
9256
+ function frontmatterRelationFields(type, config) {
9257
+ const fields = /* @__PURE__ */ new Set();
9258
+ const declared = config.relationFields?.[type];
9259
+ if (declared) {
9260
+ for (const field of declared) {
9261
+ if (!field.startsWith("@")) fields.add(field);
9262
+ }
9263
+ }
9264
+ if (type === "feature") {
9265
+ for (const field of ["scenarios", "decisions", "depends_on", "design_docs"]) fields.add(field);
9266
+ }
9267
+ if (type === "decision") {
9268
+ for (const field of ["related_features", "related_scenarios"]) fields.add(field);
9269
+ }
9270
+ if (type === "design") {
9271
+ for (const field of ["related_features", "related_scenarios", "related_decisions"]) fields.add(field);
9272
+ }
9273
+ if (!declared) {
9274
+ fields.add("related_*");
9275
+ }
9276
+ return fields;
9277
+ }
9278
+ function fieldWhitelisted(fieldName, whitelist) {
9279
+ if (whitelist.has(fieldName)) return true;
9280
+ if (whitelist.has("related_*") && fieldName.startsWith("related_")) return true;
9281
+ return false;
9282
+ }
9283
+ function findFrontmatterBlock(lines) {
9284
+ if (lines[0]?.trim() !== "---") return null;
9285
+ for (let index = 1; index < lines.length; index += 1) {
9286
+ if (lines[index].trim() === "---") {
9287
+ return { startLine: 0, endLine: index, text: lines.slice(1, index).join("\n") };
9288
+ }
9289
+ }
9290
+ return null;
9291
+ }
9292
+ function artifactTypeOfPath(path, config) {
9293
+ for (const [type, definition] of Object.entries(config.types)) {
9294
+ for (const pattern of definition.paths) {
9295
+ if (globLikeMatch(pattern, path)) return type;
9296
+ }
9297
+ }
9298
+ return null;
9299
+ }
9300
+ function globLikeMatch(pattern, path) {
9301
+ const regex = pattern.split("/").map((segment) => {
9302
+ if (segment === "**") return "<<DOUBLESTAR>>";
9303
+ return segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
9304
+ }).join("/");
9305
+ const flattened = regex.replace(/<<DOUBLESTAR>>\//g, "(?:[^/]+/)*").replace(/\/<<DOUBLESTAR>>/g, "(?:/[^/]+)*").replace(/<<DOUBLESTAR>>/g, ".*");
9306
+ return new RegExp(`^${flattened}$`).test(path);
9307
+ }
9308
+ function planFrontmatterEdits(path, raw, fileType, oldId, newId, config, isOwnFile) {
9309
+ const lines = raw.split(/\r?\n/);
9310
+ const block = findFrontmatterBlock(lines);
9311
+ if (!block) return [];
9312
+ let data;
9313
+ try {
9314
+ const loaded = yaml2.load(block.text);
9315
+ if (typeof loaded !== "object" || loaded === null || Array.isArray(loaded)) return [];
9316
+ data = loaded;
9317
+ } catch {
9318
+ return [];
9319
+ }
9320
+ const whitelist = frontmatterRelationFields(fileType, config);
9321
+ const edits = [];
9322
+ let currentField = null;
9323
+ let inEligibleField = false;
9324
+ const eligibleByValue = /* @__PURE__ */ new Set();
9325
+ for (const [fieldName, fieldValue] of Object.entries(data)) {
9326
+ if (fieldName === "title" || fieldName === "status") continue;
9327
+ if (fieldName === "id") continue;
9328
+ if (!fieldWhitelisted(fieldName, whitelist)) continue;
9329
+ const serialized = JSON.stringify(fieldValue ?? null);
9330
+ if (containsIdToken(serialized.replace(/\\u/g, "u"), oldId) || serialized.includes(`"${oldId}"`)) {
9331
+ eligibleByValue.add(fieldName);
9332
+ }
9333
+ }
9334
+ for (let index = block.startLine + 1; index < block.endLine; index += 1) {
9335
+ const line = lines[index];
9336
+ const topLevel = /^([A-Za-z_][\w-]*):(.*)$/.exec(line);
9337
+ if (topLevel) {
9338
+ currentField = topLevel[1];
9339
+ const valueText = topLevel[2];
9340
+ if (currentField === "id" && isOwnFile && valueText.trim().replace(/^["']|["']$/g, "") === oldId) {
9341
+ const newLine = line.replace(valueText, ` ${newId}`);
9342
+ edits.push({ path, line: index + 1, oldLine: line, newLine, kind: "frontmatter-id" });
9343
+ inEligibleField = false;
9344
+ continue;
9345
+ }
9346
+ inEligibleField = currentField !== null && eligibleByValue.has(currentField);
9347
+ if (inEligibleField && containsIdToken(valueText, oldId)) {
9348
+ edits.push({ path, line: index + 1, oldLine: line, newLine: replaceIdToken(line, oldId, newId), kind: "frontmatter-relation" });
9349
+ }
9350
+ continue;
9351
+ }
9352
+ if (!inEligibleField || currentField === null) continue;
9353
+ if (/^\S/.test(line)) {
9354
+ inEligibleField = false;
9355
+ currentField = null;
9356
+ continue;
9357
+ }
9358
+ if (!containsIdToken(line, oldId)) continue;
9359
+ if (currentField === "ac_coverage") {
9360
+ const keyMatch = /^(\s+)([^:\s]+)(\s*[::].*)$/.exec(line);
9361
+ if (keyMatch && keyMatch[2] === oldId) {
9362
+ edits.push({ path, line: index + 1, oldLine: line, newLine: `${keyMatch[1]}${newId}${keyMatch[3]}`, kind: "frontmatter-relation" });
9363
+ }
9364
+ continue;
9365
+ }
9366
+ edits.push({ path, line: index + 1, oldLine: line, newLine: replaceIdToken(line, oldId, newId), kind: "frontmatter-relation" });
9367
+ }
9368
+ return edits;
9369
+ }
9370
+ function planE2eBlockEdits(path, raw, oldId, newId) {
9371
+ const lines = raw.split(/\r?\n/);
9372
+ const edits = [];
9373
+ let tcHeadingIndex = -1;
9374
+ lines.forEach((line, index) => {
9375
+ if (/^#{2,3}\s+TC-\d+[a-z]?\s*[::]?\s*/.test(line)) {
9376
+ tcHeadingIndex = index;
9377
+ return;
9378
+ }
9379
+ if (tcHeadingIndex < 0) return;
9380
+ const field = /^\*\*(覆盖场景|覆盖功能)\*\*\s*[::]\s*(.*)$/.exec(line);
9381
+ if (field && containsIdToken(line, oldId)) {
9382
+ edits.push({ path, line: index + 1, oldLine: line, newLine: replaceIdToken(line, oldId, newId), kind: "markdown-relation" });
9383
+ }
9384
+ });
9385
+ return edits;
9386
+ }
9387
+ async function walkTextFiles(root) {
9388
+ const found = [];
9389
+ async function visit(dir) {
9390
+ const entries = await readdir4(dir, { withFileTypes: true });
9391
+ for (const entry of entries) {
9392
+ if (entry.name.startsWith(".") && PROSE_SCAN_SKIP_DIRS.has(entry.name)) continue;
9393
+ if (PROSE_SCAN_SKIP_DIRS.has(entry.name)) continue;
9394
+ const full = join10(dir, entry.name);
9395
+ if (entry.isDirectory()) {
9396
+ await visit(full);
9397
+ } else if (entry.isFile()) {
9398
+ const ext = entry.name.includes(".") ? `.${entry.name.split(".").slice(-1)[0]}` : "";
9399
+ if (TEXT_EXTENSIONS.has(ext.toLowerCase())) {
9400
+ found.push(relative4(root, full).replace(/\\/g, "/"));
9401
+ }
9402
+ }
9403
+ }
9404
+ }
9405
+ await visit(root);
9406
+ return found.sort();
9407
+ }
9408
+ async function computeLockImpact(root, type, oldId) {
9409
+ const lockPath = join10(root, VERSION_LOCK_PATH);
9410
+ const impact = {
9411
+ lockPath: VERSION_LOCK_PATH,
9412
+ affectedImplementationLocks: [],
9413
+ affectedRelationLocks: [],
9414
+ suggestion: "artifact-graph version-lock refresh --root . --changed-only --worktree"
9415
+ };
9416
+ let raw;
9417
+ try {
9418
+ raw = await readFile4(lockPath, "utf-8");
9419
+ } catch {
9420
+ return impact;
9421
+ }
9422
+ let lockFile;
9423
+ try {
9424
+ lockFile = JSON.parse(raw);
9425
+ } catch {
9426
+ return impact;
9427
+ }
9428
+ for (const lock of lockFile.locks ?? []) {
9429
+ if (lock.artifact && lock.artifact.type === type && lock.artifact.id === oldId) {
9430
+ impact.affectedImplementationLocks.push(lock.edgeId);
9431
+ }
9432
+ }
9433
+ for (const relation of lockFile.artifactRelations ?? []) {
9434
+ const endpoints = [relation.source, relation.target];
9435
+ if (endpoints.some((endpoint) => endpoint && endpoint.type === type && endpoint.id === oldId)) {
9436
+ impact.affectedRelationLocks.push(relation.edgeId);
9437
+ }
9438
+ }
9439
+ impact.affectedImplementationLocks.sort();
9440
+ impact.affectedRelationLocks.sort();
9441
+ return impact;
9442
+ }
9443
+ async function refactorId(options) {
9444
+ const root = options.root;
9445
+ const fail = (code, message, details) => ({
9446
+ ok: false,
9447
+ error: { code, message, ...details !== void 0 ? { details } : {} }
9448
+ });
9449
+ const colonIndex = options.from.indexOf(":");
9450
+ const config = await loadConfig(root);
9451
+ if (colonIndex <= 0) {
9452
+ const graph2 = await scanArtifacts(root, config);
9453
+ const candidates = graph2.nodes.filter((node) => node.code === options.from).map((node) => `${node.type}:${node.code}`);
9454
+ if (candidates.length === 0) {
9455
+ return fail("TARGET_MISSING", `\u65E7\u6807\u8BC6 "${options.from}" \u4E0D\u5B58\u5728\uFF1B--from \u5FC5\u987B\u4F7F\u7528\u7CBE\u786E type:id \u5F62\u5F0F`);
9456
+ }
9457
+ return fail("BARE_ID_AMBIGUOUS", `--from \u5FC5\u987B\u4F7F\u7528\u7CBE\u786E type:id \u5F62\u5F0F\uFF1B\u88F8\u7F16\u53F7 "${options.from}" \u5019\u9009\uFF1A${candidates.join(", ")}`, { candidates });
9458
+ }
9459
+ const typeToken = options.from.slice(0, colonIndex);
9460
+ const oldId = options.from.slice(colonIndex + 1);
9461
+ const resolvedType = resolveArtifactTypeName(config, typeToken);
9462
+ if (!resolvedType) {
9463
+ return fail("UNKNOWN_TYPE", `\u672A\u77E5\u5236\u54C1\u7C7B\u578B "${typeToken}"`);
9464
+ }
9465
+ if (!oldId) {
9466
+ return fail("TARGET_MISSING", `--from \u7F3A\u5C11 ID \u90E8\u5206\uFF1A${options.from}`);
9467
+ }
9468
+ if (resolvedType !== "feature" && resolvedType !== "decision" && resolvedType !== "scenario") {
9469
+ return fail(
9470
+ "UNSUPPORTED_RENAME_TARGET",
9471
+ `\u9996\u7248\u53EA\u652F\u6301 frontmatter/\u8282\u6807\u9898\u627F\u8F7D ID \u7684\u7C7B\u578B\u6539\u540D\uFF08feature/decision/scenario\uFF09\uFF1B${resolvedType} \u7684 ID \u7531\u8DEF\u5F84\u6D3E\u751F\uFF0C\u6539\u540D\u7B49\u4EF7\u4E8E\u6587\u4EF6\u6539\u540D\uFF0C\u4E0D\u5728\u9996\u7248\u8303\u56F4`
9472
+ );
9473
+ }
9474
+ const newId = options.to.trim();
9475
+ if (!newId || newId.includes(":")) {
9476
+ return fail("INVALID_NEW_ID", "--to \u5FC5\u987B\u662F\u4E0D\u542B\u7C7B\u578B\u524D\u7F00\u7684\u65B0\u7F16\u53F7");
9477
+ }
9478
+ const idPattern = config.idPatterns?.[resolvedType];
9479
+ if (idPattern && !new RegExp(idPattern).test(newId)) {
9480
+ return fail("INVALID_NEW_ID", `\u65B0 ID "${newId}" \u4E0D\u7B26\u5408 ${resolvedType} \u7684 idPattern\uFF1A${idPattern}`);
9481
+ }
9482
+ if (newId === oldId) {
9483
+ return fail("INVALID_NEW_ID", "\u65B0 ID \u4E0E\u65E7 ID \u76F8\u540C");
9484
+ }
9485
+ const graph = await scanArtifacts(root, config);
9486
+ const oldUid = `${resolvedType}:${oldId}`;
9487
+ const newUid = `${resolvedType}:${newId}`;
9488
+ const oldNode = graph.nodes.find((node) => node.uid === oldUid);
9489
+ if (!oldNode) {
9490
+ return fail("TARGET_MISSING", `\u65E7\u6807\u8BC6 ${oldUid} \u5728\u56FE\u4E2D\u4E0D\u5B58\u5728`);
9491
+ }
9492
+ if (graph.nodes.some((node) => node.uid === newUid)) {
9493
+ return fail("ID_CONFLICT", `\u65B0 ID ${newUid} \u5728\u56FE\u4E2D\u5DF2\u5B58\u5728`);
9494
+ }
9495
+ const incoming = graph.edges.filter((edge2) => edge2.to === oldUid);
9496
+ const outgoing = graph.edges.filter((edge2) => edge2.from === oldUid);
9497
+ const edits = [];
9498
+ const fileContents = /* @__PURE__ */ new Map();
9499
+ const readArtifactFile = async (path) => {
9500
+ if (!fileContents.has(path)) {
9501
+ fileContents.set(path, await readFile4(join10(root, path), "utf-8"));
9502
+ }
9503
+ return fileContents.get(path);
9504
+ };
9505
+ if (resolvedType === "scenario") {
9506
+ const raw = await readArtifactFile(oldNode.path);
9507
+ const lines = raw.split(/\r?\n/);
9508
+ const headingIndex = lines.findIndex((line) => {
9509
+ const match = /^#{2,3}\s+(S-\d+[a-z]?)\s*[::]/.exec(line);
9510
+ return match !== null && match[1] === oldId;
9511
+ });
9512
+ if (headingIndex < 0) {
9513
+ return fail("PLAN_DRIFT", `\u573A\u666F ${oldUid} \u7684\u8282\u6807\u9898\u5728 ${oldNode.path} \u4E2D\u672A\u627E\u5230`);
9514
+ }
9515
+ edits.push({
9516
+ path: oldNode.path,
9517
+ line: headingIndex + 1,
9518
+ oldLine: lines[headingIndex],
9519
+ newLine: replaceIdToken(lines[headingIndex], oldId, newId),
9520
+ kind: "frontmatter-id"
9521
+ });
9522
+ } else {
9523
+ edits.push(...planFrontmatterEdits(oldNode.path, await readArtifactFile(oldNode.path), resolvedType, oldId, newId, config, true));
9524
+ }
9525
+ const frontmatterTargets = /* @__PURE__ */ new Set();
9526
+ const lineEditsByFile = /* @__PURE__ */ new Map();
9527
+ for (const edge2 of incoming) {
9528
+ if (edge2.source === "frontmatter") {
9529
+ frontmatterTargets.add(edge2.sourcePath);
9530
+ continue;
9531
+ }
9532
+ const lines = lineEditsByFile.get(edge2.sourcePath) ?? /* @__PURE__ */ new Set();
9533
+ lines.add(edge2.sourceLine);
9534
+ lineEditsByFile.set(edge2.sourcePath, lines);
9535
+ }
9536
+ for (const path of [...frontmatterTargets].sort()) {
9537
+ if (path === oldNode.path) continue;
9538
+ const fileType = artifactTypeOfPath(path, config) ?? resolvedType;
9539
+ edits.push(...planFrontmatterEdits(path, await readArtifactFile(path), fileType, oldId, newId, config, false));
9540
+ }
9541
+ for (const [path, lineNumbers] of [...lineEditsByFile.entries()].sort(([a], [b]) => a.localeCompare(b))) {
9542
+ const raw = await readArtifactFile(path);
9543
+ const lines = raw.split(/\r?\n/);
9544
+ const isE2eFile = artifactTypeOfPath(path, config) === "e2e_test";
9545
+ if (isE2eFile) {
9546
+ edits.push(...planE2eBlockEdits(path, raw, oldId, newId));
9547
+ for (const lineNumber of [...lineNumbers].sort((a, b) => a - b)) {
9548
+ const line = lines[lineNumber - 1];
9549
+ if (line === void 0) continue;
9550
+ if (/^#{2,3}\s+TC-\d+/.test(line)) continue;
9551
+ if (containsIdToken(line, oldId)) {
9552
+ edits.push({ path, line: lineNumber, oldLine: line, newLine: replaceIdToken(line, oldId, newId), kind: "markdown-relation" });
9553
+ }
9554
+ }
9555
+ continue;
9556
+ }
9557
+ for (const lineNumber of [...lineNumbers].sort((a, b) => a - b)) {
9558
+ const line = lines[lineNumber - 1];
9559
+ if (line === void 0 || !containsIdToken(line, oldId)) continue;
9560
+ const isComment = /^\s*(\/\/|<!--)/.test(line);
9561
+ edits.push({
9562
+ path,
9563
+ line: lineNumber,
9564
+ oldLine: line,
9565
+ newLine: replaceIdToken(line, oldId, newId),
9566
+ kind: isComment ? "traceability-comment" : "markdown-relation"
9567
+ });
9568
+ }
9569
+ }
9570
+ edits.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line);
9571
+ const structuredPositions = new Set(edits.map((edit) => `${edit.path}:${edit.line}`));
9572
+ const proseHits = [];
9573
+ const approveSet = new Set((options.approveProse ?? []).map((entry) => entry.trim()));
9574
+ for (const relativePath of await walkTextFiles(root)) {
9575
+ if (relativePath === VERSION_LOCK_PATH) continue;
9576
+ if (relativePath === "artifact-graph.config.yaml") continue;
9577
+ const raw = await readFile4(join10(root, relativePath), "utf-8");
9578
+ if (!containsIdToken(raw, oldId)) continue;
9579
+ const lines = raw.split(/\r?\n/);
9580
+ lines.forEach((line, index) => {
9581
+ if (!containsIdToken(line, oldId)) return;
9582
+ if (structuredPositions.has(`${relativePath}:${index + 1}`)) return;
9583
+ proseHits.push({ path: relativePath, line: index + 1, context: line.trim().slice(0, 160), approved: false });
9584
+ });
9585
+ }
9586
+ proseHits.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line);
9587
+ const proseKeys = new Set(proseHits.map((hit) => `${hit.path}:${hit.line}`));
9588
+ const unknownApprovals = [...approveSet].filter((entry) => entry && !proseKeys.has(entry)).sort();
9589
+ if (unknownApprovals.length > 0) {
9590
+ return fail("APPROVE_PROSE_MISMATCH", "--approve-prose \u5305\u542B\u4E0E\u5B9E\u9645 prose \u547D\u4E2D\u4E0D\u5339\u914D\u7684\u6761\u76EE", { unknownApprovals });
9591
+ }
9592
+ for (const hit of proseHits) {
9593
+ hit.approved = approveSet.has(`${hit.path}:${hit.line}`);
9594
+ if (hit.approved) {
9595
+ const raw = await readArtifactFile(hit.path);
9596
+ const oldLine = raw.split(/\r?\n/)[hit.line - 1] ?? "";
9597
+ edits.push({
9598
+ path: hit.path,
9599
+ line: hit.line,
9600
+ oldLine,
9601
+ newLine: replaceIdToken(oldLine, oldId, newId),
9602
+ kind: "prose-approved"
9603
+ });
9604
+ }
9605
+ }
9606
+ edits.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line);
9607
+ const lockImpact = await computeLockImpact(root, resolvedType, oldId);
9608
+ const targetFiles = [...new Set(edits.map((edit) => edit.path))].sort();
9609
+ const plan = {
9610
+ from: oldUid,
9611
+ to: newUid,
9612
+ type: resolvedType,
9613
+ oldSummary: { type: resolvedType, id: oldId, title: oldNode.title, status: oldNode.status, path: oldNode.path },
9614
+ targetFiles,
9615
+ edits,
9616
+ incomingEdges: sortEdges(incoming),
9617
+ outgoingEdges: sortEdges(outgoing),
9618
+ proseHits,
9619
+ lockImpact
9620
+ };
9621
+ if (!options.apply) {
9622
+ return { ok: true, mode: "dry-run", plan };
9623
+ }
9624
+ const newContents = /* @__PURE__ */ new Map();
9625
+ for (const path of targetFiles) {
9626
+ const original = fileContents.has(path) ? fileContents.get(path) : await readArtifactFile(path);
9627
+ const linesWithEndings = splitLinesPreserveEndings(original);
9628
+ const fileEdits = edits.filter((edit) => edit.path === path);
9629
+ for (const edit of fileEdits) {
9630
+ const currentIndex = edit.line - 1;
9631
+ const element = linesWithEndings[currentIndex];
9632
+ if (element === void 0) {
9633
+ return fail("PLAN_DRIFT", `\u8BA1\u5212\u4E0E\u6587\u4EF6\u5185\u5BB9\u4E0D\u4E00\u81F4\uFF08apply \u4E2D\u6B62\uFF0C\u96F6\u5199\u5165\uFF09\uFF1A${path}:${edit.line}`, { path, line: edit.line });
9634
+ }
9635
+ const ending = lineEndingOf(element);
9636
+ const content = element.slice(0, element.length - ending.length);
9637
+ if (content !== edit.oldLine) {
9638
+ return fail("PLAN_DRIFT", `\u8BA1\u5212\u4E0E\u6587\u4EF6\u5185\u5BB9\u4E0D\u4E00\u81F4\uFF08apply \u4E2D\u6B62\uFF0C\u96F6\u5199\u5165\uFF09\uFF1A${path}:${edit.line}`, { path, line: edit.line });
9639
+ }
9640
+ linesWithEndings[currentIndex] = `${edit.newLine}${ending}`;
9641
+ }
9642
+ const updated = linesWithEndings.join("");
9643
+ for (const edit of fileEdits) {
9644
+ const applied = updated.split(/\r?\n/)[edit.line - 1];
9645
+ if (containsIdToken(applied ?? "", oldId)) {
9646
+ return fail("WHITELIST_RESIDUE", `\u767D\u540D\u5355\u4F4D\u7F6E\u66FF\u6362\u6821\u9A8C\u5931\u8D25\uFF08apply \u4E2D\u6B62\uFF0C\u96F6\u5199\u5165\uFF09\uFF1A${path}:${edit.line}`);
9647
+ }
9648
+ }
9649
+ newContents.set(path, updated);
9650
+ }
9651
+ if (options.__beforeWriteVerify) await options.__beforeWriteVerify();
9652
+ for (const path of targetFiles) {
9653
+ const disk = await readFile4(join10(root, path), "utf-8");
9654
+ if (disk !== fileContents.get(path)) {
9655
+ return fail("PLAN_DRIFT", `\u8BA1\u5212\u4E0E\u5199\u5165\u4E4B\u95F4\u76EE\u6807\u6587\u4EF6\u53D1\u751F\u53D8\u5316\uFF08apply \u4E2D\u6B62\uFF0C\u96F6\u5199\u5165\uFF09\uFF1A${path}`, { path });
9656
+ }
9657
+ }
9658
+ const written = [];
9659
+ const createdTempPaths = [];
9660
+ let tempSequence = 0;
9661
+ const tempPathFor = options.__tempPathFor ?? ((targetPath) => {
9662
+ tempSequence += 1;
9663
+ const suffix = randomBytes(4).toString("hex");
9664
+ return join10(dirname6(targetPath), `.${basename5(targetPath)}.${process.pid}.${tempSequence}.${suffix}.refactor-id.tmp`);
9665
+ });
9666
+ let stage = "write";
9667
+ try {
9668
+ for (const path of targetFiles) {
9669
+ stage = "write";
9670
+ if (options.__injectWriteFailure?.(path)) {
9671
+ throw Object.assign(new Error(`injected write failure: ${path}`), { code: "EINJECTED" });
9672
+ }
9673
+ const original = fileContents.has(path) ? fileContents.get(path) : await readFile4(join10(root, path), "utf-8");
9674
+ const targetPath = join10(root, path);
9675
+ const originalMode = (await stat(targetPath)).mode & 4095;
9676
+ const tempPath = tempPathFor(targetPath);
9677
+ await writeFile5(tempPath, newContents.get(path), { encoding: "utf-8", flag: "wx" });
9678
+ createdTempPaths.push(tempPath);
9679
+ await chmod(tempPath, originalMode);
9680
+ stage = "rename";
9681
+ if (options.__injectRenameFailure?.(tempPath, targetPath)) {
9682
+ throw Object.assign(new Error(`injected rename failure: ${tempPath} -> ${targetPath}`), { code: "ERENAMEINJECTED" });
9683
+ }
9684
+ await rename2(tempPath, targetPath);
9685
+ written.push({ path, original, originalMode });
9686
+ }
9687
+ } catch (error) {
9688
+ for (const tempPath of createdTempPaths) {
9689
+ await rm(tempPath, { force: true });
9690
+ }
9691
+ const rollbackErrors = [];
9692
+ for (const entry of [...written].reverse()) {
9693
+ try {
9694
+ if (options.__injectRollbackFailure?.(entry.path)) {
9695
+ throw Object.assign(new Error(`injected rollback failure: ${entry.path}`), { code: "EROLLBACKINJECTED" });
9696
+ }
9697
+ await writeFile5(join10(root, entry.path), entry.original, "utf-8");
9698
+ await chmod(join10(root, entry.path), entry.originalMode);
9699
+ } catch (rollbackError) {
9700
+ rollbackErrors.push({ path: entry.path, message: rollbackError.message });
9701
+ }
9702
+ }
9703
+ const failure = error;
9704
+ const writeCode = failure.code === "EEXIST" ? "TEMP_FILE_COLLISION" : stage === "rename" || failure.code === "ERENAMEINJECTED" ? "RENAME_FAILED" : "WRITE_FAILED";
9705
+ if (rollbackErrors.length > 0) {
9706
+ return {
9707
+ ok: false,
9708
+ error: {
9709
+ code: "ROLLBACK_FAILED",
9710
+ message: `apply \u5199\u5165\u5931\u8D25\uFF08${writeCode}\uFF09\u4E14\u56DE\u6EDA\u672A\u5B8C\u5168\u6210\u529F\uFF0C\u9700\u4EBA\u5DE5\u590D\u6838\uFF1A${failure.message}`,
9711
+ details: { writeError: { code: writeCode, message: failure.message }, rollbackErrors }
9712
+ }
9713
+ };
9714
+ }
9715
+ return {
9716
+ ok: false,
9717
+ error: {
9718
+ code: writeCode,
9719
+ message: `apply \u5199\u5165\u5931\u8D25\u5E76\u5DF2\u56DE\u6EDA\uFF1A${failure.message}`,
9720
+ details: { stage }
9721
+ }
9722
+ };
9723
+ }
9724
+ const rescanGraph = await scanArtifacts(root, config);
9725
+ const residue = [
9726
+ ...rescanGraph.nodes.filter((node) => node.uid === oldUid)
9727
+ ];
9728
+ const residueEdges = rescanGraph.edges.filter((edge2) => edge2.to === oldUid || edge2.from === oldUid);
9729
+ const newNodePresent = rescanGraph.nodes.some((node) => node.uid === newUid);
9730
+ const mapEdgeForCompare = (edge2, renameFrom, renameTo) => `${edge2.from === renameFrom ? renameTo : edge2.from}->${edge2.to === renameFrom ? renameTo : edge2.to}:${edge2.kind}:${edge2.source}:${edge2.sourcePath}`;
9731
+ const oldIncomingKeys = sortEdges(incoming).map((edge2) => mapEdgeForCompare(edge2, oldUid, newUid)).sort();
9732
+ const newIncoming = sortEdges(rescanGraph.edges.filter((edge2) => edge2.to === newUid));
9733
+ const newIncomingKeys = newIncoming.map((edge2) => mapEdgeForCompare(edge2, oldUid, newUid)).sort();
9734
+ const incomingDiffs = symmetricDifference(oldIncomingKeys, newIncomingKeys);
9735
+ const oldOutgoingKeys = sortEdges(outgoing).map((edge2) => mapEdgeForCompare(edge2, oldUid, newUid)).sort();
9736
+ const newOutgoing = sortEdges(rescanGraph.edges.filter((edge2) => edge2.from === newUid));
9737
+ const newOutgoingKeys = newOutgoing.map((edge2) => mapEdgeForCompare(edge2, oldUid, newUid)).sort();
9738
+ const outgoingDiffs = symmetricDifference(oldOutgoingKeys, newOutgoingKeys);
9739
+ const structuredResidue = residue.length + residueEdges.length;
9740
+ const rescanResult = {
9741
+ structuredResidue,
9742
+ incomingEquivalence: incomingDiffs.length === 0 ? "equal" : incomingDiffs,
9743
+ outgoingEquivalence: outgoingDiffs.length === 0 ? "equal" : outgoingDiffs,
9744
+ newNodePresent
9745
+ };
9746
+ if (structuredResidue > 0 || incomingDiffs.length > 0 || outgoingDiffs.length > 0 || !newNodePresent) {
9747
+ return {
9748
+ ok: false,
9749
+ error: {
9750
+ code: "RESCAN_FAILED",
9751
+ message: "apply \u5DF2\u5B8C\u6210\u4F46\u6539\u540E\u8BC1\u660E\u672A\u901A\u8FC7\uFF1A\u8BF7\u4EBA\u5DE5\u590D\u6838\u7ED3\u6784\u5316\u6B8B\u7559\u4E0E\u5173\u7CFB\u7B49\u4EF7\u6027",
9752
+ details: rescanResult
9753
+ }
9754
+ };
9755
+ }
9756
+ return { ok: true, mode: "apply", plan, rescan: rescanResult };
9757
+ }
9758
+ function symmetricDifference(left, right) {
9759
+ const leftSet = new Set(left);
9760
+ const rightSet = new Set(right);
9761
+ const diffs = [];
9762
+ for (const item of leftSet) if (!rightSet.has(item)) diffs.push(`- ${item}`);
9763
+ for (const item of rightSet) if (!leftSet.has(item)) diffs.push(`+ ${item}`);
9764
+ return diffs.sort();
9765
+ }
9766
+ function renderRefactorIdMarkdown(result) {
9767
+ if (!result.ok) {
9768
+ return [`# refactor-id \u5931\u8D25`, "", `- \u9519\u8BEF\u7801\uFF1A${result.error.code}`, `- \u8BF4\u660E\uFF1A${result.error.message}`, ""].join("\n");
9769
+ }
9770
+ const { plan } = result;
9771
+ const lines = [];
9772
+ lines.push(`# refactor-id ${result.mode === "apply" ? "apply \u62A5\u544A" : "dry-run \u8BA1\u5212"}`);
9773
+ lines.push("");
9774
+ lines.push(`- \u65E7\u6807\u8BC6\uFF1A\`${plan.from}\``);
9775
+ lines.push(`- \u65B0\u6807\u8BC6\uFF1A\`${plan.to}\``);
9776
+ lines.push(`- \u65E7\u6458\u8981\uFF1A${plan.oldSummary.title}\uFF08status: ${plan.oldSummary.status ?? "\u672A\u58F0\u660E"}\uFF0C\u8DEF\u5F84\uFF1A${plan.oldSummary.path}\uFF09`);
9777
+ lines.push("");
9778
+ lines.push(`## \u76EE\u6807\u6587\u4EF6\uFF08${plan.targetFiles.length}\uFF09`);
9779
+ for (const file of plan.targetFiles) lines.push(`- ${file}`);
9780
+ lines.push("");
9781
+ lines.push(`## \u62DF\u8BAE diff\uFF08${plan.edits.length} \u5904\uFF09`);
9782
+ for (const edit of plan.edits) {
9783
+ lines.push(`- ${edit.path}:${edit.line} [${edit.kind}]`);
9784
+ lines.push(` - \u65E7\uFF1A${edit.oldLine.trim()}`);
9785
+ lines.push(` - \u65B0\uFF1A${edit.newLine.trim()}`);
9786
+ }
9787
+ lines.push("");
9788
+ lines.push(`## \u5165\u8FB9\uFF08${plan.incomingEdges.length}\uFF09`);
9789
+ for (const edge2 of plan.incomingEdges) lines.push(`- ${edge2.from} --${edge2.kind}--> \uFF08${edge2.sourcePath}:${edge2.sourceLine}\uFF0C\u6765\u6E90 ${edge2.source}\uFF09`);
9790
+ lines.push("");
9791
+ lines.push(`## \u51FA\u8FB9\uFF08${plan.outgoingEdges.length}\uFF09`);
9792
+ for (const edge2 of plan.outgoingEdges) lines.push(`- \uFF08${edge2.sourcePath}\uFF09--${edge2.kind}--> ${edge2.to}`);
9793
+ lines.push("");
9794
+ lines.push(`## prose \u7591\u4F3C\u547D\u4E2D\uFF08${plan.proseHits.length}\uFF0C\u9ED8\u8BA4\u53EA\u62A5\u544A\u4E0D\u66FF\u6362\uFF09`);
9795
+ for (const hit of plan.proseHits) {
9796
+ lines.push(`- ${hit.path}:${hit.line}${hit.approved ? "\uFF08\u5DF2\u6279\u51C6\u66FF\u6362\uFF09" : ""}\uFF1A${hit.context}`);
9797
+ }
9798
+ lines.push("");
9799
+ lines.push("## \u7248\u672C\u9501\u5F71\u54CD");
9800
+ lines.push(`- \u53D7\u5F71\u54CD\u5B9E\u73B0\u9501\uFF1A${plan.lockImpact.affectedImplementationLocks.length} \u6761`);
9801
+ lines.push(`- \u53D7\u5F71\u54CD\u5173\u7CFB\u9501\uFF1A${plan.lockImpact.affectedRelationLocks.length} \u6761`);
9802
+ lines.push(`- \u672C\u547D\u4EE4\u4E0D\u81EA\u52A8\u5237\u65B0\u9501\uFF1Bapply \u6210\u529F\u540E\u8BF7\u4EBA\u5DE5\u5BA1\u67E5\u5E76\u8FD0\u884C\uFF1A\`${plan.lockImpact.suggestion}\``);
9803
+ if (result.mode === "apply" && result.rescan) {
9804
+ lines.push("");
9805
+ lines.push("## \u6539\u540E\u8BC1\u660E");
9806
+ lines.push(`- \u65E7 ID \u7ED3\u6784\u5316\u6B8B\u7559\uFF1A${result.rescan.structuredResidue}`);
9807
+ lines.push(`- \u5165\u8FB9\u96C6\u5408\u7B49\u4EF7\uFF1A${result.rescan.incomingEquivalence === "equal" ? "\u662F" : "\u5426"}`);
9808
+ lines.push(`- \u51FA\u8FB9\u96C6\u5408\u7B49\u4EF7\uFF1A${result.rescan.outgoingEquivalence === "equal" ? "\u662F" : "\u5426"}`);
9809
+ lines.push(`- \u65B0\u8282\u70B9\u5B58\u5728\uFF1A${result.rescan.newNodePresent ? "\u662F" : "\u5426"}`);
9810
+ }
9811
+ lines.push("");
9812
+ return lines.join("\n");
9813
+ }
9814
+ var TEXT_EXTENSIONS, PROSE_SCAN_SKIP_DIRS;
9815
+ var init_refactor_id = __esm({
9816
+ "src/refactor-id.ts"() {
9817
+ "use strict";
9818
+ init_index();
9819
+ TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".ts", ".tsx", ".mts", ".mjs", ".js", ".yaml", ".yml", ".json"]);
9820
+ PROSE_SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".tmp", ".codex", ".worktrees", ".qoder", ".agents"]);
9821
+ }
9822
+ });
9823
+
8897
9824
  // src/cli.ts
8898
9825
  var cli_exports = {};
8899
9826
  __export(cli_exports, {
8900
9827
  runCli: () => runCli
8901
9828
  });
8902
- import yaml2 from "js-yaml";
8903
- import { realpathSync } from "fs";
8904
- import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
8905
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join9 } from "path";
8906
- import { fileURLToPath } from "url";
9829
+ import yaml3 from "js-yaml";
9830
+ import { realpathSync as realpathSync2 } from "fs";
9831
+ import { access as access2, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
9832
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join11 } from "path";
9833
+ import { fileURLToPath as fileURLToPath2 } from "url";
8907
9834
  async function runCli(argv, io = {}) {
8908
9835
  const parsed = parseArgs(argv);
8909
9836
  const cwd = io.cwd ?? process.cwd();
8910
9837
  const out = io.stdout ?? ((chunk) => process.stdout.write(chunk));
8911
9838
  const err = io.stderr ?? ((chunk) => process.stderr.write(chunk));
8912
9839
  const root = String(parsed.flags.root ?? cwd);
9840
+ const sqliteBindingGate = () => {
9841
+ const runningInstallRoot = runningArtifactGraphInstallRoot();
9842
+ const probe = probeNativeBinding(runningInstallRoot ? { installRoot: runningInstallRoot } : {});
9843
+ if (probe.ok) {
9844
+ return void 0;
9845
+ }
9846
+ err(renderStructuredNativeBindingError(probe, {
9847
+ probedFrom: runningInstallRoot ?? "(current artifact-graph package)"
9848
+ }));
9849
+ return 1;
9850
+ };
8913
9851
  try {
8914
9852
  const hasHelpFlag = parsed.flags.help === true || parsed.positional.some((p) => p === "--help" || p === "-h");
8915
9853
  if (parsed.command === "--help" || parsed.command === "-h" || parsed.command === "help" || hasHelpFlag) {
@@ -8919,11 +9857,13 @@ async function runCli(argv, io = {}) {
8919
9857
  switch (parsed.command) {
8920
9858
  case "init": {
8921
9859
  await initConfig(root);
8922
- out(`Created ${join9(root, "artifact-graph.config.yaml")}
9860
+ out(`Created ${join11(root, "artifact-graph.config.yaml")}
8923
9861
  `);
8924
9862
  return 0;
8925
9863
  }
8926
9864
  case "scan": {
9865
+ const gate = sqliteBindingGate();
9866
+ if (gate !== void 0) return gate;
8927
9867
  const graph = await scanArtifacts(root);
8928
9868
  await writeGraphCache(root, graph);
8929
9869
  out(`Scanned ${graph.nodes.length} artifacts and ${graph.edges.length} relations
@@ -9045,6 +9985,8 @@ async function runCli(argv, io = {}) {
9045
9985
  return issues.some((issue2) => issue2.severity === "error") && !parsed.flags["warning-only"] ? 1 : 0;
9046
9986
  }
9047
9987
  case "query": {
9988
+ const gate = sqliteBindingGate();
9989
+ if (gate !== void 0) return gate;
9048
9990
  const graph = await scanArtifacts(root);
9049
9991
  const result = queryGraph(graph, {
9050
9992
  from: typeof parsed.flags.from === "string" ? parsed.flags.from : void 0,
@@ -9170,7 +10112,7 @@ async function runCli(argv, io = {}) {
9170
10112
  err("\nFix the traceability gaps above before generating an implementation packet.\n");
9171
10113
  if (typeof parsed.flags.out === "string") {
9172
10114
  const errorReport = JSON.stringify({ error: "missing", missing: manifest.missing }, null, 2);
9173
- await writeFile5(parsed.flags.out, errorReport + "\n");
10115
+ await writeFile6(parsed.flags.out, errorReport + "\n");
9174
10116
  }
9175
10117
  return 1;
9176
10118
  }
@@ -9204,7 +10146,7 @@ async function runCli(argv, io = {}) {
9204
10146
  return 1;
9205
10147
  }
9206
10148
  if (typeof parsed.flags.out === "string") {
9207
- await writeFile5(parsed.flags.out, output);
10149
+ await writeFile6(parsed.flags.out, output);
9208
10150
  out(`Packet written to ${parsed.flags.out}
9209
10151
  `);
9210
10152
  } else {
@@ -9243,7 +10185,7 @@ async function runCli(argv, io = {}) {
9243
10185
  if (packetJsonPath) {
9244
10186
  let rawJson;
9245
10187
  try {
9246
- rawJson = await readFile4(packetJsonPath, "utf-8");
10188
+ rawJson = await readFile5(packetJsonPath, "utf-8");
9247
10189
  } catch (readErr) {
9248
10190
  err(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 packet \u6587\u4EF6: "${packetJsonPath}" \u2014 ${readErr.message}
9249
10191
  `);
@@ -9333,7 +10275,7 @@ async function runCli(argv, io = {}) {
9333
10275
  `);
9334
10276
  }
9335
10277
  if (typeof parsed.flags.out === "string") {
9336
- await writeFile5(parsed.flags.out, prompt);
10278
+ await writeFile6(parsed.flags.out, prompt);
9337
10279
  out(`Prompt written to ${parsed.flags.out}
9338
10280
  `);
9339
10281
  } else {
@@ -9444,7 +10386,7 @@ async function runCli(argv, io = {}) {
9444
10386
  universalBaseline: discoverConfig.context?.universal_baseline
9445
10387
  });
9446
10388
  } else {
9447
- const targetsContent = await readFile4(targetsFile, "utf-8");
10389
+ const targetsContent = await readFile5(targetsFile, "utf-8");
9448
10390
  const auditConfig2 = await loadConfig(root);
9449
10391
  const parseResult = parseTargetsFile(targetsContent, auditConfig2);
9450
10392
  if (parseResult.errors.length > 0) {
@@ -9569,7 +10511,7 @@ async function runCli(argv, io = {}) {
9569
10511
  } else {
9570
10512
  let ppaTargetsContent;
9571
10513
  try {
9572
- ppaTargetsContent = await readFile4(ppaTargetsFile, "utf-8");
10514
+ ppaTargetsContent = await readFile5(ppaTargetsFile, "utf-8");
9573
10515
  } catch (readErr) {
9574
10516
  err(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 targets \u6587\u4EF6: "${ppaTargetsFile}" \u2014 ${readErr.message}
9575
10517
  `);
@@ -9648,7 +10590,7 @@ async function runCli(argv, io = {}) {
9648
10590
  const output = `${JSON.stringify(index, null, 2)}
9649
10591
  `;
9650
10592
  if (typeof parsed.flags.out === "string") {
9651
- await writeFile5(parsed.flags.out, output);
10593
+ await writeFile6(parsed.flags.out, output);
9652
10594
  out(`Version index written to ${parsed.flags.out}
9653
10595
  `);
9654
10596
  } else {
@@ -9816,8 +10758,8 @@ async function runCli(argv, io = {}) {
9816
10758
  const hooks = hookFlag === "all" ? ["pre-commit", "pre-push"] : [hookFlag];
9817
10759
  const prepared = [];
9818
10760
  for (const hookName of hooks) {
9819
- const templatePath = fileURLToPath(new URL(`../templates/git-hooks/${hookName}.sh`, import.meta.url));
9820
- const block = await readFile4(templatePath, "utf-8");
10761
+ const templatePath = fileURLToPath2(new URL(`../templates/git-hooks/${hookName}.sh`, import.meta.url));
10762
+ const block = await readFile5(templatePath, "utf-8");
9821
10763
  prepared.push(await prepareManagedHookBlock({
9822
10764
  hookPath: await resolveGitHookPath(root, hookName),
9823
10765
  block,
@@ -9850,6 +10792,9 @@ async function runCli(argv, io = {}) {
9850
10792
  out(`Forbidden edges: ${config.forbiddenEdges.length}
9851
10793
  `);
9852
10794
  }
10795
+ if (!report.nativeBinding.ok) {
10796
+ return 1;
10797
+ }
9853
10798
  return 0;
9854
10799
  }
9855
10800
  case "validate-review-result": {
@@ -9858,10 +10803,10 @@ async function runCli(argv, io = {}) {
9858
10803
  err("Usage: artifact-graph validate-review-result --file <path> [--format json]\n");
9859
10804
  return 1;
9860
10805
  }
9861
- const resolvedPath = isAbsolute4(filePath) ? filePath : join9(root, filePath);
10806
+ const resolvedPath = isAbsolute4(filePath) ? filePath : join11(root, filePath);
9862
10807
  let content;
9863
10808
  try {
9864
- content = await readFile4(resolvedPath, "utf-8");
10809
+ content = await readFile5(resolvedPath, "utf-8");
9865
10810
  } catch (readErr) {
9866
10811
  err(`Error: Cannot read file: "${resolvedPath}" \u2014 ${readErr.message}
9867
10812
  `);
@@ -9896,11 +10841,11 @@ async function runCli(argv, io = {}) {
9896
10841
  const deterministic = checkMode || parsed.flags.deterministic === true;
9897
10842
  const registry = await generateE2eRegistry(root, { deterministic });
9898
10843
  const output = JSON.stringify(registry, null, 2) + "\n";
9899
- const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join9(root, "artifacts/tests/e2e/e2e-test-registry.json");
10844
+ const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join11(root, "artifacts/tests/e2e/e2e-test-registry.json");
9900
10845
  if (checkMode) {
9901
10846
  let existing = "";
9902
10847
  try {
9903
- existing = await readFile4(outPath, "utf-8");
10848
+ existing = await readFile5(outPath, "utf-8");
9904
10849
  } catch {
9905
10850
  err(`Check failed: ${outPath} does not exist or is not readable
9906
10851
  `);
@@ -9916,7 +10861,7 @@ async function runCli(argv, io = {}) {
9916
10861
  return 0;
9917
10862
  }
9918
10863
  if (typeof parsed.flags.out === "string") {
9919
- await writeFile5(parsed.flags.out, output);
10864
+ await writeFile6(parsed.flags.out, output);
9920
10865
  out(`Registry written to ${parsed.flags.out} (${registry.total_batches} batches, ${registry.total_test_cases} TCs)
9921
10866
  `);
9922
10867
  } else {
@@ -9932,8 +10877,8 @@ async function runCli(argv, io = {}) {
9932
10877
  `);
9933
10878
  return 1;
9934
10879
  }
9935
- const packageDir = dirname4(fileURLToPath(import.meta.url));
9936
- const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join9(packageDir, "..", "contracts");
10880
+ const packageDir = dirname7(fileURLToPath2(import.meta.url));
10881
+ const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join11(packageDir, "..", "contracts");
9937
10882
  const revisionDigest = typeof parsed.flags["revision-digest"] === "string" ? parsed.flags["revision-digest"] : void 0;
9938
10883
  async function resolveContract(contractId) {
9939
10884
  const catalog = await loadContractCatalog(contractsDir);
@@ -10147,7 +11092,7 @@ async function runCli(argv, io = {}) {
10147
11092
  }
10148
11093
  let markdownContent;
10149
11094
  try {
10150
- markdownContent = await readFile4(markdownPath, "utf-8");
11095
+ markdownContent = await readFile5(markdownPath, "utf-8");
10151
11096
  } catch {
10152
11097
  out(`${JSON.stringify({ ok: false, error: { code: "SCHEMA_VALIDATION_FAILED", path: "/markdown", message: `Could not read markdown file "${markdownPath}"` } }, null, 2)}
10153
11098
  `);
@@ -10168,6 +11113,52 @@ async function runCli(argv, io = {}) {
10168
11113
  `);
10169
11114
  return 1;
10170
11115
  }
11116
+ case "refactor-id": {
11117
+ const { refactorId: refactorId2, renderRefactorIdMarkdown: renderRefactorIdMarkdown2 } = await Promise.resolve().then(() => (init_refactor_id(), refactor_id_exports));
11118
+ if (typeof parsed.flags.format !== "string" && parsed.flags.format !== void 0) {
11119
+ out(`${JSON.stringify({ ok: false, error: { code: "INVALID_FORMAT", path: "/format", message: 'Invalid --format: "" (missing value). Allowed values: json, markdown' } }, null, 2)}
11120
+ `);
11121
+ err('Invalid --format: "" (missing value). Allowed values: json, markdown\n');
11122
+ return 1;
11123
+ }
11124
+ const refactorIdFormat = typeof parsed.flags.format === "string" ? parsed.flags.format : "markdown";
11125
+ if (refactorIdFormat !== "json" && refactorIdFormat !== "markdown") {
11126
+ out(`${JSON.stringify({ ok: false, error: { code: "INVALID_FORMAT", path: "/format", message: `Invalid --format: "${refactorIdFormat}". Allowed values: json, markdown` } }, null, 2)}
11127
+ `);
11128
+ err(`Invalid --format: "${refactorIdFormat}". Allowed values: json, markdown
11129
+ `);
11130
+ return 1;
11131
+ }
11132
+ for (const forbidden of ["force", "all", "recursive"]) {
11133
+ if (parsed.flags[forbidden] !== void 0) {
11134
+ err(`refactor-id \u4E0D\u652F\u6301 --${forbidden}\uFF1A\u9996\u7248\u53EA\u6709\u4E00\u5BF9\u4E00\u6539\u540D\uFF0CD-ACA-29 \u5DF2\u51BB\u7ED3\u547D\u4EE4\u9762
11135
+ `);
11136
+ return 1;
11137
+ }
11138
+ }
11139
+ const from = typeof parsed.flags.from === "string" ? parsed.flags.from : "";
11140
+ const to = typeof parsed.flags.to === "string" ? parsed.flags.to : "";
11141
+ if (!from || !to) {
11142
+ err("Usage: artifact-graph refactor-id --from <type:id> --to <new-id> [--apply] [--approve-prose <file:line,...>] [--format json|markdown]\n");
11143
+ return 1;
11144
+ }
11145
+ const approveProse = typeof parsed.flags["approve-prose"] === "string" ? String(parsed.flags["approve-prose"]).split(",").map((entry) => entry.trim()).filter(Boolean) : [];
11146
+ const result = await refactorId2({
11147
+ root,
11148
+ from,
11149
+ to,
11150
+ apply: parsed.flags.apply === true,
11151
+ approveProse,
11152
+ format: refactorIdFormat
11153
+ });
11154
+ if (refactorIdFormat === "json") {
11155
+ out(`${JSON.stringify(result, null, 2)}
11156
+ `);
11157
+ } else {
11158
+ out(renderRefactorIdMarkdown2(result));
11159
+ }
11160
+ return result.ok ? 0 : 1;
11161
+ }
10171
11162
  default:
10172
11163
  err(helpText());
10173
11164
  return 1;
@@ -10200,7 +11191,7 @@ function isGraphRelevantPath(path, schema) {
10200
11191
  return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || matchesConfiguredArtifactPath(path, schema);
10201
11192
  }
10202
11193
  async function initConfig(root) {
10203
- const configPath = join9(root, "artifact-graph.config.yaml");
11194
+ const configPath = join11(root, "artifact-graph.config.yaml");
10204
11195
  try {
10205
11196
  await access2(configPath);
10206
11197
  throw new Error(`Config already exists: ${configPath}`);
@@ -10210,7 +11201,7 @@ async function initConfig(root) {
10210
11201
  }
10211
11202
  }
10212
11203
  await mkdir6(root, { recursive: true });
10213
- await writeFile5(configPath, yaml2.dump(DEFAULT_SCHEMA, { lineWidth: 120 }));
11204
+ await writeFile6(configPath, yaml3.dump(DEFAULT_SCHEMA, { lineWidth: 120 }));
10214
11205
  }
10215
11206
  function parseArgs(argv) {
10216
11207
  const [command, ...rest] = argv;
@@ -10268,6 +11259,10 @@ Commands:
10268
11259
  next-id <type> --range <name>
10269
11260
  render [--format mermaid]
10270
11261
  doctor [--format json|markdown]
11262
+ refactor-id --from <type:id> --to <new-id> [--apply] [--approve-prose <file:line,...>] [--format json|markdown]
11263
+ \u5B89\u5168\u5236\u54C1\u6807\u8BC6\u4E00\u5BF9\u4E00\u91CD\u6784\uFF1A\u7F3A\u7701 dry-run \u53EA\u8F93\u51FA\u8BA1\u5212\uFF1B--apply \u4EE5\u5355\u4E00\u4E8B\u52A1\u6539\u5199\u7ED3\u6784\u5316
11264
+ \u767D\u540D\u5355\u4F4D\u7F6E\u5E76\u81EA\u52A8 rescan \u8BC1\u660E\u3002\u9501\u4E0D\u81EA\u52A8\u5237\u65B0\uFF0Capply \u540E\u6309\u63D0\u793A\u8FD0\u884C
11265
+ version-lock refresh --changed-only --worktree\u3002
10271
11266
  validate-review-result --file <path> [--format json]
10272
11267
  generate-e2e-registry [--deterministic] [--out <path>] [--check]
10273
11268
  contract list [--contracts-dir <path>] [--format json]
@@ -10283,7 +11278,7 @@ function isCliEntrypoint(argvPath) {
10283
11278
  return false;
10284
11279
  }
10285
11280
  try {
10286
- return realpathSync(argvPath) === realpathSync(fileURLToPath(import.meta.url));
11281
+ return realpathSync2(argvPath) === realpathSync2(fileURLToPath2(import.meta.url));
10287
11282
  } catch {
10288
11283
  return false;
10289
11284
  }
@@ -10299,6 +11294,7 @@ var init_cli = __esm({
10299
11294
  init_packet_prompt_validator();
10300
11295
  init_packet_prompt_audit();
10301
11296
  init_cli_resolver();
11297
+ init_native_binding_diagnostics();
10302
11298
  init_review_result_validator();
10303
11299
  init_git_changes();
10304
11300
  init_git_hook_path();