qfai 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +56 -36
  2. package/assets/defaults/agent-routing/qfai-implement.yml +9 -0
  3. package/assets/defaults/agent-routing/qfai-triage.yml +18 -0
  4. package/assets/defaults/workflows/answer-in-session.yml +8 -0
  5. package/assets/defaults/workflows/quarantine-flaky.yml +3 -0
  6. package/assets/defaults/workflows/repair-consistency.yml +1 -0
  7. package/assets/defaults/workflows/run-operation.yml +11 -0
  8. package/assets/defaults/workflows/run-owned-cleanup.yml +12 -0
  9. package/assets/defaults/workflows/write-procedure.yml +18 -0
  10. package/assets/graph/codegraph-native.mjs +174 -0
  11. package/assets/graph/graphify-native.py +72 -0
  12. package/assets/init/.claude/settings.json +9 -9
  13. package/assets/init/.codex/hooks.json +8 -8
  14. package/assets/init/.qfai/assistant/agent/frontend-engineer.md +5 -0
  15. package/assets/init/.qfai/assistant/agent/product-experience-architect.md +4 -0
  16. package/assets/init/.qfai/assistant/agent/product-surface-reviewer.md +7 -0
  17. package/assets/init/.qfai/assistant/agent/requirements-analyst.md +8 -0
  18. package/assets/init/.qfai/assistant/rule/communication.md +18 -8
  19. package/assets/init/.qfai/assistant/rule/constitution.md +31 -15
  20. package/assets/init/.qfai/assistant/rule/drift-protocol.md +119 -2
  21. package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +6 -4
  22. package/assets/init/.qfai/assistant/rule/references/codex-delegation.md +27 -0
  23. package/assets/init/.qfai/assistant/rule/references/griller-recommendations.md +21 -9
  24. package/assets/init/.qfai/assistant/rule/references/worker-edit-boundary.md +48 -0
  25. package/assets/init/.qfai/assistant/rule/review-convergence.md +25 -12
  26. package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +16 -25
  27. package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +16 -11
  28. package/assets/init/.qfai/assistant/rule/ui-definition-protocol.md +6 -1
  29. package/assets/init/.qfai/assistant/rule/workflow.md +2 -1
  30. package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +3 -3
  31. package/assets/init/.qfai/assistant/skill/qfai-discussion/SKILL.md +6 -0
  32. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +20 -15
  33. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui-bearing-playbook.md +2 -4
  34. package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +1 -6
  35. package/assets/init/.qfai/assistant/skill/qfai-discussion/templates/uiux/00_index.md +0 -16
  36. package/assets/init/.qfai/assistant/skill/qfai-grilling/SKILL.md +30 -20
  37. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/delegated-session.md +4 -3
  38. package/assets/init/.qfai/assistant/skill/qfai-grilling/references/session-record.md +8 -2
  39. package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +16 -3
  40. package/assets/init/.qfai/assistant/skill/qfai-implement/references/parallelization-policy.md +9 -1
  41. package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +46 -0
  42. package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +5 -2
  43. package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +3 -3
  44. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +12 -6
  45. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +1 -265
  46. package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-placement.md +273 -0
  47. package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +4 -3
  48. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +2 -3
  49. package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +17 -1
  50. package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +52 -37
  51. package/assets/init/.qfai/assistant/skill/qfai-run/references/codex-cloud-install.md +80 -0
  52. package/assets/init/.qfai/assistant/skill/qfai-run/references/evidence-delivery.md +322 -0
  53. package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +83 -49
  54. package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +226 -11
  55. package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +6 -3
  56. package/assets/init/.qfai/assistant/skill/qfai-run/references/reached-step-observations.md +132 -0
  57. package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +14 -0
  58. package/assets/init/.qfai/assistant/skill/qfai-run/scripts/codex-cloud-install.mjs +117 -0
  59. package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +4 -3
  60. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +1 -0
  61. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +23 -5
  62. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-md-authoring.md +20 -7
  63. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +8 -0
  64. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +29 -4
  65. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-phase-checklists.md +1 -1
  66. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-pre-draft-grilling.md +4 -1
  67. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-quality-gate.md +1 -1
  68. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +159 -4
  69. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +14 -2
  70. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +146 -0
  71. package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +4 -29
  72. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +38 -1
  73. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/ui-contract.sample.yaml +52 -5
  74. package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +12 -0
  75. package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +34 -21
  76. package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +5 -4
  77. package/assets/init/.qfai/assistant/skill/qfai-verify/references/context-load.md +9 -6
  78. package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +80 -15
  79. package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +35 -21
  80. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/config.toml +2 -4
  81. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/mcp-config.json +9 -5
  82. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/.mcp.json +1 -2
  83. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/config.toml +3 -3
  84. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/mcp-config.json +11 -8
  85. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/config.toml +1 -1
  86. package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/mcp-config.json +7 -3
  87. package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +33 -20
  88. package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +124 -8
  89. package/assets/init/.qfai/assistant/step/common-grilling-record/STEP.md +10 -2
  90. package/assets/init/.qfai/assistant/step/common-policy-check/STEP.md +1 -0
  91. package/assets/init/.qfai/assistant/step/common-review-cycle/STEP.md +26 -9
  92. package/assets/init/.qfai/assistant/step/discussion-interview/STEP.md +15 -8
  93. package/assets/init/.qfai/assistant/step/discussion-oq/STEP.md +5 -1
  94. package/assets/init/.qfai/assistant/step/discussion-pack/STEP.md +6 -1
  95. package/assets/init/.qfai/assistant/step/discussion-research/STEP.md +2 -0
  96. package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +6 -4
  97. package/assets/init/.qfai/assistant/step/implement-backport/STEP.md +1 -1
  98. package/assets/init/.qfai/assistant/step/implement-benchmark/STEP.md +7 -4
  99. package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +11 -2
  100. package/assets/init/.qfai/assistant/step/implement-diagnose/STEP.md +2 -0
  101. package/assets/init/.qfai/assistant/step/implement-oracle-parity/STEP.md +2 -2
  102. package/assets/init/.qfai/assistant/step/implement-procedure/STEP.md +69 -0
  103. package/assets/init/.qfai/assistant/step/implement-quarantine/STEP.md +24 -7
  104. package/assets/init/.qfai/assistant/step/implement-refactor/STEP.md +2 -2
  105. package/assets/init/.qfai/assistant/step/implement-regression-fix/STEP.md +7 -6
  106. package/assets/init/.qfai/assistant/step/implement-retire/STEP.md +6 -3
  107. package/assets/init/.qfai/assistant/step/implement-revert/STEP.md +4 -3
  108. package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +6 -5
  109. package/assets/init/.qfai/assistant/step/implement-stress-harness/STEP.md +34 -3
  110. package/assets/init/.qfai/assistant/step/implement-sweep/STEP.md +4 -3
  111. package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +39 -5
  112. package/assets/init/.qfai/assistant/step/implement-tooling/STEP.md +22 -2
  113. package/assets/init/.qfai/assistant/step/maintain-edit/STEP.md +18 -14
  114. package/assets/init/.qfai/assistant/step/prototyping-grill/STEP.md +4 -3
  115. package/assets/init/.qfai/assistant/step/prototyping-loop/STEP.md +4 -0
  116. package/assets/init/.qfai/assistant/step/prototyping-preflight/STEP.md +9 -3
  117. package/assets/init/.qfai/assistant/step/sdd-contract/STEP.md +11 -0
  118. package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +2 -0
  119. package/assets/init/.qfai/assistant/step/sdd-story/STEP.md +10 -0
  120. package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +34 -10
  121. package/assets/init/.qfai/assistant/step/triage-answer/STEP.md +7 -6
  122. package/assets/init/.qfai/assistant/step/triage-close/STEP.md +33 -23
  123. package/assets/init/.qfai/assistant/step/triage-cluster/STEP.md +5 -3
  124. package/assets/init/.qfai/assistant/step/triage-decompose/STEP.md +2 -0
  125. package/assets/init/.qfai/assistant/step/triage-dedupe/STEP.md +4 -0
  126. package/assets/init/.qfai/assistant/step/triage-handoff/STEP.md +13 -4
  127. package/assets/init/.qfai/assistant/step/triage-investigate/STEP.md +8 -5
  128. package/assets/init/.qfai/assistant/step/triage-operate/STEP.md +50 -0
  129. package/assets/init/.qfai/assistant/step/triage-owned-cleanup/STEP.md +86 -0
  130. package/assets/init/.qfai/assistant/step/triage-request-info/STEP.md +2 -0
  131. package/assets/init/.qfai/assistant/step/triage-security-intake/STEP.md +6 -3
  132. package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +1 -0
  133. package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +37 -9
  134. package/assets/init/.qfai/assistant/step/verify-external/STEP.md +5 -2
  135. package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +14 -16
  136. package/assets/init/.qfai/assistant/step/verify-release-notes/STEP.md +1 -0
  137. package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +46 -4
  138. package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +133 -30
  139. package/assets/init/root/.agents/rules/ai-readable-markdown.md +2 -2
  140. package/assets/init/root/.agents/rules/api-budget.md +187 -8
  141. package/assets/init/root/.agents/rules/document-schema.md +17 -0
  142. package/assets/init/root/.agents/rules/documentation-clarity.md +163 -34
  143. package/assets/init/root/.agents/rules/graph-exploration.md +150 -0
  144. package/assets/init/root/.agents/rules/grilling.md +100 -43
  145. package/assets/init/root/.agents/rules/minimal-implementation.md +3 -3
  146. package/assets/init/root/.agents/rules/reminders.json +27 -18
  147. package/assets/init/root/.agents/rules/session-feedback.md +131 -0
  148. package/assets/init/root/.agents/rules/temporary-files.md +33 -0
  149. package/assets/init/root/.agents/rules/untrusted-content.md +2 -2
  150. package/assets/init/root/.agents/rules/user-questions.md +51 -40
  151. package/assets/init/root/.github/workflows/qfai-docs.yml +3 -3
  152. package/assets/init/root/.github/workflows/qfai-tests.yml +88 -35
  153. package/assets/init/root/.github/workflows/qfai-validate.yml +2 -2
  154. package/assets/init/root/AGENTS.md +24 -22
  155. package/assets/sandbox-templates/default-deny.yaml +9 -3
  156. package/assets/schemas/workflow/extraction.schema.json +89 -5
  157. package/assets/scripts/check-mdschema.mjs +77 -8
  158. package/assets/uix-rev/comparison-review.md +1 -1
  159. package/assets/uix-rev/contracts-review.md +1 -2
  160. package/dist/cli/index.cjs +9444 -10071
  161. package/dist/cli/index.cjs.map +1 -1
  162. package/dist/cli/index.mjs +9421 -10041
  163. package/dist/cli/index.mjs.map +1 -1
  164. package/dist/graph/cli.cjs +1568 -0
  165. package/dist/graph/cli.cjs.map +1 -0
  166. package/dist/graph/cli.d.cts +9 -0
  167. package/dist/graph/cli.d.ts +9 -0
  168. package/dist/graph/cli.mjs +1541 -0
  169. package/dist/graph/cli.mjs.map +1 -0
  170. package/dist/graph/hook.cjs +817 -0
  171. package/dist/graph/hook.cjs.map +1 -0
  172. package/dist/graph/hook.d.cts +9 -0
  173. package/dist/graph/hook.d.ts +9 -0
  174. package/dist/graph/hook.mjs +792 -0
  175. package/dist/graph/hook.mjs.map +1 -0
  176. package/dist/index.cjs +7634 -8517
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.d.cts +146 -658
  179. package/dist/index.d.ts +146 -658
  180. package/dist/index.mjs +7462 -8318
  181. package/dist/index.mjs.map +1 -1
  182. package/dist/process-C21nTvkT.d.cts +12 -0
  183. package/dist/process-C21nTvkT.d.ts +12 -0
  184. package/package.json +4 -6
  185. package/assets/init/root/CLAUDE.md +0 -46
