@duckcodeailabs/dql-agent 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/agent-run-engine.d.ts +8 -6
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +34 -2
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/answer-loop.d.ts +9 -1
  6. package/dist/answer-loop.d.ts.map +1 -1
  7. package/dist/answer-loop.js +59 -5
  8. package/dist/answer-loop.js.map +1 -1
  9. package/dist/app-builder.d.ts +19 -1
  10. package/dist/app-builder.d.ts.map +1 -1
  11. package/dist/app-builder.js +261 -51
  12. package/dist/app-builder.js.map +1 -1
  13. package/dist/cascade/route-policy.d.ts +1 -1
  14. package/dist/cascade/route-policy.d.ts.map +1 -1
  15. package/dist/context-authoring.d.ts +98 -0
  16. package/dist/context-authoring.d.ts.map +1 -0
  17. package/dist/context-authoring.js +146 -0
  18. package/dist/context-authoring.js.map +1 -0
  19. package/dist/index.d.ts +5 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/metadata/analytical-policy.d.ts +8 -0
  24. package/dist/metadata/analytical-policy.d.ts.map +1 -1
  25. package/dist/metadata/analytical-policy.js +5 -2
  26. package/dist/metadata/analytical-policy.js.map +1 -1
  27. package/dist/metadata/catalog.d.ts +2 -1
  28. package/dist/metadata/catalog.d.ts.map +1 -1
  29. package/dist/metadata/catalog.js +106 -14
  30. package/dist/metadata/catalog.js.map +1 -1
  31. package/dist/metadata/meaning-evidence.d.ts +0 -5
  32. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  33. package/dist/metadata/meaning-evidence.js +57 -0
  34. package/dist/metadata/meaning-evidence.js.map +1 -1
  35. package/dist/propose/build-from-prompt.d.ts.map +1 -1
  36. package/dist/propose/build-from-prompt.js +27 -0
  37. package/dist/propose/build-from-prompt.js.map +1 -1
  38. package/dist/skills/loader.d.ts +24 -0
  39. package/dist/skills/loader.d.ts.map +1 -1
  40. package/dist/skills/loader.js +137 -6
  41. package/dist/skills/loader.js.map +1 -1
  42. package/package.json +4 -4
