@duckcodeailabs/dql-agent 1.6.21 → 1.6.22

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 (160) hide show
  1. package/dist/agent-run-engine.d.ts +287 -0
  2. package/dist/agent-run-engine.d.ts.map +1 -0
  3. package/dist/agent-run-engine.js +916 -0
  4. package/dist/agent-run-engine.js.map +1 -0
  5. package/dist/agent-run-gates.d.ts +26 -0
  6. package/dist/agent-run-gates.d.ts.map +1 -0
  7. package/dist/agent-run-gates.js +170 -0
  8. package/dist/agent-run-gates.js.map +1 -0
  9. package/dist/agent-run-planner.d.ts +32 -0
  10. package/dist/agent-run-planner.d.ts.map +1 -0
  11. package/dist/agent-run-planner.js +280 -0
  12. package/dist/agent-run-planner.js.map +1 -0
  13. package/dist/answer-loop.d.ts +49 -0
  14. package/dist/answer-loop.d.ts.map +1 -1
  15. package/dist/answer-loop.js +292 -17
  16. package/dist/answer-loop.js.map +1 -1
  17. package/dist/app-builder.d.ts +6 -0
  18. package/dist/app-builder.d.ts.map +1 -1
  19. package/dist/app-builder.js +95 -2
  20. package/dist/app-builder.js.map +1 -1
  21. package/dist/app-planner.d.ts +78 -0
  22. package/dist/app-planner.d.ts.map +1 -0
  23. package/dist/app-planner.js +145 -0
  24. package/dist/app-planner.js.map +1 -0
  25. package/dist/embeddings/provider.d.ts +66 -0
  26. package/dist/embeddings/provider.d.ts.map +1 -0
  27. package/dist/embeddings/provider.js +105 -0
  28. package/dist/embeddings/provider.js.map +1 -0
  29. package/dist/hints/git-store.d.ts +69 -0
  30. package/dist/hints/git-store.d.ts.map +1 -0
  31. package/dist/hints/git-store.js +244 -0
  32. package/dist/hints/git-store.js.map +1 -0
  33. package/dist/hints/retrieval.d.ts +49 -0
  34. package/dist/hints/retrieval.d.ts.map +1 -0
  35. package/dist/hints/retrieval.js +60 -0
  36. package/dist/hints/retrieval.js.map +1 -0
  37. package/dist/hints/store.d.ts +39 -0
  38. package/dist/hints/store.d.ts.map +1 -0
  39. package/dist/hints/store.js +279 -0
  40. package/dist/hints/store.js.map +1 -0
  41. package/dist/hints/types.d.ts +147 -0
  42. package/dist/hints/types.d.ts.map +1 -0
  43. package/dist/hints/types.js +92 -0
  44. package/dist/hints/types.js.map +1 -0
  45. package/dist/index.d.ts +43 -6
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +35 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/intent-controller.d.ts +63 -0
  50. package/dist/intent-controller.d.ts.map +1 -0
  51. package/dist/intent-controller.js +150 -0
  52. package/dist/intent-controller.js.map +1 -0
  53. package/dist/kg/build.d.ts.map +1 -1
  54. package/dist/kg/build.js +2 -0
  55. package/dist/kg/build.js.map +1 -1
  56. package/dist/kg/sqlite-fts.js +4 -0
  57. package/dist/kg/sqlite-fts.js.map +1 -1
  58. package/dist/kg/types.d.ts +10 -0
  59. package/dist/kg/types.d.ts.map +1 -1
  60. package/dist/metadata/catalog.d.ts +81 -4
  61. package/dist/metadata/catalog.d.ts.map +1 -1
  62. package/dist/metadata/catalog.js +208 -1
  63. package/dist/metadata/catalog.js.map +1 -1
  64. package/dist/metadata/drafts.d.ts +22 -0
  65. package/dist/metadata/drafts.d.ts.map +1 -1
  66. package/dist/metadata/drafts.js +126 -2
  67. package/dist/metadata/drafts.js.map +1 -1
  68. package/dist/metadata/grain-gate.d.ts +61 -0
  69. package/dist/metadata/grain-gate.d.ts.map +1 -0
  70. package/dist/metadata/grain-gate.js +265 -0
  71. package/dist/metadata/grain-gate.js.map +1 -0
  72. package/dist/metadata/identity.d.ts +47 -0
  73. package/dist/metadata/identity.d.ts.map +1 -0
  74. package/dist/metadata/identity.js +147 -0
  75. package/dist/metadata/identity.js.map +1 -0
  76. package/dist/metadata/metric-match.d.ts +93 -0
  77. package/dist/metadata/metric-match.d.ts.map +1 -0
  78. package/dist/metadata/metric-match.js +233 -0
  79. package/dist/metadata/metric-match.js.map +1 -0
  80. package/dist/metadata/sql-grounding.d.ts +149 -0
  81. package/dist/metadata/sql-grounding.d.ts.map +1 -0
  82. package/dist/metadata/sql-grounding.js +397 -0
  83. package/dist/metadata/sql-grounding.js.map +1 -0
  84. package/dist/metadata/sql-retrieval.d.ts +29 -0
  85. package/dist/metadata/sql-retrieval.d.ts.map +1 -0
  86. package/dist/metadata/sql-retrieval.js +79 -0
  87. package/dist/metadata/sql-retrieval.js.map +1 -0
  88. package/dist/narrate.d.ts +52 -0
  89. package/dist/narrate.d.ts.map +1 -0
  90. package/dist/narrate.js +263 -0
  91. package/dist/narrate.js.map +1 -0
  92. package/dist/propose/build-from-prompt.d.ts +160 -0
  93. package/dist/propose/build-from-prompt.d.ts.map +1 -0
  94. package/dist/propose/build-from-prompt.js +815 -0
  95. package/dist/propose/build-from-prompt.js.map +1 -0
  96. package/dist/propose/classify.d.ts +55 -0
  97. package/dist/propose/classify.d.ts.map +1 -0
  98. package/dist/propose/classify.js +167 -0
  99. package/dist/propose/classify.js.map +1 -0
  100. package/dist/propose/config.d.ts +39 -0
  101. package/dist/propose/config.d.ts.map +1 -0
  102. package/dist/propose/config.js +52 -0
  103. package/dist/propose/config.js.map +1 -0
  104. package/dist/propose/dbt-artifacts.d.ts +134 -0
  105. package/dist/propose/dbt-artifacts.d.ts.map +1 -0
  106. package/dist/propose/dbt-artifacts.js +320 -0
  107. package/dist/propose/dbt-artifacts.js.map +1 -0
  108. package/dist/propose/edit-block.d.ts +74 -0
  109. package/dist/propose/edit-block.d.ts.map +1 -0
  110. package/dist/propose/edit-block.js +133 -0
  111. package/dist/propose/edit-block.js.map +1 -0
  112. package/dist/propose/enrich.d.ts +48 -0
  113. package/dist/propose/enrich.d.ts.map +1 -0
  114. package/dist/propose/enrich.js +125 -0
  115. package/dist/propose/enrich.js.map +1 -0
  116. package/dist/propose/generate-sql.d.ts +61 -0
  117. package/dist/propose/generate-sql.d.ts.map +1 -0
  118. package/dist/propose/generate-sql.js +180 -0
  119. package/dist/propose/generate-sql.js.map +1 -0
  120. package/dist/propose/index.d.ts +24 -0
  121. package/dist/propose/index.d.ts.map +1 -0
  122. package/dist/propose/index.js +15 -0
  123. package/dist/propose/index.js.map +1 -0
  124. package/dist/propose/propose.d.ts +226 -0
  125. package/dist/propose/propose.d.ts.map +1 -0
  126. package/dist/propose/propose.js +668 -0
  127. package/dist/propose/propose.js.map +1 -0
  128. package/dist/propose/reflect-block.d.ts +99 -0
  129. package/dist/propose/reflect-block.d.ts.map +1 -0
  130. package/dist/propose/reflect-block.js +220 -0
  131. package/dist/propose/reflect-block.js.map +1 -0
  132. package/dist/propose/write-draft.d.ts +90 -0
  133. package/dist/propose/write-draft.d.ts.map +1 -0
  134. package/dist/propose/write-draft.js +185 -0
  135. package/dist/propose/write-draft.js.map +1 -0
  136. package/dist/providers/claude.d.ts +13 -1
  137. package/dist/providers/claude.d.ts.map +1 -1
  138. package/dist/providers/claude.js +20 -2
  139. package/dist/providers/claude.js.map +1 -1
  140. package/dist/providers/gemini.d.ts +13 -1
  141. package/dist/providers/gemini.d.ts.map +1 -1
  142. package/dist/providers/gemini.js +24 -3
  143. package/dist/providers/gemini.js.map +1 -1
  144. package/dist/providers/index.d.ts +3 -3
  145. package/dist/providers/index.d.ts.map +1 -1
  146. package/dist/providers/index.js +3 -3
  147. package/dist/providers/index.js.map +1 -1
  148. package/dist/research-loop.d.ts +62 -0
  149. package/dist/research-loop.d.ts.map +1 -0
  150. package/dist/research-loop.js +210 -0
  151. package/dist/research-loop.js.map +1 -0
  152. package/dist/skills/defaults.d.ts +37 -0
  153. package/dist/skills/defaults.d.ts.map +1 -0
  154. package/dist/skills/defaults.js +152 -0
  155. package/dist/skills/defaults.js.map +1 -0
  156. package/dist/skills/loader.d.ts +60 -0
  157. package/dist/skills/loader.d.ts.map +1 -1
  158. package/dist/skills/loader.js +164 -2
  159. package/dist/skills/loader.js.map +1 -1
  160. package/package.json +7 -5
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Local OSS owner identity (spec 14, part C).
3
+ *
4
+ * A new DRAFT block must never be born with a "Missing owner" Certifier strike
5
+ * just because the OSS user never passed `--owner`. We resolve a sensible
6
+ * default owner ONCE and persist it, then stamp it across every draft-creating
7
+ * path (propose, generate, enrichment, /api/ai/build) when no explicit owner is
8
+ * supplied.
9
+ *
10
+ * Resolution order (first non-empty wins):
11
+ * 1. Persisted owner — `dql.config.json` `identity.owner`, else `.dql/local`.
12
+ * 2. git `user.email` (project-local, then global).
13
+ * 3. `$USER` / `$USERNAME` / OS user — `<user>@local`.
14
+ * 4. `guest@local` (guaranteed non-empty fallback).
15
+ *
16
+ * This module is deterministic and offline — it never calls a provider. The git
17
+ * lookup is best-effort and degrades silently when git is absent or the repo has
18
+ * no configured email.
19
+ */
20
+ import { execFileSync } from 'node:child_process';
21
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
22
+ import { userInfo } from 'node:os';
23
+ import { dirname, join } from 'node:path';
24
+ /** Where the resolved owner is cached when `dql.config.json` is unavailable. */
25
+ const LOCAL_OWNER_PATH = ['.dql', 'local', 'owner'];
26
+ function nonEmpty(value) {
27
+ if (typeof value !== 'string')
28
+ return undefined;
29
+ const trimmed = value.trim();
30
+ return trimmed.length > 0 ? trimmed : undefined;
31
+ }
32
+ /** Read a persisted owner from `dql.config.json` `identity.owner`, else `.dql/local/owner`. */
33
+ export function readPersistedOwner(projectRoot) {
34
+ const configPath = join(projectRoot, 'dql.config.json');
35
+ if (existsSync(configPath)) {
36
+ try {
37
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
38
+ const owner = nonEmpty(typeof raw.identity?.owner === 'string' ? raw.identity.owner : undefined);
39
+ if (owner)
40
+ return owner;
41
+ }
42
+ catch {
43
+ // Malformed config is not fatal — fall through to the local cache.
44
+ }
45
+ }
46
+ const localPath = join(projectRoot, ...LOCAL_OWNER_PATH);
47
+ if (existsSync(localPath)) {
48
+ try {
49
+ return nonEmpty(readFileSync(localPath, 'utf-8'));
50
+ }
51
+ catch {
52
+ // Unreadable cache — fall through.
53
+ }
54
+ }
55
+ return undefined;
56
+ }
57
+ /** Best-effort git `user.email` (project-local first, then global). Never throws. */
58
+ function gitUserEmail(projectRoot) {
59
+ const run = (args) => {
60
+ try {
61
+ const out = execFileSync('git', args, {
62
+ cwd: projectRoot,
63
+ encoding: 'utf-8',
64
+ timeout: 3000,
65
+ stdio: ['ignore', 'pipe', 'ignore'],
66
+ });
67
+ return nonEmpty(out);
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ };
73
+ return run(['config', 'user.email']) ?? run(['config', '--global', 'user.email']);
74
+ }
75
+ /** `$USER` / OS user → `<user>@local`, else undefined. */
76
+ function osUserOwner() {
77
+ const fromEnv = nonEmpty(process.env.USER) ?? nonEmpty(process.env.USERNAME);
78
+ if (fromEnv)
79
+ return `${fromEnv}@local`;
80
+ try {
81
+ const name = nonEmpty(userInfo().username);
82
+ if (name)
83
+ return `${name}@local`;
84
+ }
85
+ catch {
86
+ // userInfo can throw on exotic platforms — ignore.
87
+ }
88
+ return undefined;
89
+ }
90
+ /**
91
+ * Persist the owner into `dql.config.json` `identity.owner` when the config
92
+ * exists; otherwise cache it under `.dql/local/owner`. Best-effort — a failure to
93
+ * persist never blocks the caller (the owner is still returned).
94
+ */
95
+ export function persistOwner(projectRoot, owner) {
96
+ const value = nonEmpty(owner);
97
+ if (!value)
98
+ return;
99
+ const configPath = join(projectRoot, 'dql.config.json');
100
+ if (existsSync(configPath)) {
101
+ try {
102
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
103
+ const identity = (raw.identity && typeof raw.identity === 'object'
104
+ ? raw.identity
105
+ : {});
106
+ if (identity.owner === value)
107
+ return; // already current — no rewrite.
108
+ identity.owner = value;
109
+ raw.identity = identity;
110
+ writeFileSync(configPath, JSON.stringify(raw, null, 2) + '\n', 'utf-8');
111
+ return;
112
+ }
113
+ catch {
114
+ // Fall through to the local cache when the config cannot be rewritten.
115
+ }
116
+ }
117
+ try {
118
+ const localPath = join(projectRoot, ...LOCAL_OWNER_PATH);
119
+ mkdirSync(dirname(localPath), { recursive: true });
120
+ writeFileSync(localPath, value + '\n', 'utf-8');
121
+ }
122
+ catch {
123
+ // Caching is advisory; a read-only filesystem must not break resolution.
124
+ }
125
+ }
126
+ /**
127
+ * Resolve the default local OSS owner. Always returns a non-empty string. When
128
+ * an explicit owner is given it wins (and is persisted); otherwise the first
129
+ * persisted/derived value is used and persisted for reuse.
130
+ */
131
+ export function resolveLocalOwner(projectRoot, options = {}) {
132
+ const persist = options.persist !== false;
133
+ const explicit = nonEmpty(options.explicit);
134
+ if (explicit) {
135
+ if (persist)
136
+ persistOwner(projectRoot, explicit);
137
+ return explicit;
138
+ }
139
+ const persisted = readPersistedOwner(projectRoot);
140
+ if (persisted)
141
+ return persisted;
142
+ const resolved = gitUserEmail(projectRoot) ?? osUserOwner() ?? 'guest@local';
143
+ if (persist)
144
+ persistOwner(projectRoot, resolved);
145
+ return resolved;
146
+ }
147
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/metadata/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,gFAAgF;AAChF,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAgBpD,SAAS,QAAQ,CAAC,KAAgC;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAEvD,CAAC;YACF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,GAAG,CAAC,QAAQ,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACjG,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,gBAAgB,CAAC,CAAC;IACzD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,OAAO,QAAQ,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qFAAqF;AACrF,SAAS,YAAY,CAAC,WAAmB;IACvC,MAAM,GAAG,GAAG,CAAC,IAAc,EAAsB,EAAE;QACjD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;gBACpC,GAAG,EAAE,WAAW;gBAChB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;aACpC,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IACF,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,0DAA0D;AAC1D,SAAS,WAAW;IAClB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,OAAO;QAAE,OAAO,GAAG,OAAO,QAAQ,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,IAAI;YAAE,OAAO,GAAG,IAAI,QAAQ,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;IACrD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB,EAAE,KAAa;IAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA4B,CAAC;YACrF,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;gBAChE,CAAC,CAAC,GAAG,CAAC,QAAQ;gBACd,CAAC,CAAC,EAAE,CAA4B,CAAC;YACnC,IAAI,QAAQ,CAAC,KAAK,KAAK,KAAK;gBAAE,OAAO,CAAC,gCAAgC;YACtE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;YACvB,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACxB,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;YACxE,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;QACzE,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,gBAAgB,CAAC,CAAC;QACzD,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB,EAAE,UAA+B,EAAE;IACtF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC;IAE1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,OAAO;YAAE,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhC,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,IAAI,WAAW,EAAE,IAAI,aAAa,CAAC;IAC7E,IAAI,OAAO;QAAE,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Semantic-metric matching (spec 17, part C).
3
+ *
4
+ * The answer-loop already has a `semantic_layer` tier in its route order
5
+ * (certified → business_context → semantic_layer → dbt_manifest → generated →
6
+ * refuse). The miss it fixes here: matching a clear metric question to a governed
7
+ * metric was purely string-based (FTS token overlap), so "what is our total
8
+ * revenue" did not connect to a revenue-family metric like `cumulative_revenue`,
9
+ * `food_revenue`, or `lifetime_spend` — no metric is literally named "revenue" —
10
+ * and the loop refused instead of routing to the semantic tier.
11
+ *
12
+ * `matchSemanticMetric` ranks candidate metric KG nodes against the question
13
+ * using:
14
+ * - a synonym/family expansion of the question's measure terms (revenue ⇄
15
+ * spend/sales/income/arr; orders ⇄ purchases; ...), and of each metric's
16
+ * searchable text (name + synonyms + label + description + tags),
17
+ * - lexical token overlap blended through the spec-11 `hybridRank`
18
+ * (alpha defaults to 0 → pure lexical, offline-stable for tests),
19
+ * - a strong boost when a measure FAMILY (e.g. "revenue") is shared between
20
+ * the question and the metric, which is what makes "total revenue" reach the
21
+ * revenue family even though the exact word never appears in a metric name.
22
+ *
23
+ * It returns the single best metric only when the score clears a confidence
24
+ * threshold, so genuinely ad-hoc questions ("median order value by region")
25
+ * still fall through to generated SQL, and questions with no measure at all
26
+ * still refuse honestly.
27
+ */
28
+ import { type EmbeddingProvider } from '../embeddings/provider.js';
29
+ import type { KGNode } from '../kg/types.js';
30
+ /** The measure families a metric KG node resolves to (revenue, orders, ...). */
31
+ export declare function metricFamilies(metric: KGNode): Set<string>;
32
+ export interface MetricMatch {
33
+ metric: KGNode;
34
+ /** Blended relevance in [0, 1]. */
35
+ score: number;
36
+ /** The shared measure family that justified the match, if any. */
37
+ family?: string;
38
+ }
39
+ export interface MatchSemanticMetricOptions {
40
+ /**
41
+ * Confidence threshold. A match below this is treated as "no confident metric"
42
+ * so ad-hoc questions still fall through to generated SQL. Default 0.34.
43
+ */
44
+ threshold?: number;
45
+ /** Vector-similarity weight; 0 (default) = offline-stable pure lexical. */
46
+ alpha?: number;
47
+ provider?: EmbeddingProvider;
48
+ }
49
+ /**
50
+ * Pick the single best governed metric for a question, or `null` when no metric
51
+ * clears the confidence bar. Deterministic + offline by default (alpha=0).
52
+ *
53
+ * Scoring per candidate metric:
54
+ * base = lexical token overlap fraction (question content tokens present in
55
+ * the metric's searchable text), in [0, 1]
56
+ * family = +0.6 when the question and metric share a measure family
57
+ * name = +0.25 when a question content token appears in the metric NAME
58
+ * (so "food revenue" prefers `food_revenue` over `drink_revenue`)
59
+ * The blended FTS score is fed to `hybridRank` so spec-11 embeddings can refine
60
+ * it when a provider/alpha is supplied.
61
+ */
62
+ export declare function matchSemanticMetric(question: string, metrics: KGNode[], options?: MatchSemanticMetricOptions): Promise<MetricMatch | null>;
63
+ /**
64
+ * Parse a metric KG node's governed definition: the aggregate `sql:` expression
65
+ * and its `table:` relation, stored on `llmContext`. Returns undefined when the
66
+ * `sql:` is not an aggregate expression (a bare metric-name reference) or there is
67
+ * no table — those are not directly executable.
68
+ */
69
+ export declare function parseMetricDefinition(metric: KGNode): {
70
+ expr: string;
71
+ table: string;
72
+ } | undefined;
73
+ /**
74
+ * Deterministic, offline-safe governed SQL for a matched metric (spec 17, part C):
75
+ * a single read-only `SELECT <expr> AS <alias> FROM <table>` synthesised from the
76
+ * metric's governed definition. Returns undefined when the definition is too thin
77
+ * to execute, preserving honest refusal.
78
+ */
79
+ export declare function metricToGovernedSql(metric: KGNode): string | undefined;
80
+ /**
81
+ * Resolve a matched metric to executable governed SQL. dbt's semantic layer splits
82
+ * a metric from its measure: a derived MetricFlow metric (e.g. `revenue`, type=simple
83
+ * over measure `revenue`) carries no `table:`/`sql:` of its own — the aggregate lives
84
+ * on the backing measure node (`order_item.revenue` → `SUM(product_price)` over
85
+ * `order_items`). When the matched metric is itself synthesizable we use it directly;
86
+ * otherwise we fall back to a sibling whose LEAF name matches (`revenue` ⇒
87
+ * `<model>.revenue`). No fuzzy family guessing — precise resolution or nothing.
88
+ */
89
+ export declare function resolveGovernedMetricSql(metric: KGNode, pool: KGNode[]): {
90
+ sql: string;
91
+ metric: KGNode;
92
+ } | undefined;
93
+ //# sourceMappingURL=metric-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric-match.d.ts","sourceRoot":"","sources":["../../src/metadata/metric-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAwC,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACzG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AA0F7C,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAE1D;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAyD7B;AAOD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAMjG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,GACb;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAW7C"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Semantic-metric matching (spec 17, part C).
3
+ *
4
+ * The answer-loop already has a `semantic_layer` tier in its route order
5
+ * (certified → business_context → semantic_layer → dbt_manifest → generated →
6
+ * refuse). The miss it fixes here: matching a clear metric question to a governed
7
+ * metric was purely string-based (FTS token overlap), so "what is our total
8
+ * revenue" did not connect to a revenue-family metric like `cumulative_revenue`,
9
+ * `food_revenue`, or `lifetime_spend` — no metric is literally named "revenue" —
10
+ * and the loop refused instead of routing to the semantic tier.
11
+ *
12
+ * `matchSemanticMetric` ranks candidate metric KG nodes against the question
13
+ * using:
14
+ * - a synonym/family expansion of the question's measure terms (revenue ⇄
15
+ * spend/sales/income/arr; orders ⇄ purchases; ...), and of each metric's
16
+ * searchable text (name + synonyms + label + description + tags),
17
+ * - lexical token overlap blended through the spec-11 `hybridRank`
18
+ * (alpha defaults to 0 → pure lexical, offline-stable for tests),
19
+ * - a strong boost when a measure FAMILY (e.g. "revenue") is shared between
20
+ * the question and the metric, which is what makes "total revenue" reach the
21
+ * revenue family even though the exact word never appears in a metric name.
22
+ *
23
+ * It returns the single best metric only when the score clears a confidence
24
+ * threshold, so genuinely ad-hoc questions ("median order value by region")
25
+ * still fall through to generated SQL, and questions with no measure at all
26
+ * still refuse honestly.
27
+ */
28
+ import { defaultEmbeddingProvider, hybridRank } from '../embeddings/provider.js';
29
+ const TOKEN_RE = /[\p{L}\p{N}_]+/gu;
30
+ /** Stopwords that should never count toward a measure/metric match. */
31
+ const STOPWORDS = new Set([
32
+ 'what', 'whats', 'is', 'are', 'was', 'were', 'the', 'a', 'an', 'our', 'my', 'we',
33
+ 'of', 'for', 'in', 'on', 'to', 'by', 'and', 'or', 'show', 'me', 'give', 'tell',
34
+ 'how', 'much', 'many', 'this', 'that', 'these', 'those', 'do', 'does', 'did',
35
+ 'total', 'overall', 'current', 'all', 'per', 'each', 'value', 'amount', 'number',
36
+ 'count', 'sum', 'average', 'avg', 'get', 'find', 'whole', 'entire',
37
+ ]);
38
+ /**
39
+ * Measure synonym families. Each family maps a canonical measure to the set of
40
+ * words that signal it. A question and a metric "share a family" when any word
41
+ * from the question and any word from the metric resolve to the same family.
42
+ */
43
+ const MEASURE_FAMILIES = {
44
+ revenue: ['revenue', 'revenues', 'sales', 'income', 'turnover', 'earnings', 'gmv', 'arr', 'mrr', 'bookings', 'topline'],
45
+ spend: ['spend', 'spending', 'spent', 'cost', 'costs', 'expense', 'expenses', 'cogs'],
46
+ profit: ['profit', 'profits', 'margin', 'margins', 'gross', 'net', 'ebitda'],
47
+ orders: ['order', 'orders', 'purchase', 'purchases', 'transaction', 'transactions', 'checkout', 'checkouts'],
48
+ customers: ['customer', 'customers', 'user', 'users', 'account', 'accounts', 'buyer', 'buyers', 'shopper', 'shoppers'],
49
+ churn: ['churn', 'churned', 'attrition', 'cancel', 'cancellation', 'cancellations', 'logo'],
50
+ retention: ['retention', 'retained', 'renewal', 'renewals'],
51
+ quantity: ['quantity', 'units', 'volume', 'qty'],
52
+ aov: ['aov', 'basket'],
53
+ };
54
+ /**
55
+ * Row/record-listing intent. A question asking to ENUMERATE rows or identifiers
56
+ * ("list the 5 most recent orders with their ids") is not a metric question even
57
+ * though it names a measure entity ("orders") — it wants raw rows, so it should
58
+ * fall through to grounded generated SQL, not a governed aggregate. Conservative:
59
+ * only the clearest listing signals, so true aggregate questions ("how many
60
+ * orders", "total revenue", "average order value") are never suppressed.
61
+ */
62
+ const ROW_REQUEST_RE = /\b(list|listing|rows?|records?|individual|line[\s-]?items?|ids?)\b/i;
63
+ const RECENCY_RE = /\b(most\s+recent|latest|newest|oldest|last\s+\d+)\b/i;
64
+ function looksLikeRowRequest(question) {
65
+ return ROW_REQUEST_RE.test(question) || RECENCY_RE.test(question);
66
+ }
67
+ function tokenize(text) {
68
+ return (text.toLowerCase().match(TOKEN_RE) ?? []).filter((t) => t.length > 1);
69
+ }
70
+ /** Content tokens (stopwords stripped) used for measure/family signal. */
71
+ function contentTokens(text) {
72
+ return tokenize(text).filter((t) => !STOPWORDS.has(t));
73
+ }
74
+ /** The measure families a set of words resolves to. */
75
+ function familiesFor(words) {
76
+ const set = new Set(words);
77
+ const families = new Set();
78
+ for (const [family, members] of Object.entries(MEASURE_FAMILIES)) {
79
+ if (members.some((member) => set.has(member)))
80
+ families.add(family);
81
+ }
82
+ return families;
83
+ }
84
+ /** Searchable text for a metric KG node: name, synonyms, label, description, tags. */
85
+ function metricSearchText(metric) {
86
+ return [
87
+ metric.name,
88
+ metric.name.replace(/[_.]+/g, ' '),
89
+ metric.description ?? '',
90
+ metric.llmContext ?? '',
91
+ ...(metric.tags ?? []),
92
+ ]
93
+ .filter(Boolean)
94
+ .join(' ');
95
+ }
96
+ /**
97
+ * Text used ONLY for measure-FAMILY detection: the metric's name + description +
98
+ * tags, but NOT its `llmContext`. The llmContext carries `table:`/`sql:` lines, so
99
+ * including it wrongly assigns a metric to the family of its backing TABLE (a
100
+ * `tax_paid` measure on the `orders` table would falsely read as the "orders"
101
+ * family). Family must reflect what the metric MEASURES, i.e. its name/label.
102
+ */
103
+ function metricFamilyText(metric) {
104
+ return [metric.name, metric.name.replace(/[_.]+/g, ' '), metric.description ?? '', ...(metric.tags ?? [])]
105
+ .filter(Boolean)
106
+ .join(' ');
107
+ }
108
+ /** The measure families a metric KG node resolves to (revenue, orders, ...). */
109
+ export function metricFamilies(metric) {
110
+ return familiesFor(contentTokens(metricFamilyText(metric)));
111
+ }
112
+ /**
113
+ * Pick the single best governed metric for a question, or `null` when no metric
114
+ * clears the confidence bar. Deterministic + offline by default (alpha=0).
115
+ *
116
+ * Scoring per candidate metric:
117
+ * base = lexical token overlap fraction (question content tokens present in
118
+ * the metric's searchable text), in [0, 1]
119
+ * family = +0.6 when the question and metric share a measure family
120
+ * name = +0.25 when a question content token appears in the metric NAME
121
+ * (so "food revenue" prefers `food_revenue` over `drink_revenue`)
122
+ * The blended FTS score is fed to `hybridRank` so spec-11 embeddings can refine
123
+ * it when a provider/alpha is supplied.
124
+ */
125
+ export async function matchSemanticMetric(question, metrics, options = {}) {
126
+ const candidates = metrics.filter((m) => m.kind === 'metric');
127
+ if (candidates.length === 0)
128
+ return null;
129
+ // A row/record-listing question wants raw rows, not a governed aggregate —
130
+ // let it fall through to grounded generated SQL even if it names a measure.
131
+ if (looksLikeRowRequest(question))
132
+ return null;
133
+ const threshold = options.threshold ?? 0.34;
134
+ const qContent = new Set(contentTokens(question));
135
+ if (qContent.size === 0)
136
+ return null;
137
+ const qFamilies = familiesFor(qContent);
138
+ // A measure question must carry at least one content word OR a measure family;
139
+ // a bare "what is this" carries neither and should not match a metric.
140
+ const items = candidates.map((metric) => {
141
+ const text = metricSearchText(metric).toLowerCase();
142
+ const nameTokens = new Set(tokenize(metric.name));
143
+ // Family is derived from the metric's name/label only (not its `table:`), so a
144
+ // measure is never mis-assigned to the family of the relation it sits on.
145
+ const metricFams = familiesFor(contentTokens(metricFamilyText(metric)));
146
+ let overlap = 0;
147
+ let nameHit = 0;
148
+ for (const token of qContent) {
149
+ if (text.includes(token))
150
+ overlap += 1;
151
+ if (nameTokens.has(token))
152
+ nameHit += 1;
153
+ }
154
+ const lexical = overlap / qContent.size;
155
+ // Fraction of question content tokens present in the metric NAME — this is
156
+ // what separates `food_revenue` from `cumulative_revenue` for "food revenue".
157
+ const nameFraction = nameHit / qContent.size;
158
+ let sharedFamily;
159
+ for (const family of qFamilies) {
160
+ if (metricFams.has(family)) {
161
+ sharedFamily = family;
162
+ break;
163
+ }
164
+ }
165
+ // Blend the signals on a fixed budget so no single component saturates the
166
+ // clamp and hides a more specific name match.
167
+ const familyBoost = sharedFamily ? 0.45 : 0;
168
+ const fts = clamp01(0.35 * lexical + familyBoost + 0.35 * nameFraction);
169
+ return { metric, text: metricSearchText(metric), ftsScore: fts, family: sharedFamily };
170
+ });
171
+ const ranked = await hybridRank(question, items.map((entry) => ({ item: entry, text: entry.text, ftsScore: entry.ftsScore })), { alpha: options.alpha ?? 0, provider: options.provider ?? (options.alpha ? defaultEmbeddingProvider() : undefined) });
172
+ const best = ranked[0];
173
+ if (!best || best.score < threshold)
174
+ return null;
175
+ return { metric: best.item.metric, score: best.score, family: best.item.family };
176
+ }
177
+ function clamp01(value) {
178
+ if (!Number.isFinite(value))
179
+ return 0;
180
+ return Math.max(0, Math.min(1, value));
181
+ }
182
+ /**
183
+ * Parse a metric KG node's governed definition: the aggregate `sql:` expression
184
+ * and its `table:` relation, stored on `llmContext`. Returns undefined when the
185
+ * `sql:` is not an aggregate expression (a bare metric-name reference) or there is
186
+ * no table — those are not directly executable.
187
+ */
188
+ export function parseMetricDefinition(metric) {
189
+ const context = metric.llmContext ?? '';
190
+ const expr = context.match(/(?:^|\n)\s*sql:\s*(.+?)\s*(?:\n|$)/i)?.[1]?.trim();
191
+ const table = context.match(/(?:^|\n)\s*table:\s*(.+?)\s*(?:\n|$)/i)?.[1]?.trim();
192
+ if (!expr || !table || !/[()]/.test(expr))
193
+ return undefined;
194
+ return { expr, table };
195
+ }
196
+ /**
197
+ * Deterministic, offline-safe governed SQL for a matched metric (spec 17, part C):
198
+ * a single read-only `SELECT <expr> AS <alias> FROM <table>` synthesised from the
199
+ * metric's governed definition. Returns undefined when the definition is too thin
200
+ * to execute, preserving honest refusal.
201
+ */
202
+ export function metricToGovernedSql(metric) {
203
+ const def = parseMetricDefinition(metric);
204
+ if (!def)
205
+ return undefined;
206
+ const alias = metric.name.replace(/[^a-zA-Z0-9_]+/g, '_').replace(/^_+|_+$/g, '') || 'metric_value';
207
+ return `SELECT ${def.expr} AS ${alias}\nFROM ${def.table}`;
208
+ }
209
+ /**
210
+ * Resolve a matched metric to executable governed SQL. dbt's semantic layer splits
211
+ * a metric from its measure: a derived MetricFlow metric (e.g. `revenue`, type=simple
212
+ * over measure `revenue`) carries no `table:`/`sql:` of its own — the aggregate lives
213
+ * on the backing measure node (`order_item.revenue` → `SUM(product_price)` over
214
+ * `order_items`). When the matched metric is itself synthesizable we use it directly;
215
+ * otherwise we fall back to a sibling whose LEAF name matches (`revenue` ⇒
216
+ * `<model>.revenue`). No fuzzy family guessing — precise resolution or nothing.
217
+ */
218
+ export function resolveGovernedMetricSql(metric, pool) {
219
+ const direct = metricToGovernedSql(metric);
220
+ if (direct)
221
+ return { sql: direct, metric };
222
+ const leaf = metric.name.split('.').pop()?.toLowerCase();
223
+ if (!leaf)
224
+ return undefined;
225
+ const leafSibling = pool
226
+ .filter((node) => node.nodeId !== metric.nodeId && node.name.split('.').pop()?.toLowerCase() === leaf)
227
+ .sort((a, b) => a.name.length - b.name.length)
228
+ .find((node) => metricToGovernedSql(node));
229
+ if (leafSibling)
230
+ return { sql: metricToGovernedSql(leafSibling), metric: leafSibling };
231
+ return undefined;
232
+ }
233
+ //# sourceMappingURL=metric-match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric-match.js","sourceRoot":"","sources":["../../src/metadata/metric-match.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Shared SQL grounding (spec 15) — ONE grounding for BOTH the governed
3
+ * answer-loop and the AI build engine. There is no "weak path".
4
+ *
5
+ * The grounding takes dbt artifacts (+ an optional set of relevant model names)
6
+ * and produces, for each table:
7
+ * - the REAL qualified warehouse relation (`database.schema.alias`, e.g.
8
+ * `dev.order_items`) — never a bare model name,
9
+ * - the `{{ ref('<model>') }}` form for governed BLOCK SQL,
10
+ * - real columns + types (catalog.json preferred, manifest YAML fallback),
11
+ * - JOIN KEYS inferred from dbt `depends_on` and shared `*_id` / `*_key`
12
+ * columns,
13
+ * - a `certifiedBlock` flag (reserved for callers that pass certified models).
14
+ *
15
+ * On top of the grounding sit two deterministic, offline-safe primitives the
16
+ * build path was missing:
17
+ * - `resolveRelationsInSql` — rewrites any bare / unqualified table name to
18
+ * its real qualified relation (model name OR alias → `db.schema.alias`),
19
+ * - `validateSqlAgainstGrounding` — flags any table or column the generated
20
+ * SQL references that is not in the grounding.
21
+ *
22
+ * Both are pure string/AST helpers so they work with no provider and never
23
+ * crash the offline path.
24
+ */
25
+ import type { DbtArtifacts } from '../propose/dbt-artifacts.js';
26
+ /** A single grounded table the model is allowed to reference. */
27
+ export interface GroundedColumn {
28
+ name: string;
29
+ type?: string;
30
+ description?: string;
31
+ }
32
+ export interface GroundedTable {
33
+ /** Logical model / source name (what `{{ ref() }}` uses). */
34
+ name: string;
35
+ /** Real qualified warehouse relation, e.g. `dev.order_items`. */
36
+ qualifiedRelation: string;
37
+ /** `{{ ref('<model>') }}` (models) or `{{ source(...) }}` (sources) form. */
38
+ refForm?: string;
39
+ /** Resolved columns with types. */
40
+ columns: GroundedColumn[];
41
+ /** 'model' | 'source'. */
42
+ kind: 'model' | 'source';
43
+ /** dbt uniqueId (for join-key inference and citations). */
44
+ uniqueId: string;
45
+ /** True when this table is backed by a certified DQL block. */
46
+ certifiedBlock?: boolean;
47
+ }
48
+ /** A join key the model can use to connect two grounded relations. */
49
+ export interface GroundedJoinKey {
50
+ leftRelation: string;
51
+ leftColumn: string;
52
+ rightRelation: string;
53
+ rightColumn: string;
54
+ reason: string;
55
+ }
56
+ export interface SchemaGrounding {
57
+ tables: GroundedTable[];
58
+ joinKeys: GroundedJoinKey[];
59
+ /** Lower-cased lookup: bare name / alias / qualified relation → GroundedTable. */
60
+ byKey: Map<string, GroundedTable>;
61
+ }
62
+ export interface BuildSchemaGroundingOptions {
63
+ /** Cap on tables included in the grounding (top-K from retrieval). */
64
+ limit?: number;
65
+ /** Model/relation names that are backed by certified DQL blocks. */
66
+ certifiedModelNames?: Iterable<string>;
67
+ }
68
+ /** Lower-cased keys a relation can be referenced by (qualified, last 2, bare). */
69
+ export declare function relationKeys(relation: string): string[];
70
+ /**
71
+ * Build the shared schema grounding for a set of dbt models (+ sources).
72
+ * `relevantModels`, when provided, restricts/orders the grounding to the
73
+ * retrieval-selected relations; otherwise all models are grounded (capped).
74
+ */
75
+ export declare function buildSchemaGrounding(artifacts: DbtArtifacts | undefined, relevantModels?: Iterable<string> | undefined, options?: BuildSchemaGroundingOptions): SchemaGrounding;
76
+ /** A pre-qualified runtime relation (the answer-loop's schema context shape). */
77
+ export interface RuntimeRelationInput {
78
+ /** Already-qualified relation, e.g. `dev.order_items`. */
79
+ relation: string;
80
+ /** Bare table name. */
81
+ name?: string;
82
+ columns: Array<{
83
+ name: string;
84
+ type?: string;
85
+ description?: string;
86
+ }>;
87
+ }
88
+ /**
89
+ * Build a `SchemaGrounding` from already-qualified runtime relations (the
90
+ * answer-loop receives these from the host as `schemaContext` /
91
+ * `allowedSqlContext`, so the relations are already real warehouse relations).
92
+ * This lets the governed answer-loop reuse the SAME resolver + validator as the
93
+ * build path without re-reading dbt artifacts. No join-key inference here —
94
+ * the answer-loop already renders join paths from its context pack.
95
+ */
96
+ export declare function buildGroundingFromRuntimeRelations(relations: RuntimeRelationInput[]): SchemaGrounding;
97
+ export interface RelationResolution {
98
+ /** SQL with bare / unqualified table names rewritten to qualified relations. */
99
+ sql: string;
100
+ /** Each rewrite applied, e.g. order_items → dev.order_items. */
101
+ rewrites: Array<{
102
+ from: string;
103
+ to: string;
104
+ }>;
105
+ }
106
+ /**
107
+ * Deterministically rewrite any bare / unqualified table name in `FROM` /
108
+ * `JOIN` clauses to its real qualified relation from the grounding. Maps both
109
+ * the model name and its alias to `database.schema.alias`. Already-qualified
110
+ * relations that match the grounding are left untouched.
111
+ *
112
+ * For BLOCK SQL the caller may request the `{{ ref() }}` form instead of the
113
+ * qualified relation via `prefer: 'ref'`.
114
+ */
115
+ export declare function resolveRelationsInSql(sql: string, grounding: SchemaGrounding, options?: {
116
+ prefer?: 'qualified' | 'ref';
117
+ }): RelationResolution;
118
+ export type GroundingValidationCode = 'unknown_relation' | 'unknown_column' | 'unsafe_sql' | 'unparseable';
119
+ export type GroundingValidationResult = {
120
+ ok: true;
121
+ warnings: string[];
122
+ referencedRelations: string[];
123
+ } | {
124
+ ok: false;
125
+ code: GroundingValidationCode;
126
+ error: string;
127
+ warnings: string[];
128
+ referencedRelations: string[];
129
+ /** The specific table/column that failed, for a targeted repair re-prompt. */
130
+ offending?: {
131
+ relation?: string;
132
+ column?: string;
133
+ };
134
+ };
135
+ /**
136
+ * Validate that generated SQL references ONLY relations and columns present in
137
+ * the shared grounding. Read-only safety is enforced too (no DML/DDL). Returns
138
+ * a precise `offending` token so the build path can re-prompt the model with
139
+ * the exact miss.
140
+ */
141
+ export declare function validateSqlAgainstGrounding(sql: string, grounding: SchemaGrounding): GroundingValidationResult;
142
+ /**
143
+ * Render the grounding as a prompt block. Each table is presented as BOTH its
144
+ * qualified relation AND its `{{ ref() }}` form with real columns+types, plus
145
+ * the inferred join keys. The convention line tells the model how to reference
146
+ * relations for the given target (CELL → qualified; BLOCK → `{{ ref() }}`).
147
+ */
148
+ export declare function renderGroundingForPrompt(grounding: SchemaGrounding, target?: 'cell' | 'block' | 'answer'): string;
149
+ //# sourceMappingURL=sql-grounding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-grounding.d.ts","sourceRoot":"","sources":["../../src/metadata/sql-grounding.ts"],"names":[],"mappings":"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"}