@@ -0,0 +1,1541 @@
1
+ // src/core/graph/codePreparation.ts
2
+ import { randomUUID as randomUUID3 } from "crypto";
3
+ import { mkdir as mkdir4, rm as rm4 } from "fs/promises";
4
+ import path7 from "path";
5
+
6
+ // src/shared/assets.ts
7
+ import { existsSync } from "fs";
8
+ import path from "path";
9
+ import { fileURLToPath } from "url";
10
+ function initAssetsCandidates(baseDir) {
11
+ return [
12
+ path.resolve(baseDir, "../assets/init"),
13
+ path.resolve(baseDir, "../../assets/init"),
14
+ path.resolve(baseDir, "../../../assets/init")
15
+ ];
16
+ }
17
+ var INIT_ASSETS_SENTINELS = [
18
+ path.join(".qfai", "assistant"),
19
+ path.join("root", "qfai.config.yaml")
20
+ ];
21
+ function isInitAssetsDir(candidate) {
22
+ return INIT_ASSETS_SENTINELS.every((sentinel) => existsSync(path.join(candidate, sentinel)));
23
+ }
24
+ function getInitAssetsDir() {
25
+ const base = import.meta.url;
26
+ const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
27
+ const baseDir = path.dirname(basePath);
28
+ const candidates = initAssetsCandidates(baseDir);
29
+ for (const candidate of candidates) {
30
+ if (isInitAssetsDir(candidate)) {
31
+ return candidate;
32
+ }
33
+ }
34
+ throw new Error(
35
+ [
36
+ "Template assets for init were not found.",
37
+ "Checked paths:",
38
+ ...candidates.map((candidate) => `- ${candidate}`)
39
+ ].join("\n")
40
+ );
41
+ }
42
+
43
+ // src/core/graph/process.ts
44
+ import { execFile } from "child_process";
45
+ import { access, lstat as lstat2, realpath } from "fs/promises";
46
+ import path4 from "path";
47
+ import { promisify } from "util";
48
+
49
+ // src/core/graph/layout.ts
50
+ import path2 from "path";
51
+ var GRAPH_VERSIONS = { codegraph: "1.6.2", graphify: "0.9.86" };
52
+ function graphExecutionEnvironment() {
53
+ return { platform: process.platform, architecture: process.arch };
54
+ }
55
+ function graphEnvironmentKey(environment = graphExecutionEnvironment()) {
56
+ if (!/^[a-z0-9]+$/i.test(environment.platform) || !/^[a-z0-9]+$/i.test(environment.architecture))
57
+ throw new Error("Unsupported graph execution environment.");
58
+ return `${environment.platform}-${environment.architecture}`;
59
+ }
60
+ function graphRuntimePaths(root, environment = graphExecutionEnvironment()) {
61
+ const directory = path2.join(root, ".qfai", "tools", "runtimes", graphEnvironmentKey(environment));
62
+ return {
63
+ directory,
64
+ codegraph: path2.join(
65
+ directory,
66
+ "codegraph",
67
+ "node_modules",
68
+ "@colbymchenry",
69
+ "codegraph",
70
+ "npm-shim.js"
71
+ ),
72
+ codegraphSdk: path2.join(
73
+ directory,
74
+ "codegraph",
75
+ "node_modules",
76
+ "@colbymchenry",
77
+ "codegraph",
78
+ "npm-sdk.js"
79
+ ),
80
+ codegraphNode: path2.join(
81
+ directory,
82
+ "codegraph",
83
+ "node_modules",
84
+ "@colbymchenry",
85
+ `codegraph-${environment.platform}-${environment.architecture}`,
86
+ environment.platform === "win32" ? "node.exe" : "node"
87
+ ),
88
+ python: environment.platform === "win32" ? path2.join(directory, "graphify", "Scripts", "python.exe") : path2.join(directory, "graphify", "bin", "python")
89
+ };
90
+ }
91
+ function graphAnalysisRoot(root, engine, environment = graphExecutionEnvironment()) {
92
+ return path2.join(root, ".qfai", "tools", "analysis", graphEnvironmentKey(environment), engine);
93
+ }
94
+
95
+ // src/core/graph/storage.ts
96
+ import { randomUUID } from "crypto";
97
+ import { lstat, mkdir, readdir, rename, rm } from "fs/promises";
98
+ import path3 from "path";
99
+
100
+ // src/core/fs/errno.ts
101
+ function isEnoent(err) {
102
+ return hasErrnoCode(err) && err.code === "ENOENT";
103
+ }
104
+ function hasErrnoCode(err) {
105
+ return err !== null && typeof err === "object" && "code" in err && typeof err.code === "string";
106
+ }
107
+
108
+ // src/core/graph/storage.ts
109
+ async function assertGraphStoragePath(root, directory) {
110
+ const relative = path3.relative(root, directory);
111
+ const parts = relative.split(path3.sep);
112
+ if (path3.isAbsolute(relative) || parts[0] !== ".qfai" || parts[1] !== "tools" || parts.includes("..")) {
113
+ throw new Error("Graph storage must stay inside project-local .qfai/tools.");
114
+ }
115
+ for (let depth = 1; depth <= parts.length; depth += 1) {
116
+ try {
117
+ const stats = await lstat(path3.join(root, ...parts.slice(0, depth)));
118
+ if (stats.isSymbolicLink() || !stats.isDirectory())
119
+ throw new Error(
120
+ "Graph storage has an unsafe path component; retain it and repair the project configuration."
121
+ );
122
+ } catch (cause) {
123
+ if (isEnoent(cause)) return;
124
+ throw cause;
125
+ }
126
+ }
127
+ }
128
+ async function assertIndependentAnalysis(directory) {
129
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
130
+ const child = path3.join(directory, entry.name);
131
+ const stats = await lstat(child);
132
+ if (stats.isSymbolicLink() || stats.isFile() && stats.nlink > 1) {
133
+ throw new Error(
134
+ "Graph analysis contains a shared file or unsafe link; retain it and prepare an independent copy."
135
+ );
136
+ }
137
+ if (stats.isDirectory()) await assertIndependentAnalysis(child);
138
+ }
139
+ }
140
+ async function replaceGraphDirectory(staged, current, root) {
141
+ await assertGraphStoragePath(root, staged);
142
+ await assertGraphStoragePath(root, current);
143
+ const parent = path3.dirname(current);
144
+ await mkdir(parent, { recursive: true });
145
+ const previous = path3.join(parent, `.previous-${randomUUID()}`);
146
+ let retained = false;
147
+ try {
148
+ await rename(current, previous);
149
+ retained = true;
150
+ } catch (cause) {
151
+ if (!isEnoent(cause)) throw cause;
152
+ }
153
+ try {
154
+ await rename(staged, current);
155
+ } catch (cause) {
156
+ if (retained) await rename(previous, current);
157
+ throw cause;
158
+ }
159
+ if (retained) await rm(previous, { recursive: true, force: true });
160
+ }
161
+
162
+ // src/core/graph/process.ts
163
+ var execute = promisify(execFile);
164
+ var GRAPH_PYTHON_FLAGS = ["-I", "-X", "utf8"];
165
+ function privateEngineEnvironment(root, engine, execution) {
166
+ const environment = {};
167
+ const allowed = {
168
+ path: "PATH",
169
+ systemroot: "SystemRoot",
170
+ windir: "WINDIR",
171
+ comspec: "ComSpec",
172
+ pathext: "PATHEXT",
173
+ tmp: "TMP",
174
+ temp: "TEMP",
175
+ lang: "LANG",
176
+ lc_all: "LC_ALL"
177
+ };
178
+ for (const [name, value] of Object.entries(process.env)) {
179
+ const canonical = allowed[name.toLowerCase()];
180
+ if (canonical) environment[canonical] = value;
181
+ }
182
+ const home = path4.join(root, ".qfai", "tools", "home", graphEnvironmentKey(execution), engine);
183
+ return {
184
+ ...environment,
185
+ HOME: home,
186
+ USERPROFILE: home,
187
+ APPDATA: path4.join(home, "AppData", "Roaming"),
188
+ LOCALAPPDATA: path4.join(home, "AppData", "Local"),
189
+ XDG_CONFIG_HOME: path4.join(home, ".config"),
190
+ XDG_CACHE_HOME: path4.join(home, ".cache"),
191
+ XDG_DATA_HOME: path4.join(home, ".local", "share"),
192
+ XDG_STATE_HOME: path4.join(home, ".local", "state")
193
+ };
194
+ }
195
+ function graphifyEnvironment(root, environment = graphExecutionEnvironment()) {
196
+ return {
197
+ ...privateEngineEnvironment(root, "graphify", environment),
198
+ PYTHONUTF8: "1",
199
+ PYTHONIOENCODING: "utf-8",
200
+ GRAPHIFY_QUERY_LOG_DISABLE: "1",
201
+ GRAPHIFY_OUT: "graphify-out",
202
+ GRAPHIFY_ALLOW_LOCAL_PROVIDERS: "0",
203
+ GRAPHIFY_NO_AUTO_REFRESH: "1"
204
+ };
205
+ }
206
+ function codegraphEnvironment(root, environment = graphExecutionEnvironment()) {
207
+ const privateEnvironment = privateEngineEnvironment(root, "codegraph", environment);
208
+ const home = privateEnvironment.HOME ?? "";
209
+ return {
210
+ ...privateEnvironment,
211
+ CODEGRAPH_INSTALL_DIR: path4.join(home, ".codegraph"),
212
+ CODEGRAPH_NO_DOWNLOAD: "1",
213
+ CODEGRAPH_NO_UPDATE_CHECK: "1",
214
+ CODEGRAPH_TELEMETRY: "0",
215
+ CODEGRAPH_DIR: ".codegraph"
216
+ };
217
+ }
218
+ function graphifyCorpusEnvironment(root, analysis) {
219
+ return {
220
+ ...graphifyEnvironment(root),
221
+ GRAPHIFY_OUT: path4.toNamespacedPath(path4.join(analysis, "graphify-out"))
222
+ };
223
+ }
224
+ async function graphifyConfigurationSafe(root) {
225
+ const home = graphifyEnvironment(root).HOME;
226
+ if (!home) return false;
227
+ try {
228
+ const configuration = path4.join(home, ".graphify");
229
+ await assertGraphStoragePath(root, configuration);
230
+ await lstat2(path4.join(configuration, "providers.json"));
231
+ return false;
232
+ } catch (cause) {
233
+ return isEnoent(cause);
234
+ }
235
+ }
236
+ var runGraphProcess = async (command, args, options) => {
237
+ try {
238
+ const result = await execute(command, [...args], {
239
+ cwd: options.cwd,
240
+ ...options.env ? { env: options.env } : {},
241
+ windowsHide: true,
242
+ timeout: options.timeoutMs ?? 12e4,
243
+ maxBuffer: options.maxBufferBytes ?? 4 * 1024 * 1024
244
+ });
245
+ return { exitCode: 0, stdout: result.stdout };
246
+ } catch (cause) {
247
+ if (!(cause instanceof Error) || !("code" in cause)) throw cause;
248
+ return { exitCode: typeof cause.code === "number" ? cause.code : 1, stdout: "" };
249
+ }
250
+ };
251
+
252
+ // src/core/graph/reuse.ts
253
+ import os from "os";
254
+ import path6 from "path";
255
+ import { randomUUID as randomUUID2 } from "crypto";
256
+ import {
257
+ cp,
258
+ lstat as lstat4,
259
+ mkdir as mkdir3,
260
+ readFile,
261
+ readdir as readdir3,
262
+ realpath as realpath2,
263
+ rename as rename2,
264
+ rm as rm3,
265
+ writeFile
266
+ } from "fs/promises";
267
+
268
+ // src/core/graph/analysis.ts
269
+ import path5 from "path";
270
+ import { copyFile, lstat as lstat3, mkdir as mkdir2, readdir as readdir2, rm as rm2 } from "fs/promises";
271
+ async function inspectCodeCoverage(root, run) {
272
+ const unknown = { complete: false, unsupported: [], unreadable: [], unparsed: [] };
273
+ const runtime = graphRuntimePaths(root);
274
+ const analysis = graphAnalysisRoot(root, "codegraph");
275
+ const result = await run(
276
+ runtime.codegraphNode,
277
+ [
278
+ "--liftoff-only",
279
+ path5.resolve(getInitAssetsDir(), "..", "graph", "codegraph-native.mjs"),
280
+ runtime.codegraphSdk,
281
+ analysis,
282
+ "coverage"
283
+ ],
284
+ { cwd: analysis, env: codegraphEnvironment(root) }
285
+ );
286
+ if (result.exitCode !== 0) return unknown;
287
+ let value;
288
+ try {
289
+ value = JSON.parse(result.stdout);
290
+ } catch {
291
+ return unknown;
292
+ }
293
+ if (!object(value) || Object.keys(value).length !== 4 || typeof value.complete !== "boolean" || !stringArray(value.unsupported) || !stringArray(value.unreadable) || !stringArray(value.unparsed))
294
+ return unknown;
295
+ return {
296
+ complete: value.complete && value.unsupported.length === 0 && value.unreadable.length === 0 && value.unparsed.length === 0,
297
+ unsupported: value.unsupported,
298
+ unreadable: value.unreadable,
299
+ unparsed: value.unparsed
300
+ };
301
+ }
302
+ function stringArray(value) {
303
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
304
+ }
305
+ async function inspectCurrentCodeAnalysis(root, run) {
306
+ const analysis = graphAnalysisRoot(root, "codegraph");
307
+ return inspectCodeAnalysisAt(root, analysis, run);
308
+ }
309
+ async function inspectCodeAnalysisAt(root, analysis, run) {
310
+ await assertGraphStoragePath(root, analysis);
311
+ try {
312
+ if (!(await lstat3(path5.join(analysis, ".codegraph", "codegraph.db"))).isFile()) return false;
313
+ } catch (cause) {
314
+ if (isEnoent(cause)) return false;
315
+ throw cause;
316
+ }
317
+ await mirrorGraphCorpus(root, analysis);
318
+ return nativeCodeAnalysisCurrent(root, analysis, run);
319
+ }
320
+ async function nativeCodeAnalysisCurrent(root, analysis, run) {
321
+ const result = await run(
322
+ process.execPath,
323
+ [graphRuntimePaths(root).codegraph, "status", analysis, "--json"],
324
+ { cwd: root, env: codegraphEnvironment(root) }
325
+ );
326
+ if (result.exitCode !== 0) return false;
327
+ let value;
328
+ try {
329
+ value = JSON.parse(result.stdout);
330
+ } catch (cause) {
331
+ if (!(cause instanceof SyntaxError)) throw cause;
332
+ return false;
333
+ }
334
+ if (!object(value) || !object(value.index) || !object(value.pendingChanges)) return false;
335
+ const changes = value.pendingChanges;
336
+ return value.initialized === true && value.version === GRAPH_VERSIONS.codegraph && value.projectPath === analysis && value.worktreeMismatch === null && value.index.state === "complete" && value.index.pendingRefs === 0 && value.index.reindexRecommended === false && value.index.builtWithVersion === GRAPH_VERSIONS.codegraph && value.index.builtWithExtractionVersion !== void 0 && value.index.builtWithExtractionVersion === value.index.currentExtractionVersion && ["added", "modified", "removed"].every((key) => changes[key] === 0);
337
+ }
338
+ var excludedDirectories = /* @__PURE__ */ new Set([
339
+ ".git",
340
+ ".codegraph",
341
+ ".codegraph-wsl",
342
+ ".graphify",
343
+ "graphify-out",
344
+ "node_modules",
345
+ "dist",
346
+ "tmp",
347
+ ".claude",
348
+ ".codex",
349
+ ".vscode",
350
+ ".ssh",
351
+ ".aws",
352
+ ".azure"
353
+ ]);
354
+ function excludedGraphInput(relative) {
355
+ const parts = relative.split("/");
356
+ const name = parts.at(-1) ?? "";
357
+ return parts.some((part) => excludedDirectories.has(part.toLowerCase())) || /^\.qfai\/(?:tools|report|evidence|review|discussion)(?:\/|$)/i.test(relative) || relative.toLowerCase() === ".qfai/graph-processing.json" || /^(?:\.npmrc|\.pypirc|\.netrc|\.git-credentials)$/i.test(name) || /^\.env(?:\.|$)/i.test(name) || /\.(?:pem|key|p12|pfx|keystore)$/i.test(name) || /^(?:id_(?:rsa|dsa|ecdsa|ed25519)|credentials?(?:\.|$)|secrets?(?:\.|$)|\.mcp\.json$)/i.test(
358
+ name
359
+ );
360
+ }
361
+ async function mirrorGraphCorpus(root, destination, scope) {
362
+ await assertGraphStoragePath(root, destination);
363
+ await assertIndependentAnalysis(destination);
364
+ const files = /* @__PURE__ */ new Set();
365
+ await visit("");
366
+ await removeAbsent("");
367
+ async function visit(relative) {
368
+ for (const entry of await readdir2(path5.join(root, ...relative.split("/")), {
369
+ withFileTypes: true
370
+ })) {
371
+ const next = relative ? `${relative}/${entry.name}` : entry.name;
372
+ if (excludedGraphInput(next) || entry.isSymbolicLink()) continue;
373
+ if (scope && !scope.some(
374
+ (item) => item === next || next.startsWith(`${item}/`) || item.startsWith(`${next}/`)
375
+ ))
376
+ continue;
377
+ if (entry.isDirectory()) {
378
+ await visit(next);
379
+ } else if (entry.isFile()) {
380
+ const source = path5.join(root, ...next.split("/"));
381
+ const stats = await lstat3(source);
382
+ if (!stats.isFile() || stats.nlink > 1) continue;
383
+ const target = path5.join(destination, ...next.split("/"));
384
+ await mkdir2(path5.dirname(target), { recursive: true });
385
+ await copyFile(source, target);
386
+ files.add(next);
387
+ }
388
+ }
389
+ }
390
+ async function removeAbsent(relative) {
391
+ for (const entry of await readdir2(path5.join(destination, ...relative.split("/")), {
392
+ withFileTypes: true
393
+ })) {
394
+ const next = relative ? `${relative}/${entry.name}` : entry.name;
395
+ if (entry.name === ".codegraph" || entry.name === ".git" || entry.name === "graphify-out")
396
+ continue;
397
+ const target = path5.join(destination, ...next.split("/"));
398
+ if (entry.isDirectory()) await removeAbsent(next);
399
+ else if (!files.has(next)) await rm2(target);
400
+ }
401
+ }
402
+ }
403
+ function object(value) {
404
+ return typeof value === "object" && value !== null && !Array.isArray(value);
405
+ }
406
+
407
+ // src/core/graph/reuse.ts
408
+ function verifiedNativeRelease(output) {
409
+ const release = /^v(\d+\.\d+\.\d+)$/.exec(output.trim());
410
+ return release?.[1] === "24.16.0";
411
+ }
412
+ function graphReuseEnvironment() {
413
+ return { platform: process.platform, architecture: process.arch, machine: os.hostname() };
414
+ }
415
+ async function restoreCompletedCodeAnalysis(root, run, environment = graphReuseEnvironment()) {
416
+ if (environment.platform !== "win32" || environment.architecture !== "x64") return "cold";
417
+ const nativeRuntime = await run(graphRuntimePaths(root).codegraphNode, ["--version"], {
418
+ cwd: root,
419
+ env: codegraphEnvironment(root)
420
+ });
421
+ if (nativeRuntime.exitCode !== 0 || !verifiedNativeRelease(nativeRuntime.stdout)) return "cold";
422
+ const location = await completedAnalysisCache(root, "codegraph", run, environment);
423
+ if (!location) return "cold";
424
+ let candidates;
425
+ try {
426
+ candidates = await readdir3(location.directory, { withFileTypes: true });
427
+ } catch (cause) {
428
+ if (isEnoent(cause)) return "cold";
429
+ throw cause;
430
+ }
431
+ for (const entry of candidates) {
432
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) continue;
433
+ const candidate = path6.join(location.directory, entry.name);
434
+ if (!await cacheDirectorySafe(location.repository, candidate)) continue;
435
+ const metadata = await readSnapshotMetadata(candidate);
436
+ if (!metadata || Object.keys(metadata).some(
437
+ (key) => !["layout", "corpusPolicy", "repository", "engineVersion", "environment"].includes(key)
438
+ ) || metadata.layout !== 1 || metadata.corpusPolicy !== 1 || metadata.repository !== location.repository || metadata.engineVersion !== GRAPH_VERSIONS.codegraph || !sameEnvironment(metadata.environment, environment))
439
+ continue;
440
+ if (!await safeCompletedCorpus(path6.join(candidate, "analysis"))) continue;
441
+ if (!await safeCodegraphInputs(root, path6.join(candidate, "analysis"), run)) continue;
442
+ const current = graphAnalysisRoot(root, "codegraph");
443
+ const staged = path6.join(path6.dirname(current), `.restoring-codegraph-${randomUUID2()}`);
444
+ await assertGraphStoragePath(root, staged);
445
+ try {
446
+ const cached = path6.join(candidate, "analysis");
447
+ await assertIndependentAnalysis(cached);
448
+ await mkdir3(path6.dirname(staged), { recursive: true });
449
+ await cp(cached, staged, { recursive: true, dereference: false });
450
+ await assertIndependentAnalysis(staged);
451
+ if (!await inspectCodeAnalysisAt(root, staged, run)) continue;
452
+ await replaceGraphDirectory(staged, current, root);
453
+ return "restored";
454
+ } finally {
455
+ await rm3(staged, { recursive: true, force: true });
456
+ }
457
+ }
458
+ return "cold";
459
+ }
460
+ async function completedAnalysisCache(root, engine, run, environment = graphReuseEnvironment()) {
461
+ const git = await run("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
462
+ cwd: root
463
+ });
464
+ if (git.exitCode !== 0 || !path6.isAbsolute(git.stdout.trim())) return void 0;
465
+ const repository = await realpath2(git.stdout.trim());
466
+ return {
467
+ repository,
468
+ directory: path6.join(repository, "qfai-graph-cache", graphEnvironmentKey(environment), engine)
469
+ };
470
+ }
471
+ async function publishCompletedCodeAnalysis(root, run, environment = graphReuseEnvironment()) {
472
+ let staged;
473
+ try {
474
+ if (environment.platform !== "win32" || environment.architecture !== "x64")
475
+ return "unavailable";
476
+ const native = await run(graphRuntimePaths(root).codegraphNode, ["--version"], {
477
+ cwd: root,
478
+ env: codegraphEnvironment(root)
479
+ });
480
+ if (native.exitCode !== 0 || !verifiedNativeRelease(native.stdout)) return "unavailable";
481
+ const cache = await completedAnalysisCache(root, "codegraph", run, environment);
482
+ if (!cache) return "unavailable";
483
+ await ensureCacheDirectory(cache.repository, cache.directory);
484
+ const analysis = graphAnalysisRoot(root, "codegraph");
485
+ if (!await safeCompletedCorpus(analysis) || !await safeCodegraphInputs(root, analysis, run))
486
+ return "unavailable";
487
+ const identity = randomUUID2();
488
+ staged = path6.join(cache.directory, `.publishing-${identity}`);
489
+ await mkdir3(staged);
490
+ await cp(analysis, path6.join(staged, "analysis"), { recursive: true, dereference: false });
491
+ await assertIndependentAnalysis(path6.join(staged, "analysis"));
492
+ if (!await safeCompletedCorpus(path6.join(staged, "analysis")) || !await safeCodegraphInputs(root, path6.join(staged, "analysis"), run))
493
+ return "unavailable";
494
+ if (!await nativeCodeAnalysisCurrent(root, path6.join(staged, "analysis"), run))
495
+ return "unavailable";
496
+ await writeFile(
497
+ path6.join(staged, "completed.json"),
498
+ JSON.stringify({
499
+ layout: 1,
500
+ corpusPolicy: 1,
501
+ repository: cache.repository,
502
+ engineVersion: GRAPH_VERSIONS.codegraph,
503
+ environment
504
+ }) + "\n"
505
+ );
506
+ await rename2(staged, path6.join(cache.directory, identity));
507
+ return "published";
508
+ } catch {
509
+ return "unavailable";
510
+ } finally {
511
+ if (staged) await rm3(staged, { recursive: true, force: true });
512
+ }
513
+ }
514
+ async function ensureCacheDirectory(repository, directory) {
515
+ const relative = path6.relative(repository, directory);
516
+ const parts = relative.split(path6.sep);
517
+ if (path6.isAbsolute(relative) || parts.includes("..") || parts[0] !== "qfai-graph-cache")
518
+ throw new Error("Unsafe completed analysis path.");
519
+ for (let depth = 1; depth <= parts.length; depth += 1) {
520
+ const component = path6.join(repository, ...parts.slice(0, depth));
521
+ let stats;
522
+ try {
523
+ stats = await lstat4(component);
524
+ } catch (cause) {
525
+ if (!isEnoent(cause)) throw cause;
526
+ await mkdir3(component);
527
+ stats = await lstat4(component);
528
+ }
529
+ if (stats.isSymbolicLink() || !stats.isDirectory())
530
+ throw new Error("Unsafe completed analysis path.");
531
+ }
532
+ }
533
+ async function cacheDirectorySafe(repository, candidate) {
534
+ const relative = path6.relative(repository, candidate);
535
+ const parts = relative.split(path6.sep);
536
+ if (path6.isAbsolute(relative) || parts.includes("..") || parts[0] !== "qfai-graph-cache")
537
+ return false;
538
+ for (let depth = 1; depth <= parts.length; depth += 1) {
539
+ const stats = await lstat4(path6.join(repository, ...parts.slice(0, depth)));
540
+ if (stats.isSymbolicLink() || !stats.isDirectory()) return false;
541
+ }
542
+ return true;
543
+ }
544
+ async function readSnapshotMetadata(candidate) {
545
+ try {
546
+ const stats = await lstat4(path6.join(candidate, "completed.json"));
547
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) return void 0;
548
+ const metadata = JSON.parse(
549
+ await readFile(path6.join(candidate, "completed.json"), "utf8")
550
+ );
551
+ return object2(metadata) ? metadata : void 0;
552
+ } catch (cause) {
553
+ if (isEnoent(cause) || cause instanceof SyntaxError) return void 0;
554
+ throw cause;
555
+ }
556
+ }
557
+ function sameEnvironment(value, environment) {
558
+ return object2(value) && Object.keys(value).length === 3 && value.platform === environment.platform && value.architecture === environment.architecture && value.machine === environment.machine;
559
+ }
560
+ async function safeCompletedCorpus(directory, relative = "", engineDirectory = ".codegraph") {
561
+ try {
562
+ for (const entry of await readdir3(path6.join(directory, ...relative.split("/")), {
563
+ withFileTypes: true
564
+ })) {
565
+ const next = relative ? `${relative}/${entry.name}` : entry.name;
566
+ const input = next.startsWith(`${engineDirectory}/`) ? next.slice(engineDirectory.length + 1) : next;
567
+ if (next !== engineDirectory && excludedGraphInput(input) || /\.(?:pid|lock)$/i.test(entry.name))
568
+ return false;
569
+ const stats = await lstat4(path6.join(directory, ...next.split("/")));
570
+ if (stats.isSymbolicLink() || stats.isFile() && stats.nlink !== 1) return false;
571
+ if (stats.isDirectory() && !await safeCompletedCorpus(directory, next, engineDirectory))
572
+ return false;
573
+ if (!stats.isDirectory() && !stats.isFile()) return false;
574
+ }
575
+ return true;
576
+ } catch {
577
+ return false;
578
+ }
579
+ }
580
+ function object2(value) {
581
+ return typeof value === "object" && value !== null && !Array.isArray(value);
582
+ }
583
+ async function safeCodegraphInputs(root, analysis, run) {
584
+ const runtime = graphRuntimePaths(root);
585
+ const result = await run(
586
+ runtime.codegraphNode,
587
+ [
588
+ "--liftoff-only",
589
+ path6.resolve(getInitAssetsDir(), "..", "graph", "codegraph-native.mjs"),
590
+ runtime.codegraphSdk,
591
+ analysis,
592
+ "inventory"
593
+ ],
594
+ { cwd: analysis, env: codegraphEnvironment(root) }
595
+ );
596
+ if (result.exitCode !== 0) return false;
597
+ try {
598
+ const inventory = JSON.parse(result.stdout);
599
+ if (!object2(inventory) || !Array.isArray(inventory.files)) return false;
600
+ for (const file of inventory.files) {
601
+ if (typeof file !== "string" || !file || path6.posix.isAbsolute(file) || path6.win32.isAbsolute(file))
602
+ return false;
603
+ const normalized = file.replaceAll("\\", "/");
604
+ const parts = normalized.split("/");
605
+ if (parts.some((part) => !part || part === "." || part === "..") || excludedGraphInput(normalized))
606
+ return false;
607
+ const stats = await lstat4(path6.join(analysis, ...parts));
608
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) return false;
609
+ }
610
+ return true;
611
+ } catch {
612
+ return false;
613
+ }
614
+ }
615
+
616
+ // src/core/graph/codePreparation.ts
617
+ async function prepareCodeAnalysis(root, run, environment) {
618
+ const current = graphAnalysisRoot(root, "codegraph");
619
+ const staged = path7.join(path7.dirname(current), `.preparing-codegraph-${randomUUID3()}`);
620
+ let created = false;
621
+ try {
622
+ if (await inspectCurrentCodeAnalysis(root, run))
623
+ return { status: "prepared", source: "current" };
624
+ if (await restoreCompletedCodeAnalysis(root, run, environment) === "restored")
625
+ return { status: "prepared", source: "restored" };
626
+ await assertGraphStoragePath(root, staged);
627
+ await mkdir4(staged, { recursive: true });
628
+ created = true;
629
+ await mirrorGraphCorpus(root, staged);
630
+ const runtime = graphRuntimePaths(root);
631
+ const result = await run(
632
+ runtime.codegraphNode,
633
+ [
634
+ "--liftoff-only",
635
+ path7.resolve(getInitAssetsDir(), "..", "graph", "codegraph-native.mjs"),
636
+ runtime.codegraphSdk,
637
+ staged
638
+ ],
639
+ { cwd: staged, env: codegraphEnvironment(root) }
640
+ );
641
+ if (result.exitCode !== 0 || !await inspectCodeAnalysisAt(root, staged, run)) return failed();
642
+ await assertIndependentAnalysis(staged);
643
+ await replaceGraphDirectory(staged, current, root);
644
+ return {
645
+ status: "prepared",
646
+ source: "generated",
647
+ reuse: await publishCompletedCodeAnalysis(root, run, environment)
648
+ };
649
+ } catch {
650
+ return failed();
651
+ } finally {
652
+ if (created) await rm4(staged, { recursive: true, force: true });
653
+ }
654
+ }
655
+ function failed() {
656
+ return {
657
+ status: "failed",
658
+ recovery: "Repair the local CodeGraph runtime and prepare independent analysis."
659
+ };
660
+ }
661
+
662
+ // src/core/graph/documents.ts
663
+ import { mkdir as mkdir7, readFile as readFile3, rm as rm6, writeFile as writeFile3 } from "fs/promises";
664
+ import { randomUUID as randomUUID6 } from "crypto";
665
+
666
+ // src/core/graph/documentReuse.ts
667
+ import { randomUUID as randomUUID4 } from "crypto";
668
+ import { cp as cp2, lstat as lstat5, mkdir as mkdir5, readdir as readdir4, rename as rename3, rm as rm5, writeFile as writeFile2 } from "fs/promises";
669
+ import path8 from "path";
670
+ async function restoreCompletedDocumentAnalysis(root, run, inspect, environment = graphReuseEnvironment()) {
671
+ let staged;
672
+ try {
673
+ if (!await compatible(root, run, environment)) return "cold";
674
+ const cache = await completedAnalysisCache(root, "graphify", run, environment);
675
+ if (!cache) return "cold";
676
+ for (const entry of await readdir4(cache.directory, { withFileTypes: true })) {
677
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) continue;
678
+ const candidate = path8.join(cache.directory, entry.name);
679
+ if (!await cacheDirectorySafe(cache.repository, candidate)) continue;
680
+ const metadata = await readSnapshotMetadata(candidate);
681
+ if (!metadata || Object.keys(metadata).length !== 5 || metadata.layout !== 1 || metadata.corpusPolicy !== 1 || metadata.repository !== cache.repository || metadata.engineVersion !== GRAPH_VERSIONS.graphify || !sameEnvironment(metadata.environment, environment))
682
+ continue;
683
+ const analysis = path8.join(candidate, "analysis");
684
+ if (!await safeDocumentCopy(root, analysis, run)) continue;
685
+ const current = graphAnalysisRoot(root, "graphify");
686
+ staged = path8.join(path8.dirname(current), `.restoring-graphify-${randomUUID4()}`);
687
+ await assertGraphStoragePath(root, staged);
688
+ await mkdir5(path8.dirname(staged), { recursive: true });
689
+ await cp2(analysis, staged, { recursive: true, dereference: false });
690
+ await assertIndependentAnalysis(staged);
691
+ if (await inspect(staged, true)) {
692
+ await replaceGraphDirectory(staged, current, root);
693
+ return "restored";
694
+ }
695
+ await rm5(staged, { recursive: true, force: true });
696
+ staged = void 0;
697
+ }
698
+ } catch {
699
+ return "cold";
700
+ } finally {
701
+ if (staged) await discard(staged);
702
+ }
703
+ return "cold";
704
+ }
705
+ async function publishCompletedDocumentAnalysis(root, run, inspect, environment = graphReuseEnvironment()) {
706
+ let staged;
707
+ try {
708
+ if (!await compatible(root, run, environment)) return "unavailable";
709
+ const cache = await completedAnalysisCache(root, "graphify", run, environment);
710
+ if (!cache) return "unavailable";
711
+ await ensureCacheDirectory(cache.repository, cache.directory);
712
+ const analysis = graphAnalysisRoot(root, "graphify");
713
+ if (!await safeDocumentCopy(root, analysis, run)) return "unavailable";
714
+ const identity = randomUUID4();
715
+ staged = path8.join(cache.directory, `.publishing-${identity}`);
716
+ await mkdir5(staged);
717
+ const copy = path8.join(staged, "analysis");
718
+ await cp2(analysis, copy, { recursive: true, dereference: false });
719
+ await assertIndependentAnalysis(copy);
720
+ if (!await safeDocumentCopy(root, copy, run) || !await inspect(copy, false))
721
+ return "unavailable";
722
+ await writeFile2(
723
+ path8.join(staged, "completed.json"),
724
+ JSON.stringify({
725
+ layout: 1,
726
+ corpusPolicy: 1,
727
+ repository: cache.repository,
728
+ engineVersion: GRAPH_VERSIONS.graphify,
729
+ environment
730
+ }) + "\n"
731
+ );
732
+ await rename3(staged, path8.join(cache.directory, identity));
733
+ return "published";
734
+ } catch {
735
+ return "unavailable";
736
+ } finally {
737
+ if (staged) await discard(staged);
738
+ }
739
+ }
740
+ async function compatible(root, run, environment) {
741
+ if (environment.platform !== "win32" || environment.architecture !== "x64") return false;
742
+ const result = await run(
743
+ graphRuntimePaths(root).python,
744
+ [...GRAPH_PYTHON_FLAGS, "-c", "import sys; print(sys.version.split()[0])"],
745
+ { cwd: root, env: graphifyEnvironment(root) }
746
+ );
747
+ return result.exitCode === 0 && result.stdout.trim() === "3.12.13";
748
+ }
749
+ async function safeDocumentCopy(root, analysis, run) {
750
+ if (!await safeCompletedCorpus(analysis, "", "graphify-out")) return false;
751
+ const result = await run(
752
+ graphRuntimePaths(root).python,
753
+ [
754
+ ...GRAPH_PYTHON_FLAGS,
755
+ path8.resolve(getInitAssetsDir(), "..", "graph", "graphify-native.py"),
756
+ path8.toNamespacedPath(analysis),
757
+ "inventory"
758
+ ],
759
+ { cwd: analysis, env: graphifyCorpusEnvironment(root, analysis) }
760
+ );
761
+ if (result.exitCode !== 0) return false;
762
+ try {
763
+ const inventory = JSON.parse(result.stdout);
764
+ if (typeof inventory !== "object" || inventory === null || Object.keys(inventory).length !== 1 || !("files" in inventory) || !Array.isArray(inventory.files))
765
+ return false;
766
+ for (const file of inventory.files) {
767
+ if (typeof file !== "string" || path8.posix.isAbsolute(file) || path8.win32.isAbsolute(file))
768
+ return false;
769
+ const parts = file.replaceAll("\\", "/").split("/");
770
+ if (parts.some((part) => !part || part === "." || part === "..") || excludedGraphInput(parts.join("/")))
771
+ return false;
772
+ const stats = await lstat5(path8.join(analysis, ...parts));
773
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) return false;
774
+ }
775
+ return true;
776
+ } catch {
777
+ return false;
778
+ }
779
+ }
780
+ async function discard(directory) {
781
+ try {
782
+ await rm5(directory, { recursive: true, force: true });
783
+ } catch {
784
+ }
785
+ }
786
+
787
+ // src/core/graph/locks.ts
788
+ import { randomUUID as randomUUID5 } from "crypto";
789
+ import { unlink } from "fs/promises";
790
+ import path11 from "path";
791
+
792
+ // src/core/graph/localFiles.ts
793
+ import { lstat as lstat7, mkdir as mkdir6, open as open2 } from "fs/promises";
794
+ import path10 from "path";
795
+
796
+ // src/core/init/fsGuards.ts
797
+ import { constants } from "fs";
798
+ import { access as access2, lstat as lstat6, open, readFile as readFile2, stat } from "fs/promises";
799
+ import path9 from "path";
800
+ async function readPinnedRegularFileBytes(filePath, maxBytes) {
801
+ let handle;
802
+ try {
803
+ handle = await open(filePath, OPEN_READ_FLAGS);
804
+ const pinned = await handle.stat();
805
+ if (!pinned.isFile() || pinned.size > maxBytes) return null;
806
+ const buffer = Buffer.alloc(maxBytes + 1);
807
+ let filled = 0;
808
+ while (filled < buffer.length) {
809
+ const { bytesRead } = await handle.read(buffer, filled, buffer.length - filled, filled);
810
+ if (bytesRead === 0) break;
811
+ filled += bytesRead;
812
+ }
813
+ if (filled > maxBytes) return null;
814
+ return {
815
+ content: Buffer.from(buffer.subarray(0, filled)),
816
+ mode: pinned.mode & 4095,
817
+ uid: pinned.uid,
818
+ gid: pinned.gid,
819
+ dev: pinned.dev,
820
+ ino: pinned.ino
821
+ };
822
+ } catch (error) {
823
+ const code = error?.code;
824
+ if (code === "ENXIO" || code === "EISDIR") return null;
825
+ throw error;
826
+ } finally {
827
+ await handle?.close();
828
+ }
829
+ }
830
+ var OPEN_READ_FLAGS = typeof constants.O_NONBLOCK === "number" ? constants.O_RDONLY | constants.O_NONBLOCK : constants.O_RDONLY;
831
+
832
+ // src/core/graph/localFiles.ts
833
+ var GRAPH_STATE_BYTES = 32768;
834
+ var GRAPH_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
835
+ var GraphLocalStateError = class extends Error {
836
+ constructor(message, recovery, options) {
837
+ super(message, options);
838
+ this.recovery = recovery;
839
+ }
840
+ };
841
+ async function graphStateDirectory(root) {
842
+ const directory = path10.join(root, ".qfai", "tools", "state", graphEnvironmentKey());
843
+ await assertGraphStoragePath(root, directory);
844
+ await mkdir6(directory, { recursive: true });
845
+ await assertGraphStoragePath(root, directory);
846
+ return directory;
847
+ }
848
+ function invalidGraphState() {
849
+ return new GraphLocalStateError(
850
+ "Project graph permission state is invalid.",
851
+ "Retain the local state and repair .qfai/tools/state before retrying the graph CLI or hook."
852
+ );
853
+ }
854
+ function graphStateText(value, maximum) {
855
+ return typeof value === "string" && value.trim().length > 0 && value.length <= maximum && !value.includes("\0");
856
+ }
857
+ async function readGraphLocalFile(file) {
858
+ try {
859
+ const before = await lstat7(file);
860
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
861
+ throw invalidGraphState();
862
+ const pinned = await readPinnedRegularFileBytes(file, GRAPH_STATE_BYTES);
863
+ const after = await lstat7(file);
864
+ if (!pinned || before.dev !== pinned.dev || before.ino !== pinned.ino || after.dev !== pinned.dev || after.ino !== pinned.ino || !after.isFile() || after.isSymbolicLink() || after.nlink !== 1)
865
+ throw invalidGraphState();
866
+ return pinned.content.toString("utf8");
867
+ } catch (cause) {
868
+ if (isEnoent(cause)) return void 0;
869
+ throw cause;
870
+ }
871
+ }
872
+ async function writeGraphLocalFile(file, value) {
873
+ const text = JSON.stringify(value);
874
+ if (Buffer.byteLength(text) > GRAPH_STATE_BYTES) throw invalidGraphState();
875
+ const handle = await open2(file, "wx", 384);
876
+ try {
877
+ await handle.writeFile(text, "utf8");
878
+ } finally {
879
+ await handle.close();
880
+ }
881
+ }
882
+
883
+ // src/core/graph/locks.ts
884
+ async function withGraphLock(root, kind, action) {
885
+ const directory = await graphStateDirectory(root);
886
+ const file = path11.join(directory, `${kind}.lock`);
887
+ const owner = {
888
+ kind: "graph-lock",
889
+ pid: process.pid,
890
+ createdAt: Date.now(),
891
+ token: randomUUID5()
892
+ };
893
+ try {
894
+ await writeGraphLocalFile(file, owner);
895
+ } catch (cause) {
896
+ if (!hasErrnoCode(cause) || cause.code !== "EEXIST") throw cause;
897
+ const text = await readGraphLocalFile(file);
898
+ let active = false;
899
+ try {
900
+ const existing = text === void 0 ? void 0 : JSON.parse(text);
901
+ if (typeof existing !== "object" || existing === null || Array.isArray(existing))
902
+ throw new Error("Invalid graph lock metadata.", { cause });
903
+ const value = existing;
904
+ if (Object.keys(value).length !== 4 || value.kind !== "graph-lock" || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0 || typeof value.createdAt !== "number" || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || typeof value.token !== "string" || !GRAPH_UUID.test(value.token))
905
+ throw new Error("Invalid graph lock metadata.", { cause });
906
+ try {
907
+ process.kill(value.pid, 0);
908
+ active = true;
909
+ } catch (liveness) {
910
+ if (!hasErrnoCode(liveness) || liveness.code !== "ESRCH") active = true;
911
+ }
912
+ } catch {
913
+ }
914
+ throw new GraphLocalStateError(
915
+ `Project graph ${kind} lock ${active ? "is contended" : "requires manual recovery"}.`,
916
+ active ? `Retry after the active worktree ${kind} operation finishes; retain its lock.` : `Retain and inspect .qfai/tools/state/${graphEnvironmentKey()}/${kind}.lock; remove only a confirmed abandoned worktree lock before retrying.`,
917
+ { cause }
918
+ );
919
+ }
920
+ try {
921
+ return await action();
922
+ } finally {
923
+ await releaseGraphLock(file, JSON.stringify(owner));
924
+ }
925
+ }
926
+ async function releaseGraphLock(file, owner) {
927
+ const text = await readGraphLocalFile(file);
928
+ if (text !== owner)
929
+ throw new GraphLocalStateError(
930
+ "Project graph lock ownership changed.",
931
+ "Retain the lock and repair the worktree graph state before retrying."
932
+ );
933
+ await unlink(file);
934
+ }
935
+
936
+ // src/core/graph/documents.ts
937
+ import path12 from "path";
938
+ function notCurrent() {
939
+ return { current: false, unsupported: [], unreadable: [] };
940
+ }
941
+ var DOCUMENT_PROCESSING_SETUP = 'Configure a selected local backend and model in .qfai/graph-processing.json using .agents/rules/graph-exploration.md, then run node .qfai/tools/graph-cli.mjs documents "<question>".';
942
+ async function prepareDocumentAnalysisLocked(root, run = runGraphProcess, approveExternal = () => Promise.resolve(false), environment) {
943
+ if (!await graphifyConfigurationSafe(root)) return providerConflict();
944
+ if (await inspectDocumentAnalysis(root, run)) return { status: "prepared", source: "current" };
945
+ const processing = await readGraphProcessing(root);
946
+ if (!processing) {
947
+ return {
948
+ status: "not-ready",
949
+ recovery: DOCUMENT_PROCESSING_SETUP
950
+ };
951
+ }
952
+ const inspect = async (analysis, refresh) => (await inspectDocumentScopeAt(root, analysis, run, refresh)).current;
953
+ if (await restoreCompletedDocumentAnalysis(root, run, inspect, environment) === "restored")
954
+ return { status: "prepared", source: "restored" };
955
+ const endpoint = processing.endpoint ?? "http://127.0.0.1:11434/v1";
956
+ const selected = new URL(endpoint);
957
+ const backend = processing.backend;
958
+ const credential = typeof processing.credentialEnv === "string" ? process.env[processing.credentialEnv] : void 0;
959
+ if (processing.credentialEnv !== void 0 && !credential) {
960
+ return {
961
+ status: "not-ready",
962
+ recovery: "Set the explicitly selected credential environment variable before document processing."
963
+ };
964
+ }
965
+ const local = ["127.0.0.1", "localhost", "[::1]"].includes(selected.hostname);
966
+ let scope;
967
+ if (processing.scope !== void 0) {
968
+ if (!validDocumentScope(processing.scope))
969
+ return { status: "not-ready", recovery: "Select the exact document scope for processing." };
970
+ scope = Object.freeze(processing.scope.map((item) => item.replaceAll("\\", "/")));
971
+ }
972
+ if (!local) {
973
+ if (!scope) {
974
+ return {
975
+ status: "not-ready",
976
+ recovery: "Select the exact document scope for external processing."
977
+ };
978
+ }
979
+ const approval = await approveExternal(
980
+ Object.freeze({
981
+ destination: endpoint,
982
+ scope,
983
+ backend,
984
+ model: processing.model,
985
+ worktree: root
986
+ })
987
+ );
988
+ if (approval !== true) {
989
+ return {
990
+ status: "not-ready",
991
+ recovery: "External document processing requires explicit human authorization of its destination and scope."
992
+ };
993
+ }
994
+ }
995
+ const current = graphAnalysisRoot(root, "graphify");
996
+ const staged = path12.join(path12.dirname(current), `.preparing-graphify-${randomUUID6()}`);
997
+ await assertGraphStoragePath(root, staged);
998
+ await mkdir7(staged, { recursive: true });
999
+ try {
1000
+ await mirrorGraphCorpus(root, staged, scope);
1001
+ const result = await run(
1002
+ graphRuntimePaths(root).python,
1003
+ [
1004
+ ...GRAPH_PYTHON_FLAGS,
1005
+ "-m",
1006
+ "graphify",
1007
+ "extract",
1008
+ path12.toNamespacedPath(staged),
1009
+ "--backend",
1010
+ backend,
1011
+ "--model",
1012
+ processing.model,
1013
+ "--no-cluster",
1014
+ "--no-dedup",
1015
+ "--max-workers",
1016
+ "1",
1017
+ "--max-concurrency",
1018
+ "1"
1019
+ ],
1020
+ {
1021
+ cwd: staged,
1022
+ env: {
1023
+ ...graphifyCorpusEnvironment(root, staged),
1024
+ [backend === "ollama" ? "OLLAMA_BASE_URL" : "OPENAI_BASE_URL"]: endpoint,
1025
+ ...credential ? { [backend === "ollama" ? "OLLAMA_API_KEY" : "OPENAI_API_KEY"]: credential } : {}
1026
+ }
1027
+ }
1028
+ );
1029
+ if (result.exitCode !== 0 || !await currentGraphifyCorpus(root, staged, graphRuntimePaths(root).python, run)) {
1030
+ return {
1031
+ status: "failed",
1032
+ recovery: "Check the selected local model and repair Graphify document analysis."
1033
+ };
1034
+ }
1035
+ await writeFile3(
1036
+ path12.join(staged, "graphify-out", "managed.json"),
1037
+ JSON.stringify({ engineVersion: GRAPH_VERSIONS.graphify, scope: "documents", processing })
1038
+ );
1039
+ await assertIndependentAnalysis(staged);
1040
+ await replaceGraphDirectory(staged, current, root);
1041
+ return {
1042
+ status: "prepared",
1043
+ source: "generated",
1044
+ reuse: await publishCompletedDocumentAnalysis(root, run, inspect, environment)
1045
+ };
1046
+ } finally {
1047
+ await rm6(staged, { recursive: true, force: true });
1048
+ }
1049
+ }
1050
+ async function readGraphProcessing(root) {
1051
+ const processing = await readOptionalJson(path12.join(root, ".qfai", "graph-processing.json"));
1052
+ return validGraphProcessing(processing) ? processing : void 0;
1053
+ }
1054
+ function validGraphProcessing(processing) {
1055
+ const allowed = /* @__PURE__ */ new Set(["backend", "model", "endpoint", "scope", "credentialEnv"]);
1056
+ if (!object3(processing) || Object.keys(processing).some((key) => !allowed.has(key)) || processing.backend !== "ollama" && processing.backend !== "openai" || typeof processing.model !== "string" || !processing.model.trim() || processing.scope !== void 0 && !validDocumentScope(processing.scope) || processing.credentialEnv !== void 0 && (typeof processing.credentialEnv !== "string" || !/^[A-Z_][A-Z0-9_]*$/.test(processing.credentialEnv)))
1057
+ return false;
1058
+ if (processing.endpoint !== void 0) {
1059
+ if (typeof processing.endpoint !== "string" || !processing.endpoint.trim()) return false;
1060
+ try {
1061
+ const endpoint = new URL(processing.endpoint);
1062
+ if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash)
1063
+ return false;
1064
+ } catch {
1065
+ return false;
1066
+ }
1067
+ }
1068
+ return true;
1069
+ }
1070
+ async function inspectDocumentAnalysis(root, run) {
1071
+ return (await inspectDocumentScope(root, run)).current;
1072
+ }
1073
+ async function inspectDocumentScope(root, run) {
1074
+ const analysis = graphAnalysisRoot(root, "graphify");
1075
+ await assertGraphStoragePath(root, analysis);
1076
+ return inspectDocumentScopeAt(root, analysis, run);
1077
+ }
1078
+ async function inspectDocumentScopeAt(root, analysis, run, refresh = true) {
1079
+ const managed = await readOptionalJson(path12.join(analysis, "graphify-out", "managed.json"));
1080
+ const selected = await readGraphProcessing(root);
1081
+ const processing = await processingForCompletedQuery(
1082
+ root,
1083
+ selected,
1084
+ object3(managed) ? managed.processing : void 0
1085
+ );
1086
+ if (!object3(managed) || Object.keys(managed).length !== 3 || managed.engineVersion !== GRAPH_VERSIONS.graphify || managed.scope !== "documents" || !sameProcessing(managed.processing, processing))
1087
+ return notCurrent();
1088
+ if (!await safeDocumentCopy(root, analysis, run)) return notCurrent();
1089
+ const scope = object3(processing) && validDocumentScope(processing.scope) ? processing.scope : void 0;
1090
+ if (refresh) await mirrorGraphCorpus(root, analysis, scope);
1091
+ return nativeGraphifyScope(root, analysis, graphRuntimePaths(root).python, run);
1092
+ }
1093
+ async function processingForCompletedQuery(root, selected, recorded) {
1094
+ if (selected !== void 0) return selected;
1095
+ try {
1096
+ await readFile3(path12.join(root, ".qfai", "graph-processing.json"));
1097
+ return void 0;
1098
+ } catch (cause) {
1099
+ if (!isEnoent(cause)) throw cause;
1100
+ }
1101
+ if (!validGraphProcessing(recorded)) return void 0;
1102
+ return recorded;
1103
+ }
1104
+ async function currentGraphifyCorpus(root, analysis, python, run, kind = "semantic") {
1105
+ return (await nativeGraphifyScope(root, analysis, python, run, kind)).current;
1106
+ }
1107
+ async function nativeGraphifyScope(root, analysis, python, run, kind = "semantic") {
1108
+ if (!await graphifyConfigurationSafe(root)) return notCurrent();
1109
+ const result = await run(
1110
+ python,
1111
+ [
1112
+ ...GRAPH_PYTHON_FLAGS,
1113
+ path12.resolve(getInitAssetsDir(), "..", "graph", "graphify-native.py"),
1114
+ path12.toNamespacedPath(analysis),
1115
+ kind
1116
+ ],
1117
+ {
1118
+ cwd: analysis,
1119
+ env: graphifyCorpusEnvironment(root, analysis)
1120
+ }
1121
+ );
1122
+ if (result.exitCode !== 0) return notCurrent();
1123
+ let state;
1124
+ try {
1125
+ state = JSON.parse(result.stdout);
1126
+ } catch (cause) {
1127
+ if (!(cause instanceof SyntaxError)) throw cause;
1128
+ return notCurrent();
1129
+ }
1130
+ if (!object3(state) || typeof state.current !== "boolean" || !stringArray2(state.unsupported) || !stringArray2(state.unreadable))
1131
+ return notCurrent();
1132
+ const sourcePath = (file) => path12.isAbsolute(file) ? path12.relative(path12.toNamespacedPath(analysis), path12.toNamespacedPath(file)).split(path12.sep).join("/") : file;
1133
+ return {
1134
+ current: state.current,
1135
+ unsupported: state.unsupported.map(sourcePath),
1136
+ unreadable: state.unreadable.map(sourcePath)
1137
+ };
1138
+ }
1139
+ async function readOptionalJson(file) {
1140
+ try {
1141
+ return JSON.parse(await readFile3(file, "utf8"));
1142
+ } catch (cause) {
1143
+ if (isEnoent(cause) || cause instanceof SyntaxError) return void 0;
1144
+ throw cause;
1145
+ }
1146
+ }
1147
+ function object3(value) {
1148
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1149
+ }
1150
+ function sameProcessing(left, right) {
1151
+ if (!object3(left) || !object3(right)) return false;
1152
+ const fields = ["backend", "model", "endpoint", "credentialEnv", "scope"];
1153
+ if (Object.keys(left).some((key) => !fields.includes(key))) return false;
1154
+ return fields.every((key) => JSON.stringify(left[key]) === JSON.stringify(right[key]));
1155
+ }
1156
+ function stringArray2(value) {
1157
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1158
+ }
1159
+ function validDocumentScope(value) {
1160
+ return Array.isArray(value) && value.length > 0 && value.every(
1161
+ (item) => typeof item === "string" && item !== "" && !path12.isAbsolute(item) && !item.split(/[\\/]/).some((part) => part === ".." || part === "." || part === "")
1162
+ );
1163
+ }
1164
+ function providerConflict() {
1165
+ return {
1166
+ status: "not-ready",
1167
+ recovery: "Review private Graphify provider settings and unsafe home paths before processing; existing settings were retained."
1168
+ };
1169
+ }
1170
+
1171
+ // src/core/graph/query.ts
1172
+ import { readFile as readFile4 } from "fs/promises";
1173
+ import path13 from "path";
1174
+ async function exploreGraph(root, request, run = runGraphProcess) {
1175
+ const requestedScope = Object.freeze({ ...request });
1176
+ try {
1177
+ return { ...await exploreGraphScope(root, request, run), requestedScope };
1178
+ } catch {
1179
+ const engine = request.domain === "code" ? "codegraph" : "graphify";
1180
+ return {
1181
+ requestedScope,
1182
+ engine,
1183
+ status: "failed",
1184
+ failure: `${engine === "codegraph" ? "CodeGraph" : "Graphify"} operation failed.`,
1185
+ recovery: `Repair ${engine === "codegraph" ? "CodeGraph" : "Graphify"} analysis and retry the graph query.`
1186
+ };
1187
+ }
1188
+ }
1189
+ async function exploreGraphScope(root, request, run) {
1190
+ const documentScope = request.domain === "documents" ? await inspectDocumentScope(root, run) : void 0;
1191
+ if (documentScope?.current) {
1192
+ const result = await run(
1193
+ graphRuntimePaths(root).python,
1194
+ [
1195
+ ...GRAPH_PYTHON_FLAGS,
1196
+ "-m",
1197
+ "graphify",
1198
+ "query",
1199
+ request.question,
1200
+ "--graph",
1201
+ path13.toNamespacedPath(
1202
+ path13.join(graphAnalysisRoot(root, "graphify"), "graphify-out", "graph.json")
1203
+ )
1204
+ ],
1205
+ {
1206
+ cwd: graphAnalysisRoot(root, "graphify"),
1207
+ env: graphifyCorpusEnvironment(root, graphAnalysisRoot(root, "graphify"))
1208
+ }
1209
+ );
1210
+ if (result.exitCode !== 0 && result.exitCode !== 4) {
1211
+ return {
1212
+ engine: "graphify",
1213
+ status: "failed",
1214
+ failure: "Graphify query failed.",
1215
+ recovery: "Repair Graphify analysis and retry the graph query."
1216
+ };
1217
+ }
1218
+ const coverage = {
1219
+ unsupported: documentScope.unsupported,
1220
+ unreadable: documentScope.unreadable
1221
+ };
1222
+ const inference = result.stdout.includes("[INFERRED]") ? {
1223
+ inferenceNotice: "Inferred relationships need source confirmation; they do not establish semantic correctness or complete coverage."
1224
+ } : {};
1225
+ if (coverage.unsupported.length > 0 || coverage.unreadable.length > 0) {
1226
+ return {
1227
+ engine: "graphify",
1228
+ status: "incomplete",
1229
+ answer: result.stdout,
1230
+ coverage,
1231
+ ...inference,
1232
+ recovery: "Review unsupported and unreadable scope; this result does not establish absence."
1233
+ };
1234
+ }
1235
+ return {
1236
+ engine: "graphify",
1237
+ status: result.exitCode === 4 ? "no-match" : "answered",
1238
+ answer: result.stdout,
1239
+ coverage,
1240
+ ...inference
1241
+ };
1242
+ }
1243
+ if (request.domain === "code" && await inspectCurrentCodeAnalysis(root, run)) {
1244
+ const result = await run(
1245
+ process.execPath,
1246
+ [
1247
+ graphRuntimePaths(root).codegraph,
1248
+ "context",
1249
+ request.question,
1250
+ "--path",
1251
+ graphAnalysisRoot(root, "codegraph"),
1252
+ "--format",
1253
+ "json",
1254
+ "--no-code"
1255
+ ],
1256
+ {
1257
+ cwd: root,
1258
+ env: codegraphEnvironment(root)
1259
+ }
1260
+ );
1261
+ const context = nativeCodeContext(result.stdout, request.question);
1262
+ if (result.exitCode !== 0 || !context)
1263
+ return {
1264
+ engine: "codegraph",
1265
+ status: "failed",
1266
+ failure: "CodeGraph query failed or returned unusable data.",
1267
+ recovery: "Repair CodeGraph analysis and retry the graph query."
1268
+ };
1269
+ const coverage = await inspectCodeCoverage(root, run);
1270
+ if (!coverage.complete)
1271
+ return {
1272
+ engine: "codegraph",
1273
+ status: "incomplete",
1274
+ answer: result.stdout,
1275
+ coverage,
1276
+ recovery: "Review unknown, unsupported, unreadable and unparsed code scope; this result does not establish absence."
1277
+ };
1278
+ return {
1279
+ engine: "codegraph",
1280
+ status: context.empty ? "no-match" : "answered",
1281
+ answer: result.stdout,
1282
+ coverage,
1283
+ ...context.empty ? {
1284
+ inferenceNotice: "No match in this context search over the confirmed native code scope; this does not establish global absence."
1285
+ } : {}
1286
+ };
1287
+ }
1288
+ return {
1289
+ engine: request.domain === "code" ? "codegraph" : "graphify",
1290
+ status: "not-ready",
1291
+ recovery: request.domain === "documents" && await readGraphProcessing(root) === void 0 ? "Select document processing in .qfai/graph-processing.json before preparing document analysis." : "Prepare the requested graph analysis before querying it."
1292
+ };
1293
+ }
1294
+ function nativeCodeContext(stdout, question) {
1295
+ let value;
1296
+ try {
1297
+ value = JSON.parse(stdout);
1298
+ } catch {
1299
+ return void 0;
1300
+ }
1301
+ if (!object4(value) || value.query !== question || typeof value.summary !== "string")
1302
+ return void 0;
1303
+ const arrays = ["entryPoints", "nodes", "edges", "codeBlocks", "relatedFiles"];
1304
+ if (arrays.some((key) => !Array.isArray(value[key])) || !object4(value.stats)) return void 0;
1305
+ const stats = value.stats;
1306
+ const counts = ["nodeCount", "edgeCount", "fileCount", "codeBlockCount", "totalCodeSize"];
1307
+ if (counts.some((key) => !Number.isSafeInteger(stats[key]) || Number(stats[key]) < 0))
1308
+ return void 0;
1309
+ for (const [count, entries] of [
1310
+ ["nodeCount", "nodes"],
1311
+ ["edgeCount", "edges"],
1312
+ ["fileCount", "relatedFiles"],
1313
+ ["codeBlockCount", "codeBlocks"]
1314
+ ]) {
1315
+ const list = value[entries];
1316
+ if (!Array.isArray(list) || stats[count] !== list.length) return void 0;
1317
+ }
1318
+ const empty = stats.nodeCount === 0;
1319
+ if (empty && (arrays.some((key) => Array.isArray(value[key]) && value[key].length !== 0) || counts.some((key) => stats[key] !== 0)))
1320
+ return void 0;
1321
+ return { empty };
1322
+ }
1323
+ function object4(value) {
1324
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1325
+ }
1326
+
1327
+ // src/core/graph/runner.ts
1328
+ async function queryPreparedGraph(root, request, run = runGraphProcess) {
1329
+ return withGraphLock(root, "analysis", async () => {
1330
+ const initial = await exploreGraph(root, request, run);
1331
+ if (initial.status !== "not-ready") return initial;
1332
+ const preparation = request.domain === "code" ? await prepareCodeAnalysis(root, run) : await prepareDocumentAnalysisLocked(root, run);
1333
+ if (preparation.status !== "prepared")
1334
+ return {
1335
+ ...initial,
1336
+ status: preparation.status === "failed" ? "failed" : "not-ready",
1337
+ ...preparation.status === "failed" ? {
1338
+ failure: `${initial.engine === "codegraph" ? "CodeGraph" : "Graphify"} analysis preparation failed.`
1339
+ } : {},
1340
+ ...preparation.recovery ? { recovery: preparation.recovery } : {}
1341
+ };
1342
+ return exploreGraph(root, request, run);
1343
+ });
1344
+ }
1345
+
1346
+ // src/core/graph/fallbackState.ts
1347
+ import { randomUUID as randomUUID7 } from "crypto";
1348
+ import { rename as rename4, unlink as unlink3 } from "fs/promises";
1349
+ import path15 from "path";
1350
+
1351
+ // src/core/graph/permissionRecords.ts
1352
+ import { opendir, unlink as unlink2 } from "fs/promises";
1353
+ import path14 from "path";
1354
+ async function readGraphSearchRecords(root, directory) {
1355
+ const records = [];
1356
+ const entries = await opendir(directory);
1357
+ for await (const entry of entries) {
1358
+ if (entry.name === "state.lock" || entry.name === "analysis.lock") continue;
1359
+ if (records.length >= 128) throw fullGraphState();
1360
+ const match = /^(.+)\.(pending|grant|draft)\.json$/.exec(entry.name);
1361
+ if (!match || !GRAPH_UUID.test(match[1] ?? "")) throw invalidGraphState();
1362
+ const file = path14.join(directory, entry.name);
1363
+ const text = await readGraphLocalFile(file);
1364
+ if (text === void 0) throw invalidGraphState();
1365
+ let input;
1366
+ try {
1367
+ input = JSON.parse(text);
1368
+ } catch {
1369
+ throw invalidGraphState();
1370
+ }
1371
+ const record = parseGraphSearchRecord(input, root);
1372
+ if (!record || record.id !== match[1] || (match[2] === "pending" ? record.kind !== "pending" : record.kind !== "grant"))
1373
+ throw invalidGraphState();
1374
+ records.push({ file, record, draft: match[2] === "draft" });
1375
+ }
1376
+ const current = [];
1377
+ for (const item of records) {
1378
+ if (Date.now() >= item.record.expiresAt) await unlink2(item.file);
1379
+ else current.push(item);
1380
+ }
1381
+ return current;
1382
+ }
1383
+ function fullGraphState() {
1384
+ return new GraphLocalStateError(
1385
+ "Project graph permission state is full.",
1386
+ "Retry after pending attempts or search grants expire; retain valid worktree state."
1387
+ );
1388
+ }
1389
+ function parseGraphSearchRecord(input, root) {
1390
+ if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
1391
+ const value = input;
1392
+ const fields = [
1393
+ "id",
1394
+ "kind",
1395
+ "domain",
1396
+ "target",
1397
+ "operation",
1398
+ "worktree",
1399
+ "session",
1400
+ "createdAt",
1401
+ "expiresAt"
1402
+ ];
1403
+ if (value.kind === "grant") fields.push("reason");
1404
+ if (Object.keys(value).length !== fields.length || fields.some((field) => !Object.hasOwn(value, field)) || typeof value.id !== "string" || !GRAPH_UUID.test(value.id) || value.kind !== "pending" && value.kind !== "grant" || value.domain !== "code" && value.domain !== "documents" || !graphStateText(value.target, 4096) || !graphStateText(value.operation, 128) || !graphStateText(value.worktree, 4096) || value.worktree !== path14.resolve(root) || !graphStateText(value.session, 4096) || typeof value.createdAt !== "number" || typeof value.expiresAt !== "number" || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || value.createdAt > Date.now() || !Number.isSafeInteger(value.expiresAt) || value.expiresAt !== value.createdAt + (value.kind === "pending" ? 6e5 : 6e4))
1405
+ return void 0;
1406
+ const scope = {
1407
+ id: value.id,
1408
+ domain: value.domain,
1409
+ target: value.target,
1410
+ operation: value.operation,
1411
+ worktree: value.worktree,
1412
+ session: value.session,
1413
+ createdAt: value.createdAt,
1414
+ expiresAt: value.expiresAt
1415
+ };
1416
+ if (value.kind === "pending") return { ...scope, kind: "pending" };
1417
+ return graphStateText(value.reason, 2048) ? { ...scope, kind: "grant", reason: value.reason } : void 0;
1418
+ }
1419
+
1420
+ // src/core/graph/fallbackState.ts
1421
+ async function requestStoredGraphFallback(root, attemptId, question, reason, run = runGraphProcess) {
1422
+ const observed = await withGraphLock(root, "state", async () => {
1423
+ const directory = await graphStateDirectory(root);
1424
+ const records = await readGraphSearchRecords(root, directory);
1425
+ const pending = records.find(
1426
+ (item) => item.record.kind === "pending" && item.record.id === attemptId
1427
+ );
1428
+ if (!pending || pending.record.kind !== "pending") throw invalidGraphState();
1429
+ return pending.record;
1430
+ });
1431
+ const graph = await queryPreparedGraph(root, { domain: observed.domain, question }, run);
1432
+ const publication = await withGraphLock(root, "state", async () => {
1433
+ const directory = await graphStateDirectory(root);
1434
+ const records = await readGraphSearchRecords(root, directory);
1435
+ const pending = records.find(
1436
+ (item) => item.record.kind === "pending" && item.record.id === attemptId
1437
+ );
1438
+ if (!pending || JSON.stringify(pending.record) !== JSON.stringify(observed))
1439
+ throw invalidGraphState();
1440
+ if (records.length >= 128) throw fullGraphState();
1441
+ const createdAt = Date.now();
1442
+ const grant = {
1443
+ ...observed,
1444
+ kind: "grant",
1445
+ reason,
1446
+ createdAt,
1447
+ expiresAt: createdAt + 6e4
1448
+ };
1449
+ const draft = path15.join(directory, `${grant.id}.draft.json`);
1450
+ await writeGraphLocalFile(draft, grant);
1451
+ await unlink3(pending.file);
1452
+ return { directory, draft, grant };
1453
+ });
1454
+ await assertGraphStoragePath(root, publication.directory);
1455
+ if (Date.now() >= publication.grant.expiresAt) throw invalidGraphState();
1456
+ await rename4(
1457
+ publication.draft,
1458
+ path15.join(publication.directory, `${publication.grant.id}.grant.json`)
1459
+ );
1460
+ const { target, operation, worktree, session, expiresAt } = publication.grant;
1461
+ return {
1462
+ status: "ready",
1463
+ permitted: true,
1464
+ domain: observed.domain,
1465
+ scope: { target, operation, worktree, session, reason, expiresAt },
1466
+ graph
1467
+ };
1468
+ }
1469
+
1470
+ // src/cli/graphCli.ts
1471
+ var QUERY_USAGE = 'Run node .qfai/tools/graph-cli.mjs code "<question>" or documents "<question>". For a denied search, run node .qfai/tools/graph-cli.mjs fallback --attempt-id <observed-UUID> --question "<unresolved question>" --reason "<graph limitation>". Supply only the listed arguments.';
1472
+ async function runInstalledGraphCli(root, args, io = {
1473
+ output: (text) => {
1474
+ process.stdout.write(text);
1475
+ },
1476
+ fail: () => {
1477
+ process.exitCode = 2;
1478
+ }
1479
+ }, run = runGraphProcess) {
1480
+ const request = args[0] === "fallback" ? readFallback(args) : readQuestion(args);
1481
+ if (!request) {
1482
+ io.output(
1483
+ JSON.stringify({
1484
+ status: "failed",
1485
+ ...args[0] === "fallback" ? { permitted: false } : {},
1486
+ failure: "Invalid graph command arguments.",
1487
+ recovery: QUERY_USAGE
1488
+ }) + "\n"
1489
+ );
1490
+ io.fail();
1491
+ return;
1492
+ }
1493
+ let answer;
1494
+ try {
1495
+ answer = "attemptId" in request ? await requestStoredGraphFallback(
1496
+ root,
1497
+ request.attemptId,
1498
+ request.question,
1499
+ request.reason,
1500
+ run
1501
+ ) : await queryPreparedGraph(root, request, run);
1502
+ } catch (cause) {
1503
+ const fallback = "attemptId" in request;
1504
+ answer = {
1505
+ status: "failed",
1506
+ permitted: false,
1507
+ failure: cause instanceof GraphLocalStateError ? cause.message : fallback ? "Search fallback failed." : "Graph operation failed.",
1508
+ recovery: cause instanceof GraphLocalStateError ? cause.recovery : fallback ? "Retain .qfai/tools/state and local analysis, repair project-local graph preparation, then retry the same fallback CLI command." : "Repair project-local graph preparation and retry the same CLI command."
1509
+ };
1510
+ }
1511
+ io.output(JSON.stringify(answer) + "\n");
1512
+ if (answer.status === "failed" || answer.status === "not-ready") io.fail();
1513
+ }
1514
+ function readFallback(args) {
1515
+ if (args.length !== 7) return void 0;
1516
+ const values = /* @__PURE__ */ new Map();
1517
+ for (let index = 1; index < args.length; index += 2) {
1518
+ const flag = args[index];
1519
+ const value = args[index + 1]?.trim();
1520
+ if (!flag || !["--attempt-id", "--question", "--reason"].includes(flag) || values.has(flag) || !value || value.startsWith("--"))
1521
+ return void 0;
1522
+ values.set(flag, value);
1523
+ }
1524
+ const attemptId = values.get("--attempt-id");
1525
+ const question = values.get("--question");
1526
+ const reason = values.get("--reason");
1527
+ if (!attemptId || !GRAPH_UUID.test(attemptId) || !graphStateText(question, 16384) || !graphStateText(reason, 2048))
1528
+ return void 0;
1529
+ return { attemptId, question, reason };
1530
+ }
1531
+ function readQuestion(args) {
1532
+ const domain = args[0];
1533
+ const question = args[1]?.trim();
1534
+ if (domain !== "code" && domain !== "documents" || args.length !== 2 || !question || question.length > 16384 || question.includes("\0") || question.startsWith("--"))
1535
+ return void 0;
1536
+ return { domain, question };
1537
+ }
1538
+ export {
1539
+ runInstalledGraphCli
1540
+ };
1541
+ //# sourceMappingURL=cli.mjs.map