@kaelio/ktx 0.13.1 → 0.15.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 (261) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +15 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
  86. package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
  87. package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
  88. package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
  89. package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
  90. package/dist/context/ingest/adapters/sigma/client.js +176 -0
  91. package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
  92. package/dist/context/ingest/adapters/sigma/detect.js +23 -0
  93. package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
  94. package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
  95. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
  96. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
  97. package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
  98. package/dist/context/ingest/adapters/sigma/project.js +186 -0
  99. package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
  100. package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
  101. package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
  102. package/dist/context/ingest/adapters/sigma/types.js +82 -0
  103. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  104. package/dist/context/ingest/artifact-gates.js +85 -18
  105. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  106. package/dist/context/ingest/final-gate-prune.js +229 -0
  107. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  108. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  109. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  110. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  111. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  112. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  113. package/dist/context/ingest/local-adapters.d.ts +2 -1
  114. package/dist/context/ingest/local-adapters.js +28 -0
  115. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  116. package/dist/context/ingest/local-ingest.js +1 -1
  117. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  118. package/dist/context/ingest/local-stage-ingest.js +3 -1
  119. package/dist/context/ingest/ports.d.ts +3 -0
  120. package/dist/context/ingest/report-snapshot.js +13 -3
  121. package/dist/context/ingest/reports.d.ts +3 -3
  122. package/dist/context/ingest/reports.js +3 -1
  123. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  124. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  125. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  126. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  127. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  128. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  129. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  130. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  131. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  132. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  133. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  134. package/dist/context/ingest/types.d.ts +3 -0
  135. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  136. package/dist/context/ingest/wiki-body-refs.js +37 -8
  137. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  138. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  139. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  140. package/dist/context/ingest/work-unit-cache.js +230 -0
  141. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  142. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  143. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  144. package/dist/context/llm/claude-code-runtime.js +38 -8
  145. package/dist/context/llm/codex-runtime.d.ts +4 -1
  146. package/dist/context/llm/codex-runtime.js +48 -35
  147. package/dist/context/llm/runtime-port.d.ts +26 -0
  148. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  149. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  150. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  151. package/dist/context/llm/subprocess-generate-object.js +122 -0
  152. package/dist/context/mcp/context-tools.d.ts +2 -0
  153. package/dist/context/mcp/context-tools.js +111 -17
  154. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  155. package/dist/context/mcp/local-project-ports.js +29 -0
  156. package/dist/context/mcp/logger.d.ts +24 -0
  157. package/dist/context/mcp/logger.js +49 -0
  158. package/dist/context/mcp/server.js +2 -0
  159. package/dist/context/mcp/types.d.ts +17 -0
  160. package/dist/context/memory/memory-agent.service.js +1 -1
  161. package/dist/context/project/config.d.ts +72 -0
  162. package/dist/context/project/config.js +5 -0
  163. package/dist/context/project/driver-schemas.d.ts +35 -0
  164. package/dist/context/project/driver-schemas.js +89 -1
  165. package/dist/context/project/project.js +1 -1
  166. package/dist/context/project/setup-config.js +1 -0
  167. package/dist/context/scan/description-generation.d.ts +4 -0
  168. package/dist/context/scan/description-generation.js +100 -13
  169. package/dist/context/scan/enabled-tables.d.ts +5 -0
  170. package/dist/context/scan/enabled-tables.js +13 -1
  171. package/dist/context/scan/enrichment-state.d.ts +50 -7
  172. package/dist/context/scan/enrichment-state.js +30 -13
  173. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  174. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  175. package/dist/context/scan/local-enrichment.d.ts +39 -4
  176. package/dist/context/scan/local-enrichment.js +345 -92
  177. package/dist/context/scan/local-scan.d.ts +4 -1
  178. package/dist/context/scan/local-scan.js +54 -13
  179. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  180. package/dist/context/scan/local-structural-artifacts.js +5 -0
  181. package/dist/context/scan/object-introspection.d.ts +21 -0
  182. package/dist/context/scan/object-introspection.js +35 -0
  183. package/dist/context/scan/relationship-benchmarks.js +5 -3
  184. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  185. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  186. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  187. package/dist/context/scan/relationship-detection-budget.js +53 -0
  188. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  189. package/dist/context/scan/relationship-diagnostics.js +2 -0
  190. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  191. package/dist/context/scan/relationship-discovery.js +27 -4
  192. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  193. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  194. package/dist/context/scan/relationship-profiling.js +53 -41
  195. package/dist/context/scan/relationship-validation.d.ts +6 -4
  196. package/dist/context/scan/relationship-validation.js +46 -32
  197. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  198. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  199. package/dist/context/scan/types.d.ts +5 -3
  200. package/dist/context/scan/types.js +2 -0
  201. package/dist/context/sl/local-query.js +5 -0
  202. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  203. package/dist/context/sl/semantic-layer.service.js +1 -1
  204. package/dist/context/sl/source-files.d.ts +5 -0
  205. package/dist/context/sl/source-files.js +17 -11
  206. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  207. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  208. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  209. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  210. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  211. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  212. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  213. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  214. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  215. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  216. package/dist/context/wiki/keys.js +1 -1
  217. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  218. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  219. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  220. package/dist/context/wiki/local-knowledge.js +50 -6
  221. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  222. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  223. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  224. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  225. package/dist/context/wiki/types.d.ts +6 -0
  226. package/dist/context-build-view.d.ts +3 -0
  227. package/dist/context-build-view.js +1 -0
  228. package/dist/knowledge.d.ts +2 -0
  229. package/dist/knowledge.js +12 -1
  230. package/dist/local-adapters.js +11 -0
  231. package/dist/mcp-http-server.js +15 -1
  232. package/dist/mcp-server-factory.d.ts +2 -0
  233. package/dist/mcp-server-factory.js +15 -1
  234. package/dist/mcp-stdio-server.js +10 -2
  235. package/dist/notion-page-picker.js +1 -1
  236. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  237. package/dist/public-ingest.d.ts +3 -1
  238. package/dist/public-ingest.js +4 -0
  239. package/dist/scan.d.ts +3 -1
  240. package/dist/scan.js +7 -0
  241. package/dist/setup-databases.d.ts +1 -1
  242. package/dist/setup-databases.js +43 -1
  243. package/dist/setup-sources.d.ts +6 -1
  244. package/dist/setup-sources.js +208 -48
  245. package/dist/setup.d.ts +3 -0
  246. package/dist/setup.js +6 -1
  247. package/dist/skills/analytics/SKILL.md +200 -2
  248. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  249. package/dist/skills/sigma_ingest/SKILL.md +189 -0
  250. package/dist/skills/wiki_capture/SKILL.md +24 -0
  251. package/dist/sql.js +4 -0
  252. package/dist/status-project.d.ts +4 -0
  253. package/dist/status-project.js +54 -2
  254. package/dist/telemetry/events.d.ts +2 -2
  255. package/dist/text-ingest.d.ts +4 -0
  256. package/dist/text-ingest.js +58 -29
  257. package/dist/verbatim-ingest.d.ts +46 -0
  258. package/dist/verbatim-ingest.js +193 -0
  259. package/package.json +5 -1
  260. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  261. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -1,6 +1,5 @@