@@ -959,6 +959,7 @@ export async function answer(input) {
959
959
  : selectRelevantSkills(executionInput.skills ?? [], executionInput.question, {
960
960
  userId: executionInput.userId ?? null,
961
961
  modelAreaIds: executionInput.domainContext?.modelAreaId ? [executionInput.domainContext.modelAreaId] : [],
962
+ focusObjectKeys: executionInput.contextPack?.focusObjectKey ? [executionInput.contextPack.focusObjectKey] : [],
962
963
  domains: Array.from(new Set([
963
964
  ...domainContextSearchDomains(executionInput.domainContext),
964
965
  ...(executionInput.domain ? [executionInput.domain] : []),
@@ -1062,6 +1063,7 @@ async function runAnswerLoop(input) {
1062
1063
  userId: userId ?? null,
1063
1064
  domains: inferredDomains,
1064
1065
  modelAreaIds: input.domainContext?.modelAreaId ? [input.domainContext.modelAreaId] : [],
1066
+ focusObjectKeys: input.contextPack?.focusObjectKey ? [input.contextPack.focusObjectKey] : [],
1065
1067
  });
1066
1068
  const effectiveBlockHints = Array.from(new Set([
1067
1069
  ...blockHints,
@@ -1757,7 +1759,11 @@ async function runAnswerLoop(input) {
1757
1759
  domainContext: input.domainContext,
1758
1760
  })
1759
1761
  : undefined;
1760
- const analyticalPlanPrompt = renderAnalyticalPlanPrompt(analyticalPlan);
1762
+ // A multi-entity question gets the certified join plan; a single-entity one
1763
+ // gets that entity's authored grain and business meaning. Before this, only
1764
+ // the former existed, so most everyday questions saw no modeling at all.
1765
+ const analyticalPlanPrompt = renderAnalyticalPlanPrompt(analyticalPlan)
1766
+ ?? renderSingleEntityModelingPrompt(analyticalPlan, input.manifest);
1761
1767
  if (analyticalPlanPrompt)
1762
1768
  messages.push({ role: 'system', content: analyticalPlanPrompt });
1763
1769
  if (questionDomainScope.length > 0) {
@@ -4185,7 +4191,35 @@ function entityIdentifierTokensForMatching(entity) {
4185
4191
  .flatMap((value) => value.toLowerCase().replace(/^(fct|dim|stg)_/, '').split(/[^a-z0-9]+/))
4186
4192
  .filter((token) => token.length > 2 && token !== 'entity');
4187
4193
  }
4188
- function renderAnalyticalPlanPrompt(plan) {
4194
+ /**
4195
+ * CTX-008: the authored business identity of a single modeled entity. Most
4196
+ * real questions name exactly one entity, and those used to receive no modeling
4197
+ * context at all because the join planner needs two. Grain and business context
4198
+ * matter just as much to a one-table answer as keys do to a join.
4199
+ */
4200
+ export function renderSingleEntityModelingPrompt(plan, manifest) {
4201
+ if (!plan || plan.entities.length !== 1 || !manifest?.modeling)
4202
+ return undefined;
4203
+ const entity = manifest.modeling.entities[plan.entities[0]];
4204
+ if (!entity)
4205
+ return undefined;
4206
+ const relation = manifest.dbtProvenance?.nodes[entity.dbtUniqueId]?.relation;
4207
+ const lines = [
4208
+ `- ${entity.businessName || entity.localId}${relation ? ` (${relation})` : ''}`,
4209
+ entity.businessContext ? ` meaning: ${entity.businessContext}` : '',
4210
+ entity.grain ? ` grain: one row per ${entity.grain}` : '',
4211
+ entity.keys.length ? ` keys: ${entity.keys.join(', ')}` : '',
4212
+ entity.analyticalRole && entity.analyticalRole !== 'unknown' ? ` role: ${entity.analyticalRole}` : '',
4213
+ ].filter(Boolean);
4214
+ if (lines.length === 1 && !entity.businessName)
4215
+ return undefined;
4216
+ return [
4217
+ 'DQL MODELED ENTITY (authored business context):',
4218
+ ...lines,
4219
+ 'Respect the stated grain: do not aggregate across it in a way that double counts, and use the business meaning to interpret the question.',
4220
+ ].join('\n');
4221
+ }
4222
+ export function renderAnalyticalPlanPrompt(plan) {
4189
4223
  if (!plan || plan.entities.length < 2)
4190
4224
  return undefined;
4191
4225
  if (!plan.safe) {
@@ -4222,11 +4256,19 @@ function renderAnalyticalPlanPrompt(plan) {
4222
4256
  ...plan.edges.map((edge, index) => [
4223
4257
  `${index + 1}. ${edge.fromEntity} (${edge.fromRelation ?? 'bound dbt model'}) -> ${edge.toEntity} (${edge.toRelation ?? 'bound dbt model'})`,
4224
4258
  ` relationship=${edge.relationshipId}; keys=${edge.keys.map((key) => `${key.from}=${key.to}`).join(', ')}; cardinality=${edge.cardinality}; fanout=${edge.fanout}`,
4259
+ // CTX-008: the authored meaning travels with the keys, so the model can
4260
+ // tell a technically valid join from the one the question actually wants.
4261
+ analyticalEdgeMeaning(edge),
4225
4262
  edge.importRefs.length ? ` imports=${edge.importRefs.join(', ')}` : '',
4226
4263
  ].filter(Boolean).join('\n')),
4227
4264
  'Use only these relationships and exact key pairs. dbt DAG lineage and same-named columns are not join authorization. Any different join must be refused.',
4228
4265
  ].join('\n');
4229
4266
  }
4267
+ /** One `means:` line describing what a join represents, when it was authored. */
4268
+ function analyticalEdgeMeaning(edge) {
4269
+ const meaning = [edge.verb, edge.description, edge.rationale].filter(Boolean).join(' — ');
4270
+ return meaning ? ` means: ${meaning}` : '';
4271
+ }
4230
4272
  /**
4231
4273
  * Missing modeling may enter the narrow EXP-001 host-validated lane. Explicit
4232
4274
  * unsafe, cross-domain, attribution, expiry, purpose, and proof failures never do.
@@ -4560,10 +4602,22 @@ function renderCandidateJoinsForPrompt(contextPack, budget) {
4560
4602
  ...joins.map((join) => `- ${join.leftRelation}.${join.leftColumn} -> ${join.rightRelation}.${join.rightColumn}${join.reason ? ` (${join.reason})` : ''}`),
4561
4603
  ].join('\n');
4562
4604
  }
4605
+ /**
4606
+ * CTX-008: `proves_join` edges are the governed relationship graph the user
4607
+ * authored, and the quick budget sets `edgeLimit: 0` — which blacked them out
4608
+ * entirely on exactly the lookup questions that most need them. Governed join
4609
+ * proof always earns a small allowance; every other edge class still respects
4610
+ * the budget.
4611
+ */
4612
+ const GOVERNED_JOIN_EDGE_FLOOR = 12;
4563
4613
  function renderContextEdgesForPrompt(contextPack, budget) {
4564
- if (budget.edgeLimit <= 0)
4565
- return '';
4566
- const edges = contextPack.edges.slice(0, budget.edgeLimit);
4614
+ const governed = contextPack.edges
4615
+ .filter((edge) => edge.edgeType === 'proves_join')
4616
+ .slice(0, Math.max(budget.edgeLimit, GOVERNED_JOIN_EDGE_FLOOR));
4617
+ const others = budget.edgeLimit > 0
4618
+ ? contextPack.edges.filter((edge) => edge.edgeType !== 'proves_join').slice(0, budget.edgeLimit)
4619
+ : [];
4620
+ const edges = [...governed, ...others];
4567
4621
  if (edges.length === 0)
4568
4622
  return '';
4569
4623
  return [