@phyzess/pappra 0.0.0-stage → 0.1.0-beta.2

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/integrations/skills/pappra/SKILL.md +68 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +30 -0
  6. package/assets/integrations/skills/pappra/references/lifecycle.md +37 -0
  7. package/assets/integrations/skills/pappra/references/operations.md +14 -0
  8. package/assets/manifest.json +4 -0
  9. package/dist/app/collaboration.d.ts +170 -0
  10. package/dist/app/collaboration.js +1650 -0
  11. package/dist/app/doctor.d.ts +9 -0
  12. package/dist/app/doctor.js +1030 -0
  13. package/dist/app/init.d.ts +143 -0
  14. package/dist/app/init.js +1111 -0
  15. package/dist/cli/commands/code.d.ts +16 -0
  16. package/dist/cli/commands/code.js +127 -0
  17. package/dist/cli/commands/collaboration.d.ts +13 -0
  18. package/dist/cli/commands/collaboration.js +186 -0
  19. package/dist/cli/commands/continuity.d.ts +29 -0
  20. package/dist/cli/commands/continuity.js +133 -0
  21. package/dist/cli/commands/docs.d.ts +11 -0
  22. package/dist/cli/commands/docs.js +133 -0
  23. package/dist/cli/commands/doctor.d.ts +14 -0
  24. package/dist/cli/commands/doctor.js +57 -0
  25. package/dist/cli/commands/host.d.ts +13 -0
  26. package/dist/cli/commands/host.js +125 -0
  27. package/dist/cli/commands/init.d.ts +51 -0
  28. package/dist/cli/commands/init.js +668 -0
  29. package/dist/cli/main.d.ts +2 -0
  30. package/dist/cli/main.js +10 -0
  31. package/dist/cli/output.d.ts +4 -0
  32. package/dist/cli/output.js +1 -0
  33. package/dist/cli/run.d.ts +2 -0
  34. package/dist/cli/run.js +597 -0
  35. package/dist/code/budget.d.ts +14 -0
  36. package/dist/code/budget.js +39 -0
  37. package/dist/code/exec.d.ts +23 -0
  38. package/dist/code/exec.js +133 -0
  39. package/dist/code/identity.d.ts +13 -0
  40. package/dist/code/identity.js +214 -0
  41. package/dist/code/index.d.ts +6 -0
  42. package/dist/code/index.js +3 -0
  43. package/dist/code/providers/codegraph.d.ts +59 -0
  44. package/dist/code/providers/codegraph.js +567 -0
  45. package/dist/code/registration.d.ts +9 -0
  46. package/dist/code/registration.js +293 -0
  47. package/dist/code/service.d.ts +53 -0
  48. package/dist/code/service.js +633 -0
  49. package/dist/code/text.d.ts +3 -0
  50. package/dist/code/text.js +202 -0
  51. package/dist/collab/admission.d.ts +450 -0
  52. package/dist/collab/admission.js +797 -0
  53. package/dist/collab/canonical.d.ts +26 -0
  54. package/dist/collab/canonical.js +136 -0
  55. package/dist/collab/contracts.d.ts +73 -0
  56. package/dist/collab/contracts.js +76 -0
  57. package/dist/collab/envelope.d.ts +52 -0
  58. package/dist/collab/envelope.js +241 -0
  59. package/dist/collab/fingerprint.d.ts +7 -0
  60. package/dist/collab/fingerprint.js +58 -0
  61. package/dist/collab/handoff.d.ts +350 -0
  62. package/dist/collab/handoff.js +513 -0
  63. package/dist/collab/index.d.ts +9 -0
  64. package/dist/collab/index.js +8 -0
  65. package/dist/collab/lifecycle.d.ts +449 -0
  66. package/dist/collab/lifecycle.js +534 -0
  67. package/dist/collab/messages.d.ts +148 -0
  68. package/dist/collab/messages.js +583 -0
  69. package/dist/collab/notify.d.ts +47 -0
  70. package/dist/collab/notify.js +140 -0
  71. package/dist/collab/preflight.d.ts +31 -0
  72. package/dist/collab/preflight.js +271 -0
  73. package/dist/collab/resources.d.ts +119 -0
  74. package/dist/collab/resources.js +451 -0
  75. package/dist/collab/store.d.ts +143 -0
  76. package/dist/collab/store.js +606 -0
  77. package/dist/collab/types.d.ts +354 -0
  78. package/dist/collab/types.js +1 -0
  79. package/dist/collab/validation.d.ts +10 -0
  80. package/dist/collab/validation.js +303 -0
  81. package/dist/continuity/context.d.ts +24 -0
  82. package/dist/continuity/context.js +179 -0
  83. package/dist/continuity/index.d.ts +3 -0
  84. package/dist/continuity/index.js +1 -0
  85. package/dist/continuity/paths.d.ts +37 -0
  86. package/dist/continuity/paths.js +258 -0
  87. package/dist/continuity/service.d.ts +59 -0
  88. package/dist/continuity/service.js +1258 -0
  89. package/dist/hosts/claude/native.d.ts +79 -0
  90. package/dist/hosts/claude/native.js +853 -0
  91. package/dist/hosts/codex/native.d.ts +345 -0
  92. package/dist/hosts/codex/native.js +1528 -0
  93. package/dist/hosts/codex/protocol.d.ts +51 -0
  94. package/dist/hosts/codex/protocol.js +42 -0
  95. package/dist/hosts/detect.d.ts +28 -0
  96. package/dist/hosts/detect.js +73 -0
  97. package/dist/hosts/index.d.ts +10 -0
  98. package/dist/hosts/index.js +10 -0
  99. package/dist/hosts/integration/bundle.d.ts +16 -0
  100. package/dist/hosts/integration/bundle.js +81 -0
  101. package/dist/hosts/integration/hook-entry.d.ts +1 -0
  102. package/dist/hosts/integration/hook-entry.js +68 -0
  103. package/dist/hosts/integration/hook.d.ts +32 -0
  104. package/dist/hosts/integration/hook.js +781 -0
  105. package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
  106. package/dist/hosts/integration/pi-extension-runtime.js +89 -0
  107. package/dist/hosts/integration/pi-template.d.ts +3 -0
  108. package/dist/hosts/integration/pi-template.js +5 -0
  109. package/dist/hosts/integration/service.d.ts +41 -0
  110. package/dist/hosts/integration/service.js +1381 -0
  111. package/dist/hosts/integration/support.d.ts +36 -0
  112. package/dist/hosts/integration/support.js +41 -0
  113. package/dist/hosts/integration/templates.d.ts +22 -0
  114. package/dist/hosts/integration/templates.js +70 -0
  115. package/dist/hosts/pi/protocol.d.ts +22 -0
  116. package/dist/hosts/pi/protocol.js +28 -0
  117. package/dist/hosts/pi/query-result.d.ts +22 -0
  118. package/dist/hosts/pi/query-result.js +710 -0
  119. package/dist/hosts/pi/rpc.d.ts +937 -0
  120. package/dist/hosts/pi/rpc.js +1621 -0
  121. package/dist/hosts/pilot.d.ts +485 -0
  122. package/dist/hosts/pilot.js +593 -0
  123. package/dist/index.d.ts +5 -0
  124. package/dist/index.js +4 -0
  125. package/dist/infra/fs.d.ts +27 -0
  126. package/dist/infra/fs.js +130 -0
  127. package/dist/infra/git.d.ts +60 -0
  128. package/dist/infra/git.js +358 -0
  129. package/dist/infra/owned-process-group.d.ts +173 -0
  130. package/dist/infra/owned-process-group.js +571 -0
  131. package/dist/install/apply.d.ts +65 -0
  132. package/dist/install/apply.js +663 -0
  133. package/dist/install/index.d.ts +5 -0
  134. package/dist/install/index.js +5 -0
  135. package/dist/install/ownership.d.ts +85 -0
  136. package/dist/install/ownership.js +430 -0
  137. package/dist/install/plan.d.ts +39 -0
  138. package/dist/install/plan.js +97 -0
  139. package/dist/install/rollback.d.ts +18 -0
  140. package/dist/install/rollback.js +544 -0
  141. package/dist/install/runtime.d.ts +76 -0
  142. package/dist/install/runtime.js +1145 -0
  143. package/dist/knowledge/catalog.d.ts +47 -0
  144. package/dist/knowledge/catalog.js +308 -0
  145. package/dist/knowledge/context.d.ts +28 -0
  146. package/dist/knowledge/context.js +61 -0
  147. package/dist/knowledge/index.d.ts +3 -0
  148. package/dist/knowledge/index.js +1 -0
  149. package/dist/knowledge/journal.d.ts +16 -0
  150. package/dist/knowledge/journal.js +109 -0
  151. package/dist/knowledge/lifecycle.d.ts +8 -0
  152. package/dist/knowledge/lifecycle.js +1001 -0
  153. package/dist/knowledge/paths.d.ts +37 -0
  154. package/dist/knowledge/paths.js +236 -0
  155. package/dist/knowledge/refs.d.ts +36 -0
  156. package/dist/knowledge/refs.js +267 -0
  157. package/dist/knowledge/service.d.ts +21 -0
  158. package/dist/knowledge/service.js +393 -0
  159. package/dist/knowledge/types.d.ts +171 -0
  160. package/dist/knowledge/types.js +1 -0
  161. package/dist/knowledge/write.d.ts +8 -0
  162. package/dist/knowledge/write.js +1223 -0
  163. package/dist/package-info.d.ts +6 -0
  164. package/dist/package-info.js +19 -0
  165. package/dist/schemas/code.d.ts +377 -0
  166. package/dist/schemas/code.js +145 -0
  167. package/dist/schemas/collaboration.d.ts +115 -0
  168. package/dist/schemas/collaboration.js +106 -0
  169. package/dist/schemas/continuity.d.ts +315 -0
  170. package/dist/schemas/continuity.js +199 -0
  171. package/dist/schemas/diagnostics.d.ts +28 -0
  172. package/dist/schemas/diagnostics.js +27 -0
  173. package/dist/schemas/doctor.d.ts +157 -0
  174. package/dist/schemas/doctor.js +75 -0
  175. package/dist/schemas/document.d.ts +644 -0
  176. package/dist/schemas/document.js +296 -0
  177. package/dist/schemas/errors.d.ts +177 -0
  178. package/dist/schemas/errors.js +176 -0
  179. package/dist/schemas/evidence.d.ts +24 -0
  180. package/dist/schemas/evidence.js +16 -0
  181. package/dist/schemas/host.d.ts +436 -0
  182. package/dist/schemas/host.js +201 -0
  183. package/dist/schemas/index.d.ts +15 -0
  184. package/dist/schemas/index.js +15 -0
  185. package/dist/schemas/init.d.ts +121 -0
  186. package/dist/schemas/init.js +94 -0
  187. package/dist/schemas/install.d.ts +213 -0
  188. package/dist/schemas/install.js +139 -0
  189. package/dist/schemas/machine.d.ts +43 -0
  190. package/dist/schemas/machine.js +96 -0
  191. package/dist/schemas/outcome.d.ts +23 -0
  192. package/dist/schemas/outcome.js +17 -0
  193. package/dist/schemas/paths.d.ts +19 -0
  194. package/dist/schemas/paths.js +63 -0
  195. package/dist/schemas/workspace.d.ts +120 -0
  196. package/dist/schemas/workspace.js +201 -0
  197. package/dist/workspace/config-file.d.ts +36 -0
  198. package/dist/workspace/config-file.js +237 -0
  199. package/dist/workspace/config.d.ts +28 -0
  200. package/dist/workspace/config.js +79 -0
  201. package/dist/workspace/discover.d.ts +34 -0
  202. package/dist/workspace/discover.js +195 -0
  203. package/dist/workspace/documents.d.ts +21 -0
  204. package/dist/workspace/documents.js +395 -0
  205. package/dist/workspace/ignore.d.ts +10 -0
  206. package/dist/workspace/ignore.js +48 -0
  207. package/dist/workspace/index.d.ts +7 -0
  208. package/dist/workspace/index.js +3 -0
  209. package/dist/workspace/repositories.d.ts +24 -0
  210. package/dist/workspace/repositories.js +411 -0
  211. package/dist/workspace/resolve.d.ts +24 -0
  212. package/dist/workspace/resolve.js +268 -0
  213. package/dist/workspace/types.d.ts +71 -0
  214. package/dist/workspace/types.js +1 -0
  215. package/dist/workspace/validation.d.ts +18 -0
  216. package/dist/workspace/validation.js +197 -0
  217. package/package.json +105 -4
  218. package/skills/pappra/SKILL.md +68 -0
  219. package/skills/pappra/references/hosts.md +30 -0
  220. package/skills/pappra/references/lifecycle.md +37 -0
  221. package/skills/pappra/references/operations.md +14 -0