1
1
  import type { GitService } from '../../../context/core/git.service.js';
2
2
  import type { RepairVerification } from '../constrained-repair.js';
3
- import type { FinalGateRepairResult } from '../final-gate-repair.js';
4
3
  import type { IngestTraceWriter } from '../ingest-trace.js';
5
4
  import type { TextualConflictResolutionResult } from './textual-conflict-resolver.js';
6
5
  export type PatchIntegrationResult = {
@@ -8,19 +7,16 @@ export type PatchIntegrationResult = {
8
7
  commitSha: string;
9
8
  touchedPaths: string[];
10
9
  textualResolution?: TextualConflictResolutionResult;
11
- gateRepair?: FinalGateRepairResult;
12
10
  } | {
13
11
  status: 'textual_conflict';
14
12
  reason: string;
15
13
  touchedPaths: string[];
16
14
  textualResolution?: TextualConflictResolutionResult;
17
- gateRepair?: FinalGateRepairResult;
18
15
  } | {
19
16
  status: 'semantic_conflict';
20
17
  reason: string;
21
18
  touchedPaths: string[];
22
19
  textualResolution?: TextualConflictResolutionResult;
23
- gateRepair?: FinalGateRepairResult;
24
20
  };
25
21
  export interface IntegrateWorkUnitPatchInput {
26
22
  unitKey: string;
@@ -41,12 +37,5 @@ export interface IntegrateWorkUnitPatchInput {
41
37
  reason: string;
42
38
  verify(changedPaths: string[]): Promise<RepairVerification>;
43
39
  }): Promise<TextualConflictResolutionResult>;
44
- repairGateFailure?(input: {
45
- unitKey: string;
46
- patchPath: string;
47
- touchedPaths: string[];
48
- reason: string;
49
- verify(changedPaths: string[]): Promise<RepairVerification>;
50
- }): Promise<FinalGateRepairResult>;
51
40
  }
52
41
  export declare function integrateWorkUnitPatch(input: IntegrateWorkUnitPatchInput): Promise<PatchIntegrationResult>;
@@ -153,50 +153,6 @@ export async function integrateWorkUnitPatch(input) {
153
153
  touchedPaths,
154
154
  reason,
155
155
  });
156
- if (input.repairGateFailure) {
157
- const gateRepair = await input.repairGateFailure({
158
- unitKey: input.unitKey,
159
- patchPath: input.patchPath,
160
- touchedPaths,
161
- reason,
162
- verify: verifyAppliedTree,
163
- });
164
- if (gateRepair.status === 'failed') {
165
- if (preApplyHead) {
166
- await input.integrationGit.resetHardTo(preApplyHead);
167
- }
168
- return {
169
- status: 'semantic_conflict',
170
- reason: gateRepair.reason,
171
- touchedPaths,
172
- gateRepair,
173
- };
174
- }
175
- const commit = await input.integrationGit.commitFiles(gateRepair.changedPaths, `ingest: repair WorkUnit ${input.unitKey} gates`, input.author.name, input.author.email);
176
- if (!commit.created) {
177
- if (preApplyHead) {
178
- await input.integrationGit.resetHardTo(preApplyHead);
179
- }
180
- return {
181
- status: 'semantic_conflict',
182
- reason: 'gate repair produced no committable changes',
183
- touchedPaths: gateRepair.changedPaths,
184
- gateRepair,
185
- };
186
- }
187
- await input.trace.event('debug', 'integration', 'patch_accepted_after_gate_repair', {
188
- unitKey: input.unitKey,
189
- commitSha: commit.commitHash,
190
- touchedPaths: gateRepair.changedPaths,
191
- attempts: gateRepair.attempts,
192
- });
193
- return {
194
- status: 'accepted',
195
- commitSha: commit.commitHash,
196
- touchedPaths: gateRepair.changedPaths,
197
- gateRepair,
198
- };
199
- }
200
156
  if (preApplyHead) {
201
157
  await input.integrationGit.resetHardTo(preApplyHead);
202
158
  }
@@ -13,4 +13,5 @@ export interface RunIsolatedWorkUnitInput {
13
13
  run(child: IngestSessionWorktree): Promise<WorkUnitOutcome>;
14
14
  afterSuccess?(child: IngestSessionWorktree): Promise<void>;
15
15
  }
16
+ export declare function workUnitPatchFileName(unitIndex: number, unitKey: string): string;
16
17
  export declare function runIsolatedWorkUnit(input: RunIsolatedWorkUnitInput): Promise<WorkUnitOutcome>;
