@duckcodeailabs/dql-agent 1.6.21 → 1.6.23
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.
- package/dist/agent-run-engine.d.ts +287 -0
- package/dist/agent-run-engine.d.ts.map +1 -0
- package/dist/agent-run-engine.js +916 -0
- package/dist/agent-run-engine.js.map +1 -0
- package/dist/agent-run-gates.d.ts +26 -0
- package/dist/agent-run-gates.d.ts.map +1 -0
- package/dist/agent-run-gates.js +170 -0
- package/dist/agent-run-gates.js.map +1 -0
- package/dist/agent-run-planner.d.ts +32 -0
- package/dist/agent-run-planner.d.ts.map +1 -0
- package/dist/agent-run-planner.js +280 -0
- package/dist/agent-run-planner.js.map +1 -0
- package/dist/answer-loop.d.ts +49 -0
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +292 -17
- package/dist/answer-loop.js.map +1 -1
- package/dist/app-builder.d.ts +6 -0
- package/dist/app-builder.d.ts.map +1 -1
- package/dist/app-builder.js +95 -2
- package/dist/app-builder.js.map +1 -1
- package/dist/app-planner.d.ts +78 -0
- package/dist/app-planner.d.ts.map +1 -0
- package/dist/app-planner.js +145 -0
- package/dist/app-planner.js.map +1 -0
- package/dist/embeddings/provider.d.ts +66 -0
- package/dist/embeddings/provider.d.ts.map +1 -0
- package/dist/embeddings/provider.js +105 -0
- package/dist/embeddings/provider.js.map +1 -0
- package/dist/hints/git-store.d.ts +69 -0
- package/dist/hints/git-store.d.ts.map +1 -0
- package/dist/hints/git-store.js +244 -0
- package/dist/hints/git-store.js.map +1 -0
- package/dist/hints/retrieval.d.ts +49 -0
- package/dist/hints/retrieval.d.ts.map +1 -0
- package/dist/hints/retrieval.js +60 -0
- package/dist/hints/retrieval.js.map +1 -0
- package/dist/hints/store.d.ts +39 -0
- package/dist/hints/store.d.ts.map +1 -0
- package/dist/hints/store.js +279 -0
- package/dist/hints/store.js.map +1 -0
- package/dist/hints/types.d.ts +147 -0
- package/dist/hints/types.d.ts.map +1 -0
- package/dist/hints/types.js +92 -0
- package/dist/hints/types.js.map +1 -0
- package/dist/index.d.ts +43 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +63 -0
- package/dist/intent-controller.d.ts.map +1 -0
- package/dist/intent-controller.js +150 -0
- package/dist/intent-controller.js.map +1 -0
- package/dist/kg/build.d.ts.map +1 -1
- package/dist/kg/build.js +2 -0
- package/dist/kg/build.js.map +1 -1
- package/dist/kg/sqlite-fts.js +4 -0
- package/dist/kg/sqlite-fts.js.map +1 -1
- package/dist/kg/types.d.ts +10 -0
- package/dist/kg/types.d.ts.map +1 -1
- package/dist/metadata/catalog.d.ts +81 -4
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +208 -1
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/drafts.d.ts +22 -0
- package/dist/metadata/drafts.d.ts.map +1 -1
- package/dist/metadata/drafts.js +126 -2
- package/dist/metadata/drafts.js.map +1 -1
- package/dist/metadata/grain-gate.d.ts +61 -0
- package/dist/metadata/grain-gate.d.ts.map +1 -0
- package/dist/metadata/grain-gate.js +265 -0
- package/dist/metadata/grain-gate.js.map +1 -0
- package/dist/metadata/identity.d.ts +47 -0
- package/dist/metadata/identity.d.ts.map +1 -0
- package/dist/metadata/identity.js +147 -0
- package/dist/metadata/identity.js.map +1 -0
- package/dist/metadata/metric-match.d.ts +93 -0
- package/dist/metadata/metric-match.d.ts.map +1 -0
- package/dist/metadata/metric-match.js +233 -0
- package/dist/metadata/metric-match.js.map +1 -0
- package/dist/metadata/sql-grounding.d.ts +149 -0
- package/dist/metadata/sql-grounding.d.ts.map +1 -0
- package/dist/metadata/sql-grounding.js +397 -0
- package/dist/metadata/sql-grounding.js.map +1 -0
- package/dist/metadata/sql-retrieval.d.ts +29 -0
- package/dist/metadata/sql-retrieval.d.ts.map +1 -0
- package/dist/metadata/sql-retrieval.js +79 -0
- package/dist/metadata/sql-retrieval.js.map +1 -0
- package/dist/narrate.d.ts +52 -0
- package/dist/narrate.d.ts.map +1 -0
- package/dist/narrate.js +263 -0
- package/dist/narrate.js.map +1 -0
- package/dist/propose/build-from-prompt.d.ts +160 -0
- package/dist/propose/build-from-prompt.d.ts.map +1 -0
- package/dist/propose/build-from-prompt.js +815 -0
- package/dist/propose/build-from-prompt.js.map +1 -0
- package/dist/propose/classify.d.ts +55 -0
- package/dist/propose/classify.d.ts.map +1 -0
- package/dist/propose/classify.js +167 -0
- package/dist/propose/classify.js.map +1 -0
- package/dist/propose/config.d.ts +39 -0
- package/dist/propose/config.d.ts.map +1 -0
- package/dist/propose/config.js +52 -0
- package/dist/propose/config.js.map +1 -0
- package/dist/propose/dbt-artifacts.d.ts +134 -0
- package/dist/propose/dbt-artifacts.d.ts.map +1 -0
- package/dist/propose/dbt-artifacts.js +320 -0
- package/dist/propose/dbt-artifacts.js.map +1 -0
- package/dist/propose/edit-block.d.ts +74 -0
- package/dist/propose/edit-block.d.ts.map +1 -0
- package/dist/propose/edit-block.js +133 -0
- package/dist/propose/edit-block.js.map +1 -0
- package/dist/propose/enrich.d.ts +48 -0
- package/dist/propose/enrich.d.ts.map +1 -0
- package/dist/propose/enrich.js +125 -0
- package/dist/propose/enrich.js.map +1 -0
- package/dist/propose/generate-sql.d.ts +61 -0
- package/dist/propose/generate-sql.d.ts.map +1 -0
- package/dist/propose/generate-sql.js +180 -0
- package/dist/propose/generate-sql.js.map +1 -0
- package/dist/propose/index.d.ts +24 -0
- package/dist/propose/index.d.ts.map +1 -0
- package/dist/propose/index.js +15 -0
- package/dist/propose/index.js.map +1 -0
- package/dist/propose/propose.d.ts +226 -0
- package/dist/propose/propose.d.ts.map +1 -0
- package/dist/propose/propose.js +668 -0
- package/dist/propose/propose.js.map +1 -0
- package/dist/propose/reflect-block.d.ts +99 -0
- package/dist/propose/reflect-block.d.ts.map +1 -0
- package/dist/propose/reflect-block.js +220 -0
- package/dist/propose/reflect-block.js.map +1 -0
- package/dist/propose/write-draft.d.ts +90 -0
- package/dist/propose/write-draft.d.ts.map +1 -0
- package/dist/propose/write-draft.js +185 -0
- package/dist/propose/write-draft.js.map +1 -0
- package/dist/providers/claude.d.ts +13 -1
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +20 -2
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/gemini.d.ts +13 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +24 -3
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -3
- package/dist/providers/index.js.map +1 -1
- package/dist/research-loop.d.ts +62 -0
- package/dist/research-loop.d.ts.map +1 -0
- package/dist/research-loop.js +210 -0
- package/dist/research-loop.js.map +1 -0
- package/dist/skills/defaults.d.ts +37 -0
- package/dist/skills/defaults.d.ts.map +1 -0
- package/dist/skills/defaults.js +152 -0
- package/dist/skills/defaults.js.map +1 -0
- package/dist/skills/loader.d.ts +60 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +164 -2
- package/dist/skills/loader.js.map +1 -1
- package/package.json +7 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../src/metadata/drafts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../src/metadata/drafts.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8FAA8F;IAC9F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAUD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQjE;AAqGD,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAW7E;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,oBAAoB,GACxB,mBAAmB,CAiCrB"}
|
package/dist/metadata/drafts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { resolveLocalOwner } from './identity.js';
|
|
3
4
|
const STOPWORDS = new Set([
|
|
4
5
|
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'do', 'for', 'from',
|
|
5
6
|
'has', 'have', 'how', 'i', 'in', 'is', 'it', 'me', 'much', 'my', 'of',
|
|
@@ -16,16 +17,138 @@ export function deriveGeneratedDraftSlug(question) {
|
|
|
16
17
|
const slug = tokens.join('_').slice(0, 60).replace(/_+$/, '');
|
|
17
18
|
return slug || 'untitled_proposal';
|
|
18
19
|
}
|
|
20
|
+
/** Snake_case identifier rule the namer enforces: `^[a-z][a-z0-9_]*$`. */
|
|
21
|
+
const SEMANTIC_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
22
|
+
/**
|
|
23
|
+
* Words that hint at the metric/entity being asked about. Kept tiny and
|
|
24
|
+
* convention-agnostic — this is a deterministic fallback, not an NLP model.
|
|
25
|
+
*/
|
|
26
|
+
const GRAIN_HINTS = [
|
|
27
|
+
{ re: /\b(daily|per day|by day|each day)\b/i, grain: 'daily' },
|
|
28
|
+
{ re: /\b(weekly|per week|by week|each week)\b/i, grain: 'weekly' },
|
|
29
|
+
{ re: /\b(monthly|per month|by month|each month)\b/i, grain: 'monthly' },
|
|
30
|
+
{ re: /\b(quarterly|per quarter|by quarter)\b/i, grain: 'quarterly' },
|
|
31
|
+
{ re: /\b(yearly|annual|annually|per year|by year)\b/i, grain: 'yearly' },
|
|
32
|
+
];
|
|
33
|
+
/** Common analytic entities/measures we recognize for the rule-based fallback. */
|
|
34
|
+
const ENTITY_HINTS = [
|
|
35
|
+
'orders', 'order', 'revenue', 'sales', 'customers', 'customer', 'users', 'user',
|
|
36
|
+
'sessions', 'session', 'payments', 'payment', 'subscriptions', 'subscription',
|
|
37
|
+
'churn', 'retention', 'signups', 'signup', 'spend', 'cost', 'profit', 'margin',
|
|
38
|
+
'shipments', 'shipment', 'products', 'product', 'accounts', 'account', 'leads',
|
|
39
|
+
'lead', 'tickets', 'ticket', 'visits', 'visit', 'transactions', 'transaction',
|
|
40
|
+
];
|
|
41
|
+
/** A "by <dimension>" phrase points at the key dimension of the question. */
|
|
42
|
+
const DIMENSION_RE = /\bby\s+([a-z][a-z0-9 ]{1,30}?)(?:\s+(?:at|per|for|in|over|grouped|broken)\b|[?.,]|$)/i;
|
|
43
|
+
function snakeTokens(value) {
|
|
44
|
+
return value
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
47
|
+
.split(/\s+/)
|
|
48
|
+
.filter((token) => token.length > 0);
|
|
49
|
+
}
|
|
50
|
+
function toSnake(value) {
|
|
51
|
+
return snakeTokens(value).join('_').replace(/^_+|_+$/g, '');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate + normalize a provider-suggested block name. Returns a clean
|
|
55
|
+
* snake_case slug (deduped against `existing`) or undefined when the suggestion
|
|
56
|
+
* is unusable.
|
|
57
|
+
*/
|
|
58
|
+
function sanitizeProviderName(suggested, existing) {
|
|
59
|
+
if (typeof suggested !== 'string')
|
|
60
|
+
return undefined;
|
|
61
|
+
let candidate = suggested.trim().toLowerCase();
|
|
62
|
+
if (!candidate)
|
|
63
|
+
return undefined;
|
|
64
|
+
// Coerce light formatting (spaces / dashes / camelCase) into snake_case before
|
|
65
|
+
// validating, so a well-meaning but loosely-formatted suggestion still passes.
|
|
66
|
+
if (!SEMANTIC_NAME_RE.test(candidate)) {
|
|
67
|
+
candidate = candidate
|
|
68
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
71
|
+
.replace(/^_+|_+$/g, '');
|
|
72
|
+
}
|
|
73
|
+
if (!SEMANTIC_NAME_RE.test(candidate))
|
|
74
|
+
return undefined;
|
|
75
|
+
return dedupeSlug(candidate.slice(0, 60).replace(/_+$/, ''), existing);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Rule-based semantic extraction: entity (+ key dimension + grain) from the
|
|
79
|
+
* question, e.g. "total orders by geography at the daily level" →
|
|
80
|
+
* `orders_by_geography_daily`. Returns undefined when no entity is recognizable.
|
|
81
|
+
*/
|
|
82
|
+
function ruleBasedName(question) {
|
|
83
|
+
const lower = question.toLowerCase();
|
|
84
|
+
const tokens = snakeTokens(question);
|
|
85
|
+
// Entity: first recognized analytic noun, singular/plural insensitive.
|
|
86
|
+
const entity = ENTITY_HINTS.find((hint) => tokens.includes(hint));
|
|
87
|
+
if (!entity)
|
|
88
|
+
return undefined;
|
|
89
|
+
const parts = [entity];
|
|
90
|
+
// Key dimension: a "by <dimension>" phrase, reduced to its head noun.
|
|
91
|
+
const dimMatch = lower.match(DIMENSION_RE);
|
|
92
|
+
if (dimMatch) {
|
|
93
|
+
const dim = toSnake(dimMatch[1]);
|
|
94
|
+
if (dim && dim !== entity)
|
|
95
|
+
parts.push('by', dim);
|
|
96
|
+
}
|
|
97
|
+
// Grain: an explicit time grain hint.
|
|
98
|
+
const grainHint = GRAIN_HINTS.find((hint) => hint.re.test(lower));
|
|
99
|
+
if (grainHint)
|
|
100
|
+
parts.push(grainHint.grain);
|
|
101
|
+
const slug = parts.join('_').replace(/^_+|_+$/g, '');
|
|
102
|
+
return slug || undefined;
|
|
103
|
+
}
|
|
104
|
+
/** Append `_2`, `_3`, … until the slug is unused. */
|
|
105
|
+
function dedupeSlug(slug, existing) {
|
|
106
|
+
if (!existing.has(slug))
|
|
107
|
+
return slug;
|
|
108
|
+
let n = 2;
|
|
109
|
+
while (existing.has(`${slug}_${n}`))
|
|
110
|
+
n += 1;
|
|
111
|
+
return `${slug}_${n}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Pick a SEMANTIC snake_case name for a builder-generated block.
|
|
115
|
+
*
|
|
116
|
+
* Priority:
|
|
117
|
+
* 1. A valid provider-suggested name (`^[a-z][a-z0-9_]*$`), deduped.
|
|
118
|
+
* 2. A rule-based extraction (entity + key dimension + grain), deduped.
|
|
119
|
+
* 3. The legacy question tokenizer (`deriveGeneratedDraftSlug`) — LAST resort.
|
|
120
|
+
*
|
|
121
|
+
* This deliberately avoids the old behavior of naming a block after the literal
|
|
122
|
+
* question (e.g. `can_you_build_the_total_orders_by_geography_at_the_level`).
|
|
123
|
+
*/
|
|
124
|
+
export function deriveSemanticDraftName(input) {
|
|
125
|
+
const existing = new Set(input.existingSlugs ?? []);
|
|
126
|
+
const fromProvider = sanitizeProviderName(input.providerName, existing);
|
|
127
|
+
if (fromProvider)
|
|
128
|
+
return fromProvider;
|
|
129
|
+
const fromRule = ruleBasedName(input.question);
|
|
130
|
+
if (fromRule)
|
|
131
|
+
return dedupeSlug(fromRule.slice(0, 60).replace(/_+$/, ''), existing);
|
|
132
|
+
// Last resort: the legacy tokenizer, still deduped so re-asks don't collide.
|
|
133
|
+
return dedupeSlug(deriveGeneratedDraftSlug(input.question), existing);
|
|
134
|
+
}
|
|
19
135
|
export function upsertGeneratedDraft(projectRoot, rec) {
|
|
20
136
|
const { filePath, relativePath } = resolveGeneratedDraftPath(projectRoot, rec);
|
|
137
|
+
// Owner is the one field a human must confirm to certify — never let a captured
|
|
138
|
+
// draft be born owner-less (it would hard-block certification for no reason).
|
|
139
|
+
const owner = rec.owner?.trim() || resolveLocalOwner(projectRoot);
|
|
21
140
|
if (existsSync(filePath)) {
|
|
22
141
|
const existing = readFileSync(filePath, 'utf-8');
|
|
23
142
|
const m = existing.match(/asked_times\s*=\s*(\d+)/);
|
|
24
143
|
const prev = m ? Number.parseInt(m[1], 10) : 1;
|
|
25
144
|
const next = Number.isFinite(prev) ? prev + 1 : 2;
|
|
26
|
-
|
|
145
|
+
let updated = m
|
|
27
146
|
? existing.replace(/asked_times\s*=\s*\d+/, `asked_times = ${next}`)
|
|
28
147
|
: existing.replace(/\n\s*query\s*=/, `\n asked_times = ${next}\n\n query =`);
|
|
148
|
+
// Backfill owner on older owner-less drafts so re-asking makes them certifiable.
|
|
149
|
+
if (!/\n\s*owner\s*=/.test(updated)) {
|
|
150
|
+
updated = updated.replace(/(\n\s*status\s*=\s*"draft")/, `$1\n owner = "${escapeDqlString(owner)}"`);
|
|
151
|
+
}
|
|
29
152
|
writeFileSync(filePath, stampLastAsked(updated));
|
|
30
153
|
return {
|
|
31
154
|
path: relativePath,
|
|
@@ -34,7 +157,7 @@ export function upsertGeneratedDraft(projectRoot, rec) {
|
|
|
34
157
|
};
|
|
35
158
|
}
|
|
36
159
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
37
|
-
writeFileSync(filePath, renderGeneratedDraft(rec, relativePath));
|
|
160
|
+
writeFileSync(filePath, renderGeneratedDraft({ ...rec, owner }, relativePath));
|
|
38
161
|
return {
|
|
39
162
|
path: relativePath,
|
|
40
163
|
askedTimes: 1,
|
|
@@ -66,6 +189,7 @@ function renderGeneratedDraft(rec, draftPath) {
|
|
|
66
189
|
domain = "${escapeDqlString(rec.proposedDomain ?? 'misc')}"
|
|
67
190
|
type = "custom"
|
|
68
191
|
status = "draft"
|
|
192
|
+
owner = "${escapeDqlString(rec.owner ?? '')}"
|
|
69
193
|
description = """${rec.question.replace(/"""/g, '\\"\\"\\"')}"""
|
|
70
194
|
|
|
71
195
|
// Tier-2 generated proposal. This block is NOT certified.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/metadata/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/metadata/drafts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grain / contract gate for certified-block routing.
|
|
3
|
+
*
|
|
4
|
+
* Retrieval is keyword-based (FTS5/BM25), so the router can surface a certified
|
|
5
|
+
* block that is *close* to the question but answers a DIFFERENT grain than what
|
|
6
|
+
* was asked (e.g. an account-grain block for a region-grain question). Serving
|
|
7
|
+
* that as "Certified" is worse than refusing — it is a confidently wrong governed
|
|
8
|
+
* answer.
|
|
9
|
+
*
|
|
10
|
+
* Blocks already carry `grain`, `entities`, and `declaredOutputs`; this module
|
|
11
|
+
* uses them as a hard gate. Before a Tier-1 certified route is served, the
|
|
12
|
+
* question's requested grain/entity is compared with the candidate block's
|
|
13
|
+
* declared grain. Exact or compatible (coarser-rollup) grains pass; a genuine
|
|
14
|
+
* grain/entity mismatch demotes the answer to Tier-2 (generated, block-as-context).
|
|
15
|
+
*
|
|
16
|
+
* The gate is deliberately CONSERVATIVE: when the question has no clearly
|
|
17
|
+
* extractable grain, or the block declares no grain, it does NOT demote — it
|
|
18
|
+
* must not regress today's behavior or over-refuse true matches.
|
|
19
|
+
*/
|
|
20
|
+
import type { MetadataObject } from './catalog.js';
|
|
21
|
+
import type { AnalysisQuestionPlan } from './analysis-planner.js';
|
|
22
|
+
/** Coarsest-to-finest time grain ordering. Lower index = finer granularity. */
|
|
23
|
+
declare const TIME_GRAIN_ORDER: readonly ["hour", "day", "week", "month", "quarter", "year"];
|
|
24
|
+
type TimeGrain = (typeof TIME_GRAIN_ORDER)[number];
|
|
25
|
+
export type GrainGateKind = 'no_requested_grain' | 'block_grain_unknown' | 'exact' | 'compatible_rollup' | 'mismatch';
|
|
26
|
+
export interface RequestedGrain {
|
|
27
|
+
/** Canonical time grain requested, if the question implies one. */
|
|
28
|
+
time?: TimeGrain;
|
|
29
|
+
/**
|
|
30
|
+
* Entity/dimension grain tokens requested by the question (normalized,
|
|
31
|
+
* canonicalized to a synonym group representative where known).
|
|
32
|
+
*/
|
|
33
|
+
entities: string[];
|
|
34
|
+
/** True when neither a time grain nor an entity grain could be extracted. */
|
|
35
|
+
empty: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface GrainGateResult {
|
|
38
|
+
/** Whether the candidate block may be served as a Tier-1 certified answer. */
|
|
39
|
+
allow: boolean;
|
|
40
|
+
kind: GrainGateKind;
|
|
41
|
+
/** Human-readable description of the requested grain (for routeReason). */
|
|
42
|
+
requestedGrainLabel: string;
|
|
43
|
+
/** Human-readable description of the block's declared grain (for routeReason). */
|
|
44
|
+
blockGrainLabel: string;
|
|
45
|
+
reason: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract the question's requested grain from the already-computed analysis
|
|
49
|
+
* plan. Conservative by design: we only treat a token as a requested grain when
|
|
50
|
+
* it is a recognized dimension/time word. Free-form named entities (e.g. a
|
|
51
|
+
* person's name used as a filter value) are NOT a grain request.
|
|
52
|
+
*/
|
|
53
|
+
export declare function requestedGrainFromPlan(plan: AnalysisQuestionPlan): RequestedGrain;
|
|
54
|
+
/**
|
|
55
|
+
* Compare a certified block's declared grain / outputs / entities against the
|
|
56
|
+
* question's requested grain. Returns whether Tier-1 is still allowed and a
|
|
57
|
+
* structured reason.
|
|
58
|
+
*/
|
|
59
|
+
export declare function grainMatches(block: MetadataObject, requested: RequestedGrain): GrainGateResult;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=grain-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grain-gate.d.ts","sourceRoot":"","sources":["../../src/metadata/grain-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,+EAA+E;AAC/E,QAAA,MAAM,gBAAgB,8DAA+D,CAAC;AACtF,KAAK,SAAS,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAsCnD,MAAM,MAAM,aAAa,GACrB,oBAAoB,GACpB,qBAAqB,GACrB,OAAO,GACP,mBAAmB,GACnB,UAAU,CAAC;AAEf,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;IACpB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CAiBjF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,cAAc,GACxB,eAAe,CAqFjB"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grain / contract gate for certified-block routing.
|
|
3
|
+
*
|
|
4
|
+
* Retrieval is keyword-based (FTS5/BM25), so the router can surface a certified
|
|
5
|
+
* block that is *close* to the question but answers a DIFFERENT grain than what
|
|
6
|
+
* was asked (e.g. an account-grain block for a region-grain question). Serving
|
|
7
|
+
* that as "Certified" is worse than refusing — it is a confidently wrong governed
|
|
8
|
+
* answer.
|
|
9
|
+
*
|
|
10
|
+
* Blocks already carry `grain`, `entities`, and `declaredOutputs`; this module
|
|
11
|
+
* uses them as a hard gate. Before a Tier-1 certified route is served, the
|
|
12
|
+
* question's requested grain/entity is compared with the candidate block's
|
|
13
|
+
* declared grain. Exact or compatible (coarser-rollup) grains pass; a genuine
|
|
14
|
+
* grain/entity mismatch demotes the answer to Tier-2 (generated, block-as-context).
|
|
15
|
+
*
|
|
16
|
+
* The gate is deliberately CONSERVATIVE: when the question has no clearly
|
|
17
|
+
* extractable grain, or the block declares no grain, it does NOT demote — it
|
|
18
|
+
* must not regress today's behavior or over-refuse true matches.
|
|
19
|
+
*/
|
|
20
|
+
/** Coarsest-to-finest time grain ordering. Lower index = finer granularity. */
|
|
21
|
+
const TIME_GRAIN_ORDER = ['hour', 'day', 'week', 'month', 'quarter', 'year'];
|
|
22
|
+
/**
|
|
23
|
+
* Maps surface words (singular, after light normalization) to a canonical time
|
|
24
|
+
* grain. Both question terms and block grain tokens are run through this.
|
|
25
|
+
*/
|
|
26
|
+
const TIME_GRAIN_SYNONYMS = {
|
|
27
|
+
hour: 'hour', hourly: 'hour',
|
|
28
|
+
day: 'day', daily: 'day', date: 'day', dt: 'day',
|
|
29
|
+
week: 'week', weekly: 'week', wk: 'week',
|
|
30
|
+
month: 'month', monthly: 'month', mo: 'month', mtd: 'month',
|
|
31
|
+
quarter: 'quarter', quarterly: 'quarter', qtr: 'quarter', qtd: 'quarter',
|
|
32
|
+
year: 'year', yearly: 'year', annual: 'year', annually: 'year', yr: 'year', ytd: 'year',
|
|
33
|
+
season: 'year',
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Known entity-grain synonym groups. Each group's members are treated as the
|
|
37
|
+
* same entity grain. This is intentionally small and high-confidence — unknown
|
|
38
|
+
* tokens fall back to literal comparison, never a forced match.
|
|
39
|
+
*/
|
|
40
|
+
const ENTITY_GRAIN_GROUPS = [
|
|
41
|
+
['account', 'accounts'],
|
|
42
|
+
['customer', 'customers', 'client', 'clients'],
|
|
43
|
+
['user', 'users', 'member', 'members'],
|
|
44
|
+
['region', 'regions', 'geo', 'market', 'territory'],
|
|
45
|
+
['product', 'products', 'sku', 'item', 'items'],
|
|
46
|
+
['order', 'orders'],
|
|
47
|
+
['player', 'players', 'athlete', 'athletes'],
|
|
48
|
+
['team', 'teams'],
|
|
49
|
+
['vendor', 'vendors', 'supplier', 'suppliers', 'merchant', 'merchants'],
|
|
50
|
+
['segment', 'segments', 'cohort', 'cohorts'],
|
|
51
|
+
['channel', 'channels'],
|
|
52
|
+
['department', 'departments', 'team'],
|
|
53
|
+
['country', 'countries', 'nation'],
|
|
54
|
+
['store', 'stores', 'location', 'locations'],
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Extract the question's requested grain from the already-computed analysis
|
|
58
|
+
* plan. Conservative by design: we only treat a token as a requested grain when
|
|
59
|
+
* it is a recognized dimension/time word. Free-form named entities (e.g. a
|
|
60
|
+
* person's name used as a filter value) are NOT a grain request.
|
|
61
|
+
*/
|
|
62
|
+
export function requestedGrainFromPlan(plan) {
|
|
63
|
+
const time = extractTimeGrain([
|
|
64
|
+
...plan.timeTerms,
|
|
65
|
+
...plan.dimensionTerms,
|
|
66
|
+
plan.normalizedQuestion,
|
|
67
|
+
]);
|
|
68
|
+
const entityTokens = new Set();
|
|
69
|
+
for (const term of [...plan.dimensionTerms, ...plan.metricTerms]) {
|
|
70
|
+
for (const token of grainTokens(term)) {
|
|
71
|
+
const canonical = canonicalEntityToken(token);
|
|
72
|
+
if (canonical)
|
|
73
|
+
entityTokens.add(canonical);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const entities = [...entityTokens];
|
|
77
|
+
return { time, entities, empty: !time && entities.length === 0 };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Compare a certified block's declared grain / outputs / entities against the
|
|
81
|
+
* question's requested grain. Returns whether Tier-1 is still allowed and a
|
|
82
|
+
* structured reason.
|
|
83
|
+
*/
|
|
84
|
+
export function grainMatches(block, requested) {
|
|
85
|
+
const requestedGrainLabel = describeRequestedGrain(requested);
|
|
86
|
+
// No-grain-no-demote: if the question carries no clearly-extractable grain,
|
|
87
|
+
// never demote — preserve current behavior.
|
|
88
|
+
if (requested.empty) {
|
|
89
|
+
return {
|
|
90
|
+
allow: true,
|
|
91
|
+
kind: 'no_requested_grain',
|
|
92
|
+
requestedGrainLabel,
|
|
93
|
+
blockGrainLabel: describeBlockGrain(block),
|
|
94
|
+
reason: 'question has no clearly-extractable grain; grain gate does not demote',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const blockGrain = blockGrainProfile(block);
|
|
98
|
+
const blockGrainLabel = describeBlockGrain(block);
|
|
99
|
+
// Backward compatible: a block with no declared grain/outputs/entities cannot
|
|
100
|
+
// be gated — we have nothing to compare against, so allow (don't over-refuse).
|
|
101
|
+
if (!blockGrain.time && blockGrain.entities.length === 0) {
|
|
102
|
+
return {
|
|
103
|
+
allow: true,
|
|
104
|
+
kind: 'block_grain_unknown',
|
|
105
|
+
requestedGrainLabel,
|
|
106
|
+
blockGrainLabel,
|
|
107
|
+
reason: 'block declares no grain/outputs/entities to gate against; allowing Tier 1',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ---- Time grain check (only when the question requests a time grain) ----
|
|
111
|
+
if (requested.time) {
|
|
112
|
+
if (blockGrain.time) {
|
|
113
|
+
if (blockGrain.time === requested.time) {
|
|
114
|
+
// exact time match; fall through to entity check
|
|
115
|
+
}
|
|
116
|
+
else if (timeGrainIndex(blockGrain.time) < timeGrainIndex(requested.time)) {
|
|
117
|
+
// Block grain is FINER than requested (e.g. daily block, weekly
|
|
118
|
+
// question) → it can roll up. Compatible.
|
|
119
|
+
return {
|
|
120
|
+
allow: true,
|
|
121
|
+
kind: 'compatible_rollup',
|
|
122
|
+
requestedGrainLabel,
|
|
123
|
+
blockGrainLabel,
|
|
124
|
+
reason: `block grain=${blockGrain.time} is finer than requested grain=${requested.time} → safe roll-up`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Block grain is COARSER than requested (e.g. monthly block, daily
|
|
129
|
+
// question) → cannot answer the finer question. Mismatch.
|
|
130
|
+
return {
|
|
131
|
+
allow: false,
|
|
132
|
+
kind: 'mismatch',
|
|
133
|
+
requestedGrainLabel,
|
|
134
|
+
blockGrainLabel,
|
|
135
|
+
reason: `certified block grain=${blockGrain.time} ≠ requested grain=${requested.time} → Tier 2`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Block declares no time grain but does declare an entity grain that the
|
|
140
|
+
// question does not request as a dimension → that is itself a likely
|
|
141
|
+
// entity mismatch, handled below.
|
|
142
|
+
}
|
|
143
|
+
// ---- Entity / dimension grain check ----
|
|
144
|
+
if (requested.entities.length > 0 && blockGrain.entities.length > 0) {
|
|
145
|
+
const overlap = requested.entities.some((entity) => blockGrain.entities.includes(entity));
|
|
146
|
+
if (!overlap) {
|
|
147
|
+
const reqEntity = requested.entities[0];
|
|
148
|
+
const blockEntity = blockGrain.entities[0];
|
|
149
|
+
return {
|
|
150
|
+
allow: false,
|
|
151
|
+
kind: 'mismatch',
|
|
152
|
+
requestedGrainLabel,
|
|
153
|
+
blockGrainLabel,
|
|
154
|
+
reason: `certified block grain=${blockEntity} ≠ requested grain=${reqEntity} → Tier 2`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
allow: true,
|
|
160
|
+
kind: 'exact',
|
|
161
|
+
requestedGrainLabel,
|
|
162
|
+
blockGrainLabel,
|
|
163
|
+
reason: 'certified block grain satisfies the requested grain',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build the block's declared grain profile from `grain`, `declaredOutputs`, and
|
|
168
|
+
* `entities` (read from the indexed payload).
|
|
169
|
+
*/
|
|
170
|
+
function blockGrainProfile(block) {
|
|
171
|
+
const payload = block.payload ?? {};
|
|
172
|
+
const grainStr = typeof payload.grain === 'string' ? payload.grain : '';
|
|
173
|
+
const declaredOutputs = stringArray(payload.declaredOutputs);
|
|
174
|
+
const entities = stringArray(payload.entities);
|
|
175
|
+
const time = extractTimeGrain([grainStr, ...declaredOutputs]);
|
|
176
|
+
const entityTokens = new Set();
|
|
177
|
+
// The declared `grain` string is the strongest signal (e.g. "account_id",
|
|
178
|
+
// "region", "customer_id"). Tokenize it and canonicalize known entity words.
|
|
179
|
+
for (const token of grainTokens(grainStr)) {
|
|
180
|
+
const canonical = canonicalEntityToken(token);
|
|
181
|
+
if (canonical)
|
|
182
|
+
entityTokens.add(canonical);
|
|
183
|
+
}
|
|
184
|
+
// Declared business entities are also an entity-grain signal.
|
|
185
|
+
for (const entity of entities) {
|
|
186
|
+
for (const token of grainTokens(entity)) {
|
|
187
|
+
const canonical = canonicalEntityToken(token);
|
|
188
|
+
if (canonical)
|
|
189
|
+
entityTokens.add(canonical);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Output columns named like `<entity>_id` are a strong row-grain signal.
|
|
193
|
+
for (const output of declaredOutputs) {
|
|
194
|
+
for (const token of grainTokens(output)) {
|
|
195
|
+
const canonical = canonicalEntityToken(token);
|
|
196
|
+
if (canonical)
|
|
197
|
+
entityTokens.add(canonical);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { time, entities: [...entityTokens] };
|
|
201
|
+
}
|
|
202
|
+
function extractTimeGrain(values) {
|
|
203
|
+
for (const value of values) {
|
|
204
|
+
for (const token of grainTokens(value)) {
|
|
205
|
+
const grain = TIME_GRAIN_SYNONYMS[token];
|
|
206
|
+
if (grain)
|
|
207
|
+
return grain;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Split a value into normalized grain tokens. Strips a trailing `_id`/`id`
|
|
214
|
+
* suffix so `account_id` and `account` compare equal, and lightly singularizes.
|
|
215
|
+
*/
|
|
216
|
+
function grainTokens(value) {
|
|
217
|
+
if (!value)
|
|
218
|
+
return [];
|
|
219
|
+
return value
|
|
220
|
+
.toLowerCase()
|
|
221
|
+
.replace(/[_\-./]+/g, ' ')
|
|
222
|
+
.replace(/[^a-z0-9 ]+/g, ' ')
|
|
223
|
+
.split(/\s+/)
|
|
224
|
+
.map((token) => token.trim())
|
|
225
|
+
.filter((token) => token.length >= 2 && token !== 'id')
|
|
226
|
+
.map((token) => (token.endsWith('id') && token.length > 3 ? token.slice(0, -2) : token))
|
|
227
|
+
.map(singularize);
|
|
228
|
+
}
|
|
229
|
+
function singularize(token) {
|
|
230
|
+
if (token.endsWith('ies') && token.length > 4)
|
|
231
|
+
return `${token.slice(0, -3)}y`;
|
|
232
|
+
if (token.endsWith('ses') && token.length > 4)
|
|
233
|
+
return token.slice(0, -2);
|
|
234
|
+
if (token.endsWith('s') && token.length > 3)
|
|
235
|
+
return token.slice(0, -1);
|
|
236
|
+
return token;
|
|
237
|
+
}
|
|
238
|
+
/** Canonicalize a token to its entity-grain group representative, if known. */
|
|
239
|
+
function canonicalEntityToken(token) {
|
|
240
|
+
const normalized = singularize(token);
|
|
241
|
+
for (const group of ENTITY_GRAIN_GROUPS) {
|
|
242
|
+
if (group.map(singularize).includes(normalized))
|
|
243
|
+
return singularize(group[0]);
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
function timeGrainIndex(grain) {
|
|
248
|
+
return TIME_GRAIN_ORDER.indexOf(grain);
|
|
249
|
+
}
|
|
250
|
+
function stringArray(value) {
|
|
251
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
252
|
+
}
|
|
253
|
+
function describeRequestedGrain(requested) {
|
|
254
|
+
const parts = [requested.time, ...requested.entities].filter(Boolean);
|
|
255
|
+
return parts.length ? parts.join('+') : 'none';
|
|
256
|
+
}
|
|
257
|
+
function describeBlockGrain(block) {
|
|
258
|
+
const profile = blockGrainProfile(block);
|
|
259
|
+
const declared = typeof block.payload?.grain === 'string' ? block.payload.grain : '';
|
|
260
|
+
if (declared)
|
|
261
|
+
return declared;
|
|
262
|
+
const parts = [profile.time, ...profile.entities].filter(Boolean);
|
|
263
|
+
return parts.length ? parts.join('+') : 'unknown';
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=grain-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grain-gate.js","sourceRoot":"","sources":["../../src/metadata/grain-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
export interface ResolveOwnerOptions {
|
|
21
|
+
/**
|
|
22
|
+
* When provided, this owner wins over any persisted/derived value (the human
|
|
23
|
+
* passed `--owner` / `body.owner`). Empty / whitespace is treated as absent.
|
|
24
|
+
*/
|
|
25
|
+
explicit?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Persist the resolved owner so subsequent calls (and other tools) reuse it.
|
|
28
|
+
* Defaults to true. Set false for pure reads (e.g. a preview that must write
|
|
29
|
+
* nothing).
|
|
30
|
+
*/
|
|
31
|
+
persist?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Read a persisted owner from `dql.config.json` `identity.owner`, else `.dql/local/owner`. */
|
|
34
|
+
export declare function readPersistedOwner(projectRoot: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Persist the owner into `dql.config.json` `identity.owner` when the config
|
|
37
|
+
* exists; otherwise cache it under `.dql/local/owner`. Best-effort — a failure to
|
|
38
|
+
* persist never blocks the caller (the owner is still returned).
|
|
39
|
+
*/
|
|
40
|
+
export declare function persistOwner(projectRoot: string, owner: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the default local OSS owner. Always returns a non-empty string. When
|
|
43
|
+
* an explicit owner is given it wins (and is persisted); otherwise the first
|
|
44
|
+
* persisted/derived value is used and persisted for reuse.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveLocalOwner(projectRoot: string, options?: ResolveOwnerOptions): string;
|
|
47
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/metadata/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAQD,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsB1E;AAiCD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA0BrE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAehG"}
|