@@ -0,0 +1,567 @@
1
+ import { join } from 'node:path';
2
+ import { z } from 'zod';
3
+ import { runCommand } from '../exec.js';
4
+ import { runTextQuery } from '../text.js';
5
+ import { observePath, readFileBounded, sha256Digest } from '../../infra/fs.js';
6
+ import { isPathInside, samePath } from '../../schemas/paths.js';
7
+ import { ensureCodeMutationBoundary } from '../registration.js';
8
+ import { boundCodeItems } from '../budget.js';
9
+ import { diagnosticCodes } from '../../schemas/errors.js';
10
+ import { failed, succeeded } from '../../schemas/outcome.js';
11
+ import { codeIndexHealthSchema, codeQueryResultSchema, } from '../../schemas/code.js';
12
+ const STATUS_MAX_BYTES = 1024 * 1024;
13
+ const PREPARE_MAX_BYTES = 4 * 1024 * 1024;
14
+ const engineStatusSchema = z.object({
15
+ initialized: z.boolean(),
16
+ version: z.string().min(1),
17
+ projectPath: z.string().optional(),
18
+ indexPath: z.string().optional(),
19
+ lastIndexed: z.string().nullable().optional(),
20
+ fileCount: z.number().int().nonnegative().optional(),
21
+ nodeCount: z.number().int().nonnegative().optional(),
22
+ edgeCount: z.number().int().nonnegative().optional(),
23
+ dbSizeBytes: z.number().int().nonnegative().optional(),
24
+ languages: z.array(z.string()).optional(),
25
+ pendingChanges: z
26
+ .object({
27
+ added: z.number().int().nonnegative(),
28
+ modified: z.number().int().nonnegative(),
29
+ removed: z.number().int().nonnegative(),
30
+ })
31
+ .optional(),
32
+ worktreeMismatch: z.unknown().nullable().optional(),
33
+ index: z
34
+ .object({
35
+ builtWithVersion: z.string().nullable().optional(),
36
+ builtWithExtractionVersion: z.number().nullable().optional(),
37
+ currentExtractionVersion: z.number().nullable().optional(),
38
+ reindexRecommended: z.boolean().optional(),
39
+ state: z.string().optional(),
40
+ pendingRefs: z.number().int().nonnegative().optional(),
41
+ })
42
+ .optional(),
43
+ });
44
+ function evidence(ref, stdout) {
45
+ return {
46
+ kind: 'runtime-probe',
47
+ ref,
48
+ digest: sha256Digest(stdout),
49
+ observedAt: new Date().toISOString(),
50
+ scope: 'live-owned',
51
+ };
52
+ }
53
+ const DEFAULT_CONFIG_DIGEST = sha256Digest('codegraph:default');
54
+ function indexCoverage(engine) {
55
+ const languages = engine?.languages ?? [];
56
+ return {
57
+ scope: 'selected-repository',
58
+ languages: [...new Set(languages)].sort().slice(0, 64),
59
+ semantic: engine?.initialized === true,
60
+ callGraph: engine?.initialized === true && (engine.edgeCount ?? 0) > 0,
61
+ note: 'CodeGraph reports its own index coverage; ready does not imply complete semantic or call-graph coverage for every language or dynamic call.',
62
+ };
63
+ }
64
+ function health(state, reasons, evidenceRefs, engine, engineVersion) {
65
+ return {
66
+ schema: 'pappra/code-index-health/1',
67
+ state,
68
+ reasons: [...new Set(reasons)].slice(0, 32),
69
+ evidence: [...evidenceRefs].slice(0, 32),
70
+ coverage: indexCoverage(engine),
71
+ engineVersion,
72
+ observedAt: new Date().toISOString(),
73
+ };
74
+ }
75
+ function parseEngineStatus(stdout) {
76
+ let value;
77
+ try {
78
+ value = JSON.parse(stdout);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ const parsed = engineStatusSchema.safeParse(value);
84
+ return parsed.success ? parsed.data : null;
85
+ }
86
+ export async function codeGraphVersion(signal) {
87
+ const result = await runCommand({
88
+ command: 'codegraph',
89
+ args: ['--version'],
90
+ cwd: process.cwd(),
91
+ timeoutMs: 5_000,
92
+ maxOutputBytes: 16 * 1024,
93
+ ...(signal === undefined ? {} : { signal }),
94
+ });
95
+ if (!result.ok)
96
+ return result;
97
+ const version = result.value.stdout.trim().split(/\s+/)[0] ?? '';
98
+ if (version.length === 0 || result.value.exitCode !== 0) {
99
+ return failed(diagnosticCodes.code_engine_unavailable, 'CodeGraph did not report a usable version.', { nextAction: 'Install or repair the selected CodeGraph CLI.' });
100
+ }
101
+ return succeeded(version);
102
+ }
103
+ const UNKNOWN_CONFIG_DIGEST = sha256Digest('codegraph:unknown');
104
+ export async function observeCodeGraphConfig(root) {
105
+ const configPath = join(root, '.codegraph', 'config.json');
106
+ const observed = await observePath(configPath);
107
+ if (observed.kind === 'missing') {
108
+ return succeeded({ digest: DEFAULT_CONFIG_DIGEST, status: 'default' });
109
+ }
110
+ if (observed.kind !== 'file') {
111
+ return succeeded({ digest: UNKNOWN_CONFIG_DIGEST, status: 'unknown' });
112
+ }
113
+ const boundary = await ensureCodeMutationBoundary(root, configPath);
114
+ if (!boundary.ok) {
115
+ return succeeded({ digest: UNKNOWN_CONFIG_DIGEST, status: 'unknown' });
116
+ }
117
+ const read = await readFileBounded(configPath, 64 * 1024);
118
+ if (!read.ok) {
119
+ return succeeded({ digest: UNKNOWN_CONFIG_DIGEST, status: 'unknown' });
120
+ }
121
+ return succeeded({
122
+ digest: sha256Digest(JSON.stringify({ id: 'codegraph', config: read.value.digest })),
123
+ status: 'observed',
124
+ });
125
+ }
126
+ export async function codeGraphConfigDigest(root) {
127
+ const observed = await observeCodeGraphConfig(root);
128
+ return observed.ok ? observed.value.digest : UNKNOWN_CONFIG_DIGEST;
129
+ }
130
+ export function codeGraphPlanCommand(command, action, root) {
131
+ if (action === 'none') {
132
+ return [command, 'status', '--json', root];
133
+ }
134
+ if (action === 'rebuild') {
135
+ return [command, 'index', '--force', root];
136
+ }
137
+ return [command, action, root];
138
+ }
139
+ export class CodeGraphProvider {
140
+ command;
141
+ id = 'codegraph';
142
+ constructor(command = 'codegraph') {
143
+ this.command = command;
144
+ }
145
+ async detect(root, signal) {
146
+ const result = await runCommand({
147
+ command: this.command,
148
+ args: ['--version'],
149
+ cwd: root,
150
+ timeoutMs: 5_000,
151
+ maxOutputBytes: 16 * 1024,
152
+ ...(signal === undefined ? {} : { signal }),
153
+ });
154
+ if (!result.ok)
155
+ return result;
156
+ const version = result.value.stdout.trim().split(/\s+/)[0] ?? '';
157
+ if (result.value.exitCode !== 0 || version.length === 0) {
158
+ return failed(diagnosticCodes.code_engine_unavailable, 'CodeGraph did not report a usable version.', { nextAction: 'Install or repair the selected CodeGraph CLI.' });
159
+ }
160
+ return succeeded([evidence('codegraph --version', version)]);
161
+ }
162
+ async status(identity, signal) {
163
+ const observed = await runCommand({
164
+ command: this.command,
165
+ args: ['status', '--json', identity.root],
166
+ cwd: identity.root,
167
+ timeoutMs: 10_000,
168
+ maxOutputBytes: STATUS_MAX_BYTES,
169
+ ...(signal === undefined ? {} : { signal }),
170
+ });
171
+ if (!observed.ok) {
172
+ return succeeded(health('unsupported', [observed.code, observed.message], [], null, identity.engine.version));
173
+ }
174
+ const evidenceRefs = [
175
+ evidence('codegraph status --json', observed.value.stdout),
176
+ ];
177
+ const engine = parseEngineStatus(observed.value.stdout);
178
+ if (observed.value.exitCode !== 0) {
179
+ return succeeded(health('failed', [`engine_status_exit_${observed.value.exitCode}`], evidenceRefs, null, identity.engine.version));
180
+ }
181
+ if (engine === null) {
182
+ return succeeded(health('partial', ['engine_status_unparseable'], evidenceRefs, null, identity.engine.version));
183
+ }
184
+ if (!engine.initialized) {
185
+ return succeeded(health('missing', ['engine_index_missing'], evidenceRefs, engine, identity.engine.version));
186
+ }
187
+ const reasons = [];
188
+ if (identity.engine.configStatus === 'unknown') {
189
+ reasons.push('engine_config_unknown');
190
+ }
191
+ if (engine.projectPath !== undefined &&
192
+ !samePath(engine.projectPath, identity.root)) {
193
+ reasons.push('engine_project_path_mismatch');
194
+ }
195
+ if (engine.version !== identity.engine.version) {
196
+ reasons.push(`engine_version_changed:${engine.version}`);
197
+ }
198
+ if (typeof engine.index?.builtWithVersion === 'string' &&
199
+ engine.index.builtWithVersion !== engine.version) {
200
+ reasons.push('index_engine_version_changed');
201
+ }
202
+ if (engine.worktreeMismatch !== null &&
203
+ engine.worktreeMismatch !== undefined) {
204
+ reasons.push('engine_worktree_mismatch');
205
+ }
206
+ const pending = engine.pendingChanges;
207
+ if (pending !== undefined &&
208
+ pending.added + pending.modified + pending.removed > 0) {
209
+ reasons.push('source_changes_pending');
210
+ }
211
+ if (engine.index?.reindexRecommended === true) {
212
+ reasons.push('engine_reindex_recommended');
213
+ }
214
+ if (identity.workingTree.completeness !== 'complete') {
215
+ reasons.push('working_tree_incomplete');
216
+ }
217
+ const indexState = engine.index?.state;
218
+ if (indexState === 'building') {
219
+ return succeeded(health('building', [...reasons, 'engine_building'], evidenceRefs, engine, identity.engine.version));
220
+ }
221
+ if (indexState === 'failed') {
222
+ return succeeded(health('failed', [...reasons, 'engine_failed'], evidenceRefs, engine, identity.engine.version));
223
+ }
224
+ if (indexState === 'partial' || (engine.index?.pendingRefs ?? 0) > 0) {
225
+ return succeeded(health('partial', [...reasons, 'engine_partial_coverage'], evidenceRefs, engine, identity.engine.version));
226
+ }
227
+ if (reasons.length > 0) {
228
+ return succeeded(health('stale', reasons, evidenceRefs, engine, identity.engine.version));
229
+ }
230
+ if (engine.lastIndexed === null || engine.lastIndexed === undefined) {
231
+ return succeeded(health('partial', ['engine_last_index_unknown'], evidenceRefs, engine, identity.engine.version));
232
+ }
233
+ return succeeded(health('ready', [], evidenceRefs, engine, identity.engine.version));
234
+ }
235
+ plan(identity, current, rebuild) {
236
+ const indexPath = join(identity.root, '.codegraph');
237
+ let action = 'none';
238
+ const reasons = [...current.reasons];
239
+ if (rebuild) {
240
+ action = 'rebuild';
241
+ }
242
+ else if (current.state === 'missing') {
243
+ action = 'init';
244
+ }
245
+ else if (current.state === 'ready') {
246
+ action = 'none';
247
+ }
248
+ else if (current.state === 'building' ||
249
+ current.state === 'failed' ||
250
+ current.reasons.some((reason) => reason === 'engine_worktree_mismatch' ||
251
+ reason.startsWith('engine_version_changed') ||
252
+ reason === 'engine_reindex_recommended')) {
253
+ action = 'none';
254
+ reasons.push('explicit_rebuild_required');
255
+ }
256
+ else if (current.state === 'stale' || current.state === 'partial') {
257
+ action = 'sync';
258
+ }
259
+ else {
260
+ action = 'none';
261
+ reasons.push('prepare_unsupported');
262
+ }
263
+ const command = codeGraphPlanCommand(this.command, action, identity.root);
264
+ return {
265
+ schema: 'pappra/code-prepare-plan/1',
266
+ repositoryId: identity.repositoryId,
267
+ identityDigest: sha256Digest(JSON.stringify(identity)),
268
+ action,
269
+ indexPath,
270
+ command,
271
+ mutates: action !== 'none',
272
+ reasons: [...new Set(reasons)].slice(0, 16),
273
+ };
274
+ }
275
+ async prepare(identity, plan, signal) {
276
+ if (plan.action === 'none') {
277
+ return this.status(identity, signal);
278
+ }
279
+ const [executable, ...prepareArgs] = plan.command;
280
+ const result = await runCommand({
281
+ command: executable ?? this.command,
282
+ args: prepareArgs,
283
+ cwd: identity.root,
284
+ timeoutMs: plan.action === 'sync' ? 60_000 : 180_000,
285
+ maxOutputBytes: PREPARE_MAX_BYTES,
286
+ ...(signal === undefined ? {} : { signal }),
287
+ });
288
+ if (!result.ok) {
289
+ return succeeded(health('failed', [result.code, result.message], [], null, identity.engine.version));
290
+ }
291
+ if (result.value.exitCode !== 0) {
292
+ return succeeded(health('failed', [`engine_prepare_exit_${result.value.exitCode}`], [evidence('codegraph prepare', result.value.stdout)], null, identity.engine.version));
293
+ }
294
+ return this.status(identity, signal);
295
+ }
296
+ async query(request, signal) {
297
+ if (request.kind === 'text') {
298
+ return runTextQuery(request, 'text', signal);
299
+ }
300
+ const current = await this.status(request.identity, signal);
301
+ if (!current.ok)
302
+ return current;
303
+ if (current.value.state !== 'ready') {
304
+ if (request.kind === 'symbol') {
305
+ return runTextQuery(request, 'structure', signal);
306
+ }
307
+ return succeeded({
308
+ schema: 'pappra/code-query-result/1',
309
+ provider: 'codegraph',
310
+ kind: request.kind,
311
+ query: request.query,
312
+ identityDigest: sha256Digest(JSON.stringify(request.identity)),
313
+ observedIdentity: request.identity,
314
+ coverage: 'unsupported',
315
+ items: [],
316
+ bytes: 0,
317
+ truncated: false,
318
+ reasons: ['index_not_ready'],
319
+ evidence: current.value.evidence,
320
+ unsupportedReason: 'The selected CodeGraph index is not ready; use text query or prepare explicitly.',
321
+ });
322
+ }
323
+ const limit = request.limits.maxResults;
324
+ const root = request.identity.root;
325
+ if (request.kind === 'explore') {
326
+ const result = await runCommand({
327
+ command: this.command,
328
+ args: [
329
+ 'explore',
330
+ '--path',
331
+ root,
332
+ '--max-files',
333
+ String(Math.min(request.limits.maxFiles, 20)),
334
+ request.query,
335
+ ],
336
+ cwd: root,
337
+ timeoutMs: 30_000,
338
+ maxOutputBytes: request.limits.maxBytes,
339
+ ...(signal === undefined ? {} : { signal }),
340
+ });
341
+ if (!result.ok)
342
+ return result;
343
+ if (result.value.exitCode !== 0) {
344
+ return failed(diagnosticCodes.code_engine_failed, `CodeGraph explore exited ${result.value.exitCode}: ${result.value.stderr.slice(0, 300)}`, { nextAction: 'Use text fallback or inspect the engine error.' });
345
+ }
346
+ const rawExplore = Buffer.from(result.value.stdout, 'utf8');
347
+ const text = rawExplore
348
+ .subarray(0, request.limits.maxBytes)
349
+ .toString('utf8');
350
+ const snippet = text.slice(0, 4096);
351
+ const clipped = rawExplore.length > request.limits.maxBytes ||
352
+ text.length > snippet.length;
353
+ const exploreItems = [
354
+ {
355
+ path: '.',
356
+ line: null,
357
+ column: null,
358
+ kind: 'explore',
359
+ name: null,
360
+ snippet,
361
+ source: 'codegraph',
362
+ },
363
+ ];
364
+ const bounded = boundCodeItems(exploreItems, request.limits.maxBytes, request.limits.maxResults, request.limits.maxFiles);
365
+ const output = {
366
+ schema: 'pappra/code-query-result/1',
367
+ provider: 'codegraph',
368
+ kind: request.kind,
369
+ query: request.query,
370
+ identityDigest: sha256Digest(JSON.stringify(request.identity)),
371
+ observedIdentity: request.identity,
372
+ coverage: 'partial',
373
+ items: bounded.items,
374
+ bytes: bounded.bytes,
375
+ truncated: clipped || bounded.truncated,
376
+ reasons: [
377
+ ...(clipped ? ['explore_output_clipped'] : []),
378
+ ...bounded.reasons,
379
+ ].slice(0, 16),
380
+ evidence: [evidence('codegraph explore', result.value.stdout)],
381
+ unsupportedReason: null,
382
+ };
383
+ const parsed = codeQueryResultSchema.safeParse(output);
384
+ if (!parsed.success) {
385
+ return failed(diagnosticCodes.code_engine_failed, `CodeGraph explore result violates the query contract: ${parsed.error.issues[0]?.message ?? 'unknown issue'}`, { nextAction: 'Use text fallback or inspect the engine output.' });
386
+ }
387
+ return succeeded(parsed.data);
388
+ }
389
+ const args = request.kind === 'symbol'
390
+ ? [
391
+ 'query',
392
+ '--json',
393
+ '--path',
394
+ root,
395
+ '--limit',
396
+ String(limit),
397
+ request.query,
398
+ ]
399
+ : request.kind === 'references'
400
+ ? [
401
+ 'callers',
402
+ '--json',
403
+ '--path',
404
+ root,
405
+ '--limit',
406
+ String(limit),
407
+ request.query,
408
+ ]
409
+ : ['impact', '--json', '--path', root, '--depth', '2', request.query];
410
+ const result = await runCommand({
411
+ command: this.command,
412
+ args,
413
+ cwd: root,
414
+ timeoutMs: 30_000,
415
+ maxOutputBytes: request.limits.maxBytes,
416
+ ...(signal === undefined ? {} : { signal }),
417
+ });
418
+ if (!result.ok) {
419
+ if (request.kind === 'symbol') {
420
+ return runTextQuery(request, 'structure', signal);
421
+ }
422
+ return result;
423
+ }
424
+ if (result.value.exitCode !== 0) {
425
+ if (request.kind === 'symbol') {
426
+ return runTextQuery(request, 'structure', signal);
427
+ }
428
+ return failed(diagnosticCodes.code_engine_failed, `CodeGraph query exited ${result.value.exitCode}: ${result.value.stderr.slice(0, 300)}`, { nextAction: 'Inspect the index or prepare it explicitly.' });
429
+ }
430
+ let parsed;
431
+ try {
432
+ parsed = JSON.parse(result.value.stdout);
433
+ }
434
+ catch {
435
+ if (request.kind === 'symbol') {
436
+ return runTextQuery(request, 'structure', signal);
437
+ }
438
+ return failed(diagnosticCodes.code_engine_failed, 'CodeGraph returned non-JSON query output.', { nextAction: 'Use text query or inspect the engine output.' });
439
+ }
440
+ let rows;
441
+ if (request.kind === 'symbol') {
442
+ if (!Array.isArray(parsed)) {
443
+ return failed(diagnosticCodes.code_engine_failed, 'CodeGraph symbol query result is not an array.', { nextAction: 'Use text query or inspect the engine output.' });
444
+ }
445
+ rows = parsed;
446
+ }
447
+ else {
448
+ const key = request.kind === 'references' ? 'callers' : 'affected';
449
+ const container = typeof parsed === 'object' && parsed !== null
450
+ ? parsed
451
+ : null;
452
+ const value = container?.[key];
453
+ if (!Array.isArray(value)) {
454
+ return failed(diagnosticCodes.code_engine_failed, `CodeGraph ${request.kind} result is missing the ${key} array.`, { nextAction: 'Use text query or inspect the engine output.' });
455
+ }
456
+ rows = value;
457
+ }
458
+ const items = [];
459
+ const protectedRoots = [
460
+ join(request.identity.root, '.pappra', 'local'),
461
+ join(request.identity.root, '.pappra', 'generated'),
462
+ ...(request.excludedRoots ?? []),
463
+ ];
464
+ let rowAdjusted = false;
465
+ for (const raw of rows.slice(0, limit)) {
466
+ if (typeof raw !== 'object' || raw === null) {
467
+ rowAdjusted = true;
468
+ continue;
469
+ }
470
+ const row = raw;
471
+ const node = typeof row.node === 'object' && row.node !== null
472
+ ? row.node
473
+ : row;
474
+ const path = typeof node.filePath === 'string' ? node.filePath : null;
475
+ if (path === null || path.length > 2048) {
476
+ rowAdjusted = true;
477
+ continue;
478
+ }
479
+ const absolute = join(request.identity.root, path);
480
+ if (!isPathInside(request.identity.root, absolute) ||
481
+ (request.excludedRoots ?? []).some((excluded) => isPathInside(excluded, absolute))) {
482
+ rowAdjusted = true;
483
+ continue;
484
+ }
485
+ const boundary = await ensureCodeMutationBoundary(request.identity.root, absolute, protectedRoots);
486
+ if (!boundary.ok) {
487
+ rowAdjusted = true;
488
+ continue;
489
+ }
490
+ const rawName = typeof node.name === 'string' ? node.name : null;
491
+ const name = rawName === null ? null : rawName.slice(0, 512);
492
+ if (rawName !== null && rawName.length > 512)
493
+ rowAdjusted = true;
494
+ const rawKind = typeof node.kind === 'string' ? node.kind : '';
495
+ const kind = rawKind.length > 0 ? rawKind.slice(0, 64) : 'unknown';
496
+ if (rawKind.length === 0 || rawKind.length > 64)
497
+ rowAdjusted = true;
498
+ const rawLine = node.startLine;
499
+ const line = typeof rawLine === 'number' && Number.isInteger(rawLine) && rawLine > 0
500
+ ? rawLine
501
+ : null;
502
+ if (rawLine !== null && line === null)
503
+ rowAdjusted = true;
504
+ const rawColumn = node.startColumn;
505
+ const column = typeof rawColumn === 'number' &&
506
+ Number.isInteger(rawColumn) &&
507
+ rawColumn >= 0
508
+ ? rawColumn
509
+ : null;
510
+ if (rawColumn !== null && column === null)
511
+ rowAdjusted = true;
512
+ const rawSnippet = typeof node.signature === 'string' ? node.signature : '';
513
+ const snippet = rawSnippet.slice(0, 4096);
514
+ if (rawSnippet.length > 4096)
515
+ rowAdjusted = true;
516
+ items.push({
517
+ path,
518
+ line,
519
+ column,
520
+ kind,
521
+ name,
522
+ snippet,
523
+ source: 'codegraph',
524
+ });
525
+ }
526
+ const bounded = boundCodeItems(items, request.limits.maxBytes, request.limits.maxResults, request.limits.maxFiles);
527
+ const clipped = rowAdjusted || rows.length > limit || bounded.truncated;
528
+ const output = {
529
+ schema: 'pappra/code-query-result/1',
530
+ provider: 'codegraph',
531
+ kind: request.kind,
532
+ query: request.query,
533
+ identityDigest: sha256Digest(JSON.stringify(request.identity)),
534
+ observedIdentity: request.identity,
535
+ coverage: clipped || request.kind === 'references'
536
+ ? 'partial'
537
+ : 'complete',
538
+ items: bounded.items,
539
+ bytes: bounded.bytes,
540
+ truncated: clipped,
541
+ reasons: [
542
+ ...(request.kind === 'references' ? ['callers_not_references'] : []),
543
+ ...(rowAdjusted ? ['native_rows_adjusted'] : []),
544
+ ...(rows.length > limit ? ['result_limit'] : []),
545
+ ...bounded.reasons,
546
+ ].slice(0, 16),
547
+ evidence: [evidence('codegraph query', result.value.stdout)],
548
+ unsupportedReason: request.kind === 'references'
549
+ ? 'CodeGraph callers are caller edges, not an exhaustive reference index.'
550
+ : null,
551
+ };
552
+ const parsedResult = codeQueryResultSchema.safeParse(output);
553
+ if (!parsedResult.success) {
554
+ return failed(diagnosticCodes.code_engine_failed, `CodeGraph query result violates the query contract: ${parsedResult.error.issues[0]?.message ?? 'unknown issue'}`, { nextAction: 'Use text fallback or inspect the engine output.' });
555
+ }
556
+ return succeeded(parsedResult.data);
557
+ }
558
+ }
559
+ export function codeGraphIndexPath(root) {
560
+ return join(root, '.codegraph');
561
+ }
562
+ export function codeGraphStatusSchemaForTests() {
563
+ return engineStatusSchema;
564
+ }
565
+ export function codeIndexHealthForTests(state, reasons) {
566
+ return codeIndexHealthSchema.parse(health(state, reasons, [], null, 'test'));
567
+ }
@@ -0,0 +1,9 @@
1
+ import { type Outcome } from '../schemas/outcome.js';
2
+ import { type CodeIdentity, type CodeIndexRegistration } from '../schemas/code.js';
3
+ export declare function codeRegistrationPath(root: string, repositoryId: string): string;
4
+ export declare function ensureCodeMutationBoundary(root: string, path: string, excludedRoots?: readonly string[]): Promise<Outcome<null>>;
5
+ export declare function acquireCodePrepareLock(root: string, excludedRoots?: readonly string[]): Promise<Outcome<string>>;
6
+ export declare function releaseCodePrepareLock(path: string): Promise<Outcome<null>>;
7
+ export declare function readCodeRegistration(root: string, repositoryId: string): Promise<Outcome<CodeIndexRegistration | null>>;
8
+ export declare function registrationMatches(registration: CodeIndexRegistration, identity: CodeIdentity): boolean;
9
+ export declare function writeCodeRegistration(root: string, registration: CodeIndexRegistration, excludedRoots?: readonly string[]): Promise<Outcome<string>>;