@@ -1,7 +1,8 @@
1
1
  import { mkdir, readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { captureIngestWorkUnitCachedArtifactFiles } from '../work-unit-cache.js';
3
4
  import { parsePatchTouchedPaths } from './git-patch.js';
4
- function patchFileName(unitIndex, unitKey) {
5
+ export function workUnitPatchFileName(unitIndex, unitKey) {
5
6
  const safeKey = unitKey.replace(/[^a-zA-Z0-9_.-]+/g, '-');
6
7
  return `${String(unitIndex).padStart(4, '0')}-${safeKey}.patch`;
7
8
  }
@@ -49,21 +50,29 @@ export async function runIsolatedWorkUnit(input) {
49
50
  }
50
51
  await input.afterSuccess?.(child);
51
52
  await mkdir(input.patchDir, { recursive: true });
52
- const patchPath = join(input.patchDir, patchFileName(input.unitIndex, input.workUnit.unitKey));
53
+ const patchPath = join(input.patchDir, workUnitPatchFileName(input.unitIndex, input.workUnit.unitKey));
53
54
  await child.git.writeBinaryNoRenamePatch(input.ingestionBaseSha, 'HEAD', patchPath);
54
55
  const patch = await readFile(patchPath, 'utf-8');
55
56
  const touched = parsePatchTouchedPaths(patch);
57
+ const patchTouchedPaths = touched.map((entry) => entry.path);
58
+ const artifactFiles = await captureIngestWorkUnitCachedArtifactFiles({
59
+ git: child.git,
60
+ workdir: child.workdir,
61
+ baseSha: input.ingestionBaseSha,
62
+ patchTouchedPaths,
63
+ });
56
64
  cleanupOutcome = 'success';
57
65
  await input.trace.event('debug', 'work_unit', 'work_unit_patch_collected', {
58
66
  unitKey: input.workUnit.unitKey,
59
67
  patchPath,
60
- touchedPaths: touched.map((entry) => entry.path),
68
+ touchedPaths: patchTouchedPaths,
61
69
  patchBytes: Buffer.byteLength(patch),
62
70
  });
63
71
  return {
64
72
  ...outcome,
65
73
  patchPath,
66
- patchTouchedPaths: touched.map((entry) => entry.path),
74
+ patchTouchedPaths,
75
+ artifactFiles,
67
76
  childWorktreePath: child.workdir,
68
77
  };
69
78
  }
@@ -7,6 +7,7 @@ import { type LookerMappingClient, type LookerTableIdentifierParser } from './ad
7
7
  import type { LookerRuntimeClient } from './adapters/looker/fetch.js';
8
8
  import type { MetabaseClientLogger } from './adapters/metabase/client.js';
9
9
  import type { MetabaseFetchLogger } from './adapters/metabase/fetch.js';
10
+ import type { SigmaFetchLogger } from './adapters/sigma/fetch.js';
10
11
  import type { NotionFetchLogger } from './adapters/notion/fetch.js';
11
12
  import type { SourceAdapter } from './types.js';
12
13
  export interface DefaultLocalIngestAdaptersOptions {
@@ -29,7 +30,7 @@ export interface DefaultLocalIngestAdaptersOptions {
29
30
  };
30
31
  logger?: LocalIngestOperationalLogger;
31
32
  }
32
- type LocalIngestOperationalLogger = MetabaseClientLogger & MetabaseFetchLogger & LookerClientLogger & NotionFetchLogger;
33
+ type LocalIngestOperationalLogger = MetabaseClientLogger & MetabaseFetchLogger & LookerClientLogger & NotionFetchLogger & SigmaFetchLogger;
33
34
  export declare function createDefaultLocalIngestAdapters(project: KtxLocalProject, options?: DefaultLocalIngestAdaptersOptions): SourceAdapter[];
34
35
  export declare function localPullConfigForAdapter(project: KtxLocalProject, adapter: SourceAdapter, connectionId: string, options?: DefaultLocalIngestAdaptersOptions): Promise<unknown>;
35
36
  export {};
@@ -1,10 +1,12 @@
1
1
  import { join } from 'node:path';
2
+ import { gdriveConnectionToPullConfig, parseGdriveConnectionConfig } from '../../context/connections/gdrive-config.js';
2
3
  import { localConnectionToWarehouseDescriptor } from '../../context/connections/local-warehouse-descriptor.js';
3
4
  import { notionConnectionToPullConfig, parseNotionConnectionConfig } from '../../context/connections/notion-config.js';
4
5
  import { resolveKtxConfigReference } from '../core/config-reference.js';
5
6
  import { ktxLocalStateDbPath } from '../../context/project/local-state-db.js';
6
7
  import { DbtSourceAdapter } from './adapters/dbt/dbt.adapter.js';
7
8
  import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js';
9
+ import { GdriveSourceAdapter } from './adapters/gdrive/gdrive.adapter.js';
8
10
  import { HistoricSqlSourceAdapter } from './adapters/historic-sql/historic-sql.adapter.js';
9
11
  import { PostgresPgssReader } from './adapters/historic-sql/postgres-pgss-reader.js';
10
12
  import { resolveQueryHistoryScopeFloor } from './adapters/historic-sql/scope-floor.js';
@@ -20,6 +22,7 @@ import { buildLookerPullConfigFromInputs, } from './adapters/looker/mapping.js';
20
22
  import { LookmlSourceAdapter } from './adapters/lookml/lookml.adapter.js';
21
23
  import { pullConfigFromIntegrationConfig } from './adapters/lookml/pull-config.js';
22
24
  import { createLocalMetabaseSourceAdapter } from './adapters/metabase/local-metabase.adapter.js';
25
+ import { createLocalSigmaSourceAdapter } from './adapters/sigma/local-sigma.adapter.js';
23
26
  import { MetricflowSourceAdapter } from './adapters/metricflow/metricflow.adapter.js';
24
27
  import { pullConfigFromMetricflowIntegration } from './adapters/metricflow/pull-config.js';
25
28
  import { LocalNotionRuntimeStore } from './adapters/notion/local-state-store.js';
@@ -49,6 +52,10 @@ export function createDefaultLocalIngestAdapters(project, options = {}) {
49
52
  createLocalMetabaseSourceAdapter(project, {
50
53
  ...(options.logger ? { logger: options.logger } : {}),
51
54
  }),
55
+ createLocalSigmaSourceAdapter(project, {
56
+ ...(options.logger ? { logger: options.logger } : {}),
57
+ }),
58
+ new GdriveSourceAdapter(),
52
59
  new LookerSourceAdapter({
53
60
  clientFactory: {
54
61
  async createClient(config, ctx) {
@@ -180,6 +187,24 @@ export async function localPullConfigForAdapter(project, adapter, connectionId,
180
187
  if (adapter.source === 'metabase') {
181
188
  throw new Error('Metabase scheduled pulls fan out by mapping. Call runLocalMetabaseIngest() or use `ktx ingest <metabase-source-id>` from the CLI.');
182
189
  }
190
+ if (adapter.source === 'sigma') {
191
+ const sigmaConn = project.config.connections[connectionId];
192
+ const connectionMappings = sigmaConn && 'connectionMappings' in sigmaConn && sigmaConn.connectionMappings != null
193
+ ? sigmaConn.connectionMappings
194
+ : undefined;
195
+ const workbookFilter = sigmaConn && 'workbookFilter' in sigmaConn && sigmaConn.workbookFilter != null
196
+ ? sigmaConn.workbookFilter
197
+ : undefined;
198
+ const dataModelFilter = sigmaConn && 'dataModelFilter' in sigmaConn && sigmaConn.dataModelFilter != null
199
+ ? sigmaConn.dataModelFilter
200
+ : undefined;
201
+ return {
202
+ sigmaConnectionId: connectionId,
203
+ ...(connectionMappings ? { connectionMappings } : {}),
204
+ ...(workbookFilter ? { workbookFilter } : {}),
205
+ ...(dataModelFilter ? { dataModelFilter } : {}),
206
+ };
207
+ }
183
208
  const connection = project.config.connections[connectionId];
184
209
  if (adapter.source === HISTORIC_SQL_SOURCE_KEY) {
185
210
  if (options.historicSqlPullConfigOverride) {
@@ -239,6 +264,9 @@ export async function localPullConfigForAdapter(project, adapter, connectionId,
239
264
  lastSuccessfulCursor: await localNotionRuntimeStore(project).readCursor(connectionId),
240
265
  };
241
266
  }
267
+ if (adapter.source === 'gdrive') {
268
+ return await gdriveConnectionToPullConfig(parseGdriveConnectionConfig(connection));
269
+ }
242
270
  if (adapter.source === 'metricflow') {
243
271
  const metricflow = connection.metricflow;
244
272
  const metricflowConfig = typeof metricflow === 'object' && metricflow !== null && !Array.isArray(metricflow)
@@ -10,6 +10,7 @@ import { createLocalKtxLlmRuntimeFromConfig } from '../../context/llm/local-conf
10
10
  import { KtxIngestEmbeddingPortAdapter } from '../../context/llm/embedding-port.js';
11
11
  import { createRateLimitGovernorConfig, RateLimitGovernor } from '../../context/llm/rate-limit-governor.js';
12
12
  import { RuntimeAgentRunner } from '../../context/llm/runtime-port.js';
13
+ import { getKtxCliPackageInfo } from '../../cli-runtime.js';
13
14
  import { ktxLocalStateDbPath } from '../../context/project/local-state-db.js';
14
15
  import { PromptService } from '../../context/prompts/prompt.service.js';
15
16
  import { SkillsRegistryService } from '../../context/skills/skills-registry.service.js';
@@ -40,6 +41,7 @@ import { WikiWriteTool } from '../../context/wiki/tools/wiki-write.tool.js';
40
41
  import { CandidateDedupService } from '../../context/ingest/context-candidates/candidate-dedup.service.js';
41
42
  import { ContextCandidateCarryforwardService } from '../../context/ingest/context-candidates/context-candidate-carryforward.service.js';
42
43
  import { CuratorPaginationService } from '../../context/ingest/context-candidates/curator-pagination.service.js';
44
+ import { SqliteContentResultCache } from '../cache/sqlite-content-result-cache.js';
43
45
  import { createEmitHistoricSqlEvidenceTool } from './adapters/historic-sql/evidence-tool.js';
44
46
  import { ContextEvidenceIndexService } from '../../context/ingest/context-evidence/context-evidence-index.service.js';
45
47
  import { SqliteContextEvidenceStore } from '../../context/ingest/context-evidence/sqlite-context-evidence-store.js';
@@ -378,11 +380,11 @@ function parseKnowledgeIndexPath(file) {
378
380
  const segments = file.split('/');
379
381
  if (segments.length === 2 && segments[0] === 'global') {
380
382
  const pageKey = segments[1].replace(/\.md$/, '');
381
- return /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(pageKey) ? { scope: 'GLOBAL', pageKey } : null;
383
+ return /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(pageKey) ? { scope: 'GLOBAL', pageKey } : null;
382
384
  }
383
385
  if (segments.length === 3 && segments[0] === 'user') {
384
386
  const pageKey = segments[2].replace(/\.md$/, '');
385
- return /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(pageKey) ? { scope: 'USER', pageKey } : null;
387
+ return /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(pageKey) ? { scope: 'USER', pageKey } : null;
386
388
  }
387
389
  return null;
388
390
  }
@@ -519,6 +521,7 @@ export function createLocalBundleIngestRuntime(options) {
519
521
  mkdirSync(join(options.project.projectDir, '.ktx/cache/local-ingest'), { recursive: true });
520
522
  const store = new SqliteBundleIngestStore({ dbPath });
521
523
  const contextStore = new SqliteContextEvidenceStore({ dbPath });
524
+ const contentCache = new SqliteContentResultCache({ dbPath });
522
525
  const embeddingProvider = options.embeddingProvider ?? null;
523
526
  if (!embeddingProvider && options.project.config.ingest.embeddings.backend !== 'none') {
524
527
  // Embedding-dependent stages (CandidateDedup clustering, ContextEvidenceIndex
@@ -563,6 +566,7 @@ export function createLocalBundleIngestRuntime(options) {
563
566
  provenance: store,
564
567
  reports: store,
565
568
  canonicalPins: store,
569
+ contentCache,
566
570
  registry,
567
571
  diffSetService: new DiffSetService(store),
568
572
  sessionWorktreeService: new SessionWorktreeService({
@@ -576,6 +580,7 @@ export function createLocalBundleIngestRuntime(options) {
576
580
  storage,
577
581
  settings: {
578
582
  memoryIngestionModel: options.project.config.llm.models.default ?? 'local-ingest-model',
583
+ cliVersion: getKtxCliPackageInfo().version,
579
584
  probeRowCount: 0,
580
585
  workUnitMaxConcurrency: options.project.config.ingest.workUnits.maxConcurrency,
581
586
  workUnitStepBudget: options.project.config.ingest.workUnits.stepBudget,
@@ -17,7 +17,7 @@ class LocalIngestPhase {
17
17
  }
18
18
  }
19
19
  function safeSegment(kind, value) {
20
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
20
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(value)) {
21
21
  throw new Error(`Unsafe ${kind}: ${value}`);
22
22
  }
23
23
  return value;
@@ -1,7 +1,7 @@
1
1
  import type { KtxLocalProject } from '../../context/project/project.js';
2
2
  import type { MemoryFlowEventSink } from './memory-flow/types.js';
3
3
  import type { KtxTableRefKey } from '../scan/table-ref.js';
4
- import type { IngestTrigger, SourceAdapter, WorkUnit } from './types.js';
4
+ import type { IngestTrigger, SourceAdapter, SourceFetchReport, WorkUnit } from './types.js';
5
5
  type LocalIngestStatus = 'running' | 'done' | 'error';
6
6
  interface LocalIngestDiffPaths {
7
7
  added: string[];
@@ -34,6 +34,8 @@ export interface LocalIngestRunRecord {
34
34
  workUnits: Array<Pick<WorkUnit, 'unitKey' | 'rawFiles' | 'peerFileIndex' | 'dependencyPaths'>>;
35
35
  evictionDeletedRawPaths: string[];
36
36
  errors: string[];
37
+ /** Fetch-phase outcome (e.g. objects skipped during introspection). */
38
+ fetch?: SourceFetchReport;
37
39
  }
38
40
  export type LocalIngestReport = LocalIngestRunRecord & {
39
41
  rawContentHashes: Record<string, string>;
@@ -10,7 +10,7 @@ import { SqliteLocalIngestStore } from './sqlite-local-ingest-store.js';
10
10
  const LOCAL_AUTHOR = 'ktx';
11
11
  const LOCAL_AUTHOR_EMAIL = 'ktx@example.com';
12
12
  function safeSegment(kind, value) {
13
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
13
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(value)) {
14
14
  throw new Error(`Unsafe ${kind}: ${value}`);
15
15
  }
16
16
  return value;
@@ -172,6 +172,7 @@ async function runLocalStageOnlyIngestInner(options) {
172
172
  if (!detected) {
173
173
  throw new Error(`Adapter "${adapter.source}" did not recognize ${sourceDir ?? 'fetched source output'}`);
174
174
  }
175
+ const fetchReport = adapter.readFetchReport ? await adapter.readFetchReport(stagedDir) : null;
175
176
  const relativeFiles = await walkFiles(stagedDir);
176
177
  options.memoryFlow?.update({ sourceDir });
177
178
  options.memoryFlow?.emit({
@@ -270,6 +271,7 @@ async function runLocalStageOnlyIngestInner(options) {
270
271
  })),
271
272
  evictionDeletedRawPaths: chunkResult.eviction?.deletedRawPaths ?? [],
272
273
  errors: [],
274
+ ...(fetchReport ? { fetch: fetchReport } : {}),
273
275
  };
274
276
  if (!options.dryRun) {
275
277
  store.saveCompletedRun({
@@ -18,6 +18,7 @@ import type { ToolContext } from '../../context/tools/base-tool.js';
18
18
  import type { ToolSession } from '../../context/tools/tool-session.js';
19
19
  import type { KnowledgeIndexPort } from '../../context/wiki/ports.js';
20
20
  import type { KnowledgeWikiService } from '../../context/wiki/knowledge-wiki.service.js';
21
+ import type { ContentResultCache } from '../cache/content-result-cache.js';
21
22
  import type { CanonicalPin } from './canonical-pins.js';
22
23
  import type { IngestTraceLevel } from './ingest-trace.js';
23
24
  import type { IngestReportSnapshot } from './reports.js';
@@ -108,6 +109,7 @@ export interface IngestSessionWorktreePort {
108
109
  }
109
110
  interface IngestSettingsPort {
110
111
  memoryIngestionModel: string;
112
+ cliVersion: string;
111
113
  probeRowCount: number;
112
114
  workUnitMaxConcurrency?: number;
113
115
  workUnitStepBudget?: number;
@@ -295,6 +297,7 @@ export interface IngestBundleRunnerDeps {
295
297
  provenance: IngestProvenancePort;
296
298
  reports: IngestReportsPort;
297
299
  canonicalPins: IngestCanonicalPinsPort;
300
+ contentCache: ContentResultCache;
298
301
  registry: SourceAdapterRegistryPort;
299
302
  diffSetService: DiffSetComputerPort;
300
303
  sessionWorktreeService: IngestSessionWorktreePort;
@@ -140,6 +140,17 @@ const finalizationOutcomeSchema = z.object({
140
140
  actions: z.array(ingestActionSchema).default([]),
141
141
  provenanceExclusions: z.array(finalizationProvenanceExclusionSchema).default([]),
142
142
  });
143
+ const finalGatePrunedReferenceSchema = z.object({
144
+ kind: z.enum(['join', 'wiki_ref', 'wiki_sl_ref', 'wiki_body_ref']),
145
+ artifact: z.string(),
146
+ removedRef: z.string(),
147
+ absentTarget: z.string(),
148
+ });
149
+ const finalGateDroppedSourceSchema = z.object({
150
+ connectionId: z.string(),
151
+ sourceName: z.string(),
152
+ reason: z.string(),
153
+ });
143
154
  const ingestReportSnapshotSchema = z
144
155
  .object({
145
156
  id: z.string().min(1),
@@ -169,9 +180,6 @@ const ingestReportSnapshotSchema = z
169
180
  resolverAttempts: z.number().int().min(0).default(0),
170
181
  resolverRepairs: z.number().int().min(0).default(0),
171
182
  resolverFailures: z.number().int().min(0).default(0),
172
- gateRepairAttempts: z.number().int().min(0).default(0),
173
- gateRepairs: z.number().int().min(0).default(0),
174
- gateRepairFailures: z.number().int().min(0).default(0),
175
183
  })
176
184
  .optional(),
177
185
  workUnits: z.array(z.object({
@@ -198,6 +206,8 @@ const ingestReportSnapshotSchema = z
198
206
  provenanceRows: z.array(provenanceDetailSchema).default([]),
199
207
  toolTranscripts: z.array(toolTranscriptSummarySchema).default([]),
200
208
  finalization: finalizationOutcomeSchema.optional(),
209
+ finalGatePrunedReferences: z.array(finalGatePrunedReferenceSchema).default([]),
210
+ finalGateDroppedSources: z.array(finalGateDroppedSourceSchema).default([]),
201
211
  memoryFlow: memoryFlowReplayInputSchema.optional(),
202
212
  })
203
213
  .passthrough(),
@@ -1,6 +1,7 @@
1
1
  import type { MemoryAction } from '../../context/memory/types.js';
2
2
  import type { TouchedSlSource } from '../../context/tools/touched-sl-sources.js';
3
3
  import type { MemoryFlowReplayInput } from './memory-flow/types.js';
4
+ import type { FinalGateDroppedSource, FinalGatePrunedReference } from './final-gate-prune.js';
4
5
  import type { IngestProvenanceInsert } from './ports.js';
5
6
  import type { ArtifactResolutionRecord, ConflictResolvedRecord, EvictionAppliedRecord, StageIndex, UnmappedFallbackRecord } from './stages/stage-index.types.js';
6
7
  import type { WikiSlRefRepair } from './wiki-sl-ref-repair.js';
@@ -79,9 +80,6 @@ export interface IngestReportBody {
79
80
  resolverAttempts?: number;
80
81
  resolverRepairs?: number;
81
82
  resolverFailures?: number;
82
- gateRepairAttempts?: number;
83
- gateRepairs?: number;
84
- gateRepairFailures?: number;
85
83
  };
86
84
  workUnits: IngestReportWorkUnit[];
87
85
  failedWorkUnits: string[];
@@ -98,6 +96,8 @@ export interface IngestReportBody {
98
96
  provenanceRows: IngestReportProvenanceDetail[];
99
97
  toolTranscripts: IngestReportToolTranscriptSummary[];
100
98
  finalization?: IngestReportFinalizationOutcome;
99
+ finalGatePrunedReferences?: FinalGatePrunedReference[];
100
+ finalGateDroppedSources?: FinalGateDroppedSource[];
101
101
  wikiSlRefRepairs?: WikiSlRefRepair[];
102
102
  wikiSlRefRepairWarnings?: string[];
103
103
  memoryFlow?: MemoryFlowReplayInput;
@@ -12,7 +12,9 @@ export function ingestReportOutcome(report) {
12
12
  if (report.body.status === 'failed') {
13
13
  return 'error';
14
14
  }
15
- if (report.body.failedWorkUnits.length === 0) {
15
+ const hasPruneOrDrop = (report.body.finalGatePrunedReferences?.length ?? 0) > 0 ||
16
+ (report.body.finalGateDroppedSources?.length ?? 0) > 0;
17
+ if (report.body.failedWorkUnits.length === 0 && !hasPruneOrDrop) {
16
18
  return 'done';
17
19
  }
18
20
  const { wikiCount, slCount } = savedMemoryCountsForReport(report);
@@ -13,6 +13,7 @@ export declare function buildWuSystemPrompt(params: {
13
13
  skillsPrompt: string;
14
14
  syncId: string;
15
15
  sourceKey: string;
16
+ connectionId?: string;
16
17
  canonicalPins?: CanonicalPin[];
17
18
  }): string;
18
19
  export declare function buildWuUserPrompt(input: BuildWuPromptInput): string;
@@ -6,12 +6,13 @@ import { createReadRawSpanTool } from '../tools/read-raw-span.tool.js';
6
6
  import { createVerificationLedgerState, VERIFICATION_LEDGER_PROMPT, withVerificationLedger, } from '../tools/verification-ledger.tool.js';
7
7
  const PEER_FILE_INDEX_PROMPT_LIMIT = 100;
8
8
  export function buildWuSystemPrompt(params) {
9
+ const connectionLine = params.connectionId ? `\nconnectionId: ${params.connectionId}` : '';
9
10
  const parts = [
10
11
  params.baseFraming.trimEnd(),
11
12
  VERIFICATION_LEDGER_PROMPT,
12
13
  params.skillsPrompt.trimEnd(),
13
14
  buildCanonicalPinsPromptBlock(params.canonicalPins ?? []),
14
- `\n<context>\nsyncId: ${params.syncId}\nsource: ${params.sourceKey}\n</context>`,
15
+ `\n<context>\nsyncId: ${params.syncId}\nsource: ${params.sourceKey}${connectionLine}\n</context>`,
15
16
  ];
16
17
  return parts.filter(Boolean).join('\n');
17
18
  }
@@ -3,13 +3,13 @@ import type { AgentRunnerPort, KtxRuntimeToolSet, RunLoopMetrics } from '../../.
3
3
  import type { CaptureSession, MemoryAction } from '../../../context/memory/types.js';
4
4
  import { type TouchedSlSource } from '../../../context/tools/touched-sl-sources.js';
5
5
  import { type WuValidationResult } from './validate-wu-sources.js';
6
+ import type { IngestWorkUnitCachedArtifactFile } from '../work-unit-cache.js';
6
7
  import type { WorkUnit } from '../types.js';
7
8
  export interface WorkUnitExecutionDeps {
8
9
  sessionWorktreeGit: {
9
10
  revParseHead(): Promise<string | null>;
10
11
  };
11
12
  agentRunner: AgentRunnerPort;
12
- validateWikiRefs?: (actions: MemoryAction[]) => Promise<string[]>;
13
13
  validateTouchedSources: (touched: TouchedSlSource[]) => Promise<WuValidationResult>;
14
14
  resetHardTo: (targetSha: string) => Promise<void>;
15
15
  buildSystemPrompt: (wu: WorkUnit) => string;
@@ -37,6 +37,7 @@ export interface WorkUnitOutcome {
37
37
  slDisallowedReason?: 'lookml_connection_mismatch';
38
38
  patchPath?: string;
39
39
  patchTouchedPaths?: string[];
40
+ artifactFiles?: IngestWorkUnitCachedArtifactFile[];
40
41
  childWorktreePath?: string;
41
42
  /** Timing and token metrics for the work-unit agent loop, used for ingest profiling. */
42
43
  metrics?: RunLoopMetrics;
@@ -1,6 +1,6 @@
1
1
  import { isAbortError } from '../../core/abort.js';
2
2
  import { listTouchedSlSources } from '../../../context/tools/touched-sl-sources.js';
3
- import { formatInvalidWuSources } from './validate-wu-sources.js';
3
+ import { formatInvalidWuSources, hasBlockingWuSourceIssue, } from './validate-wu-sources.js';
4
4
  const MAX_WORK_UNIT_PROMPT_CHARS = 240_000;
5
5
  export async function executeWorkUnit(deps, wu) {
6
6
  const preSha = (await deps.sessionWorktreeGit.revParseHead()) ?? '';
@@ -87,18 +87,12 @@ export async function executeWorkUnit(deps, wu) {
87
87
  if (toolFailureCount > 0) {
88
88
  return failWithReset(`${toolFailureCount} tool call(s) failed during WorkUnit ${wu.unitKey}`);
89
89
  }
90
- const danglingWikiRefs = (await deps.validateWikiRefs?.(deps.sessionActions)) ?? [];
91
- if (danglingWikiRefs.length > 0) {
92
- return failWithReset(`wiki references target missing page(s): ${danglingWikiRefs.join(', ')}`);
93
- }
94
90
  const touched = listTouchedSlSources(deps.captureSession.touchedSlSources);
95
91
  if (touched.length > 0) {
96
92
  const validation = await deps.validateTouchedSources(touched);
97
- if (validation.invalidSources.length > 0) {
98
- // Spec: invalid SL writes reset the session worktree to the WU's pre-state, WU is marked failed,
99
- // its files are absent from the Stage Index. Per-source surgical revert is the
100
- // memory-agent pattern — NOT the bundle-ingest pattern.
101
- return failWithReset(`sl_validate failed for: ${formatInvalidWuSources(validation.invalidSources)}`);
93
+ const blockingInvalidSources = validation.invalidSources.filter(hasBlockingWuSourceIssue);
94
+ if (blockingInvalidSources.length > 0) {
95
+ return failWithReset(`sl_validate failed for: ${formatInvalidWuSources(blockingInvalidSources)}`);
102
96
  }
103
97
  }
104
98
  return {
@@ -5,12 +5,24 @@ export interface InvalidWuSource {
5
5
  /** `${connectionId}:${sourceName}` */
6
6
  source: string;
7
7
  errors: string[];
8
+ issues?: WuValidationIssue[];
8
9
  }
9
10
  export interface WuValidationResult {
10
11
  validSources: string[];
11
12
  invalidSources: InvalidWuSource[];
12
13
  }
14
+ type WuValidationIssue = {
15
+ kind: 'source_validation';
16
+ message: string;
17
+ } | {
18
+ kind: 'missing_join_target';
19
+ targetSourceName: string;
20
+ caseMismatch: string | null;
21
+ message: string;
22
+ };
13
23
  export declare function formatInvalidWuSources(invalid: InvalidWuSource[]): string;
24
+ export declare function hasBlockingWuSourceIssue(source: InvalidWuSource): boolean;
14
25
  export declare function validateWuTouchedSources(deps: SlValidationDeps & {
15
26
  slValidator: SlValidatorPort<SlValidationDeps>;
16
27
  }, touched: TouchedSlSource[]): Promise<WuValidationResult>;
28
+ export {};
@@ -2,6 +2,14 @@ import { findMissingJoinTargets, formatMissingJoinTarget } from '../../../contex
2
2
  export function formatInvalidWuSources(invalid) {
3
3
  return invalid.map((entry) => `${entry.source} (${entry.errors.join('; ')})`).join(', ');
4
4
  }
5
+ export function hasBlockingWuSourceIssue(source) {
6
+ const issues = source.issues ??
7
+ source.errors.map((message) => ({
8
+ kind: 'source_validation',
9
+ message,
10
+ }));
11
+ return issues.some((issue) => issue.kind === 'source_validation');
12
+ }
5
13
  function uniqueTouchedSources(sources) {
6
14
  const seen = new Set();
7
15
  const unique = [];
@@ -61,8 +69,8 @@ function expandWithExistingJoinNeighbors(touched, sourcesByConnection) {
61
69
  * are out of scope — they must not block unrelated work. Resolution is the
62
70
  * Python engine's: exact source-name match within the connection.
63
71
  */
64
- function findJoinTargetErrors(touched, sourcesByConnection) {
65
- const errorsBySource = new Map();
72
+ function findJoinTargetIssues(touched, sourcesByConnection) {
73
+ const issuesBySource = new Map();
66
74
  const touchedByConnection = new Map();
67
75
  for (const source of touched) {
68
76
  const bucket = touchedByConnection.get(source.connectionId) ?? new Set();
@@ -85,11 +93,16 @@ function findJoinTargetErrors(touched, sourcesByConnection) {
85
93
  continue;
86
94
  }
87
95
  const key = `${connectionId}:${source.name}`;
88
- const messages = missing.map(formatMissingJoinTarget);
89
- errorsBySource.set(key, [...(errorsBySource.get(key) ?? []), ...messages]);
96
+ const issues = missing.map((entry) => ({
97
+ kind: 'missing_join_target',
98
+ targetSourceName: entry.to,
99
+ caseMismatch: entry.caseMismatch,
100
+ message: formatMissingJoinTarget(entry),
101
+ }));
102
+ issuesBySource.set(key, [...(issuesBySource.get(key) ?? []), ...issues]);
90
103
  }
91
104
  }
92
- return errorsBySource;
105
+ return issuesBySource;
93
106
  }
94
107
  export async function validateWuTouchedSources(deps, touched) {
95
108
  if (touched.length === 0) {
@@ -101,18 +114,20 @@ export async function validateWuTouchedSources(deps, touched) {
101
114
  sourcesByConnection.set(connectionId, sources);
102
115
  }
103
116
  const expanded = expandWithExistingJoinNeighbors(touched, sourcesByConnection);
104
- const joinTargetErrors = findJoinTargetErrors(touched, sourcesByConnection);
117
+ const joinTargetIssues = findJoinTargetIssues(touched, sourcesByConnection);
105
118
  const valid = [];
106
119
  const invalid = [];
107
120
  for (const source of expanded) {
108
121
  const key = `${source.connectionId}:${source.sourceName}`;
109
122
  const result = await deps.slValidator.validateSingleSource(deps, source.connectionId, source.sourceName);
110
- const errors = [...result.errors, ...(joinTargetErrors.get(key) ?? [])];
123
+ const sourceIssues = result.errors.map((message) => ({ kind: 'source_validation', message }));
124
+ const issues = [...sourceIssues, ...(joinTargetIssues.get(key) ?? [])];
125
+ const errors = issues.map((issue) => issue.message);
111
126
  if (errors.length === 0) {
112
127
  valid.push(key);
113
128
  }
114
129
  else {
115
- invalid.push({ source: key, errors });
130
+ invalid.push({ source: key, errors, issues });
116
131
  }
117
132
  }
118
133
  return { validSources: valid, invalidSources: invalid };
@@ -1,22 +1,27 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
- import { join, normalize, resolve } from 'node:path';
2
+ import { isAbsolute, join, normalize, relative, resolve } from 'node:path';
3
3
  import { tool } from 'ai';
4
4
  import { z } from 'zod';
5
5
  const MAX_READ_RAW_FILE_BYTES = 120_000;
6
+ function normalizeRawPath(path) {
7
+ return normalize(path).replace(/^[/\\]+/, '').replace(/\\/g, '/');
8
+ }
6
9
  export function createReadRawFileTool(deps) {
7
10
  const stagedRoot = resolve(deps.stagedDir);
11
+ const allowedPaths = new Set([...deps.allowedPaths].map(normalizeRawPath));
8
12
  return tool({
9
13
  description: "Read the full text content of a raw source file inside this WorkUnit. `path` must be relative to the staged bundle root (no leading slash, no `..`) and must appear in the WorkUnit's rawFiles or dependencyPaths list.",
10
14
  inputSchema: z.object({
11
15
  path: z.string().describe('Path relative to the staged bundle root. Example: "views/customers/customer.lkml".'),
12
16
  }),
13
17
  execute: async ({ path }) => {
14
- const normalized = normalize(path).replace(/^[/\\]+/, '');
15
- if (normalized.startsWith('..') || !deps.allowedPaths.has(normalized)) {
16
- return `Error: path "${path}" is not accessible from this WorkUnit. Allowed paths: ${[...deps.allowedPaths].sort().join(', ')}`;
18
+ const normalized = normalizeRawPath(path);
19
+ if (normalized.startsWith('..') || !allowedPaths.has(normalized)) {
20
+ return `Error: path "${path}" is not accessible from this WorkUnit. Allowed paths: ${[...allowedPaths].sort().join(', ')}`;
17
21
  }
18
22
  const absolute = resolve(join(stagedRoot, normalized));
19
- if (!absolute.startsWith(`${stagedRoot}/`) && absolute !== stagedRoot) {
23
+ const stagedRelative = relative(stagedRoot, absolute);
24
+ if (stagedRelative.startsWith('..') || isAbsolute(stagedRelative)) {
20
25
  return `Error: path "${path}" is not accessible from this WorkUnit.`;
21
26
  }
22
27
  try {