@contractspec/lib.product-intent-utils 1.57.0 → 1.58.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 (42) hide show
  1. package/dist/browser/index.js +1592 -0
  2. package/dist/impact-engine.d.ts +10 -14
  3. package/dist/impact-engine.d.ts.map +1 -1
  4. package/dist/index.d.ts +9 -9
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +1593 -10
  7. package/dist/node/index.js +1592 -0
  8. package/dist/project-management-sync.d.ts +17 -21
  9. package/dist/project-management-sync.d.ts.map +1 -1
  10. package/dist/prompts.d.ts +41 -45
  11. package/dist/prompts.d.ts.map +1 -1
  12. package/dist/ticket-pipeline-runner.d.ts +22 -25
  13. package/dist/ticket-pipeline-runner.d.ts.map +1 -1
  14. package/dist/ticket-pipeline.d.ts +29 -32
  15. package/dist/ticket-pipeline.d.ts.map +1 -1
  16. package/dist/ticket-prompts.d.ts +13 -16
  17. package/dist/ticket-prompts.d.ts.map +1 -1
  18. package/dist/ticket-validators.d.ts +4 -8
  19. package/dist/ticket-validators.d.ts.map +1 -1
  20. package/dist/validators.d.ts +24 -28
  21. package/dist/validators.d.ts.map +1 -1
  22. package/dist/validators.test.d.ts +2 -0
  23. package/dist/validators.test.d.ts.map +1 -0
  24. package/package.json +20 -15
  25. package/dist/impact-engine.js +0 -168
  26. package/dist/impact-engine.js.map +0 -1
  27. package/dist/project-management-sync.js +0 -80
  28. package/dist/project-management-sync.js.map +0 -1
  29. package/dist/prompts.js +0 -372
  30. package/dist/prompts.js.map +0 -1
  31. package/dist/schema/dist/SchemaModelType.d.ts +0 -50
  32. package/dist/schema/dist/SchemaModelType.d.ts.map +0 -1
  33. package/dist/ticket-pipeline-runner.js +0 -97
  34. package/dist/ticket-pipeline-runner.js.map +0 -1
  35. package/dist/ticket-pipeline.js +0 -425
  36. package/dist/ticket-pipeline.js.map +0 -1
  37. package/dist/ticket-prompts.js +0 -131
  38. package/dist/ticket-prompts.js.map +0 -1
  39. package/dist/ticket-validators.js +0 -106
  40. package/dist/ticket-validators.js.map +0 -1
  41. package/dist/validators.js +0 -277
  42. package/dist/validators.js.map +0 -1
@@ -1,50 +0,0 @@
1
- import * as z$1 from "zod";
2
-
3
- //#region ../schema/dist/SchemaModelType.d.ts
4
- /**
5
- * Unified interface for all schema-compatible types.
6
- * Any type used in ContractSpec schemas must implement this interface.
7
- *
8
- * @template T - The TypeScript type this schema represents
9
- *
10
- * @example
11
- * ```typescript
12
- * // All of these implement SchemaType:
13
- * const fieldType = ScalarTypeEnum.String_unsecure();
14
- * const schemaModel = new SchemaModel({ name: 'User', fields: {...} });
15
- * const zodWrapper = fromZod(z.object({ name: z.string() }));
16
- * ```
17
- */
18
- interface SchemaModelType<T = unknown> {
19
- /**
20
- * Return the Zod schema for runtime validation.
21
- * This is the primary method - all schema types must provide Zod conversion.
22
- */
23
- getZod(): z$1.ZodType<T>;
24
- /**
25
- * Return GraphQL type info for Pothos/GraphQL integration.
26
- * Optional - types without GraphQL representation return undefined.
27
- */
28
- getPothos?(): unknown;
29
- /**
30
- * Return JSON Schema representation.
31
- * Optional - types without JSON Schema representation return undefined.
32
- */
33
- getJsonSchema?(): unknown;
34
- }
35
- /**
36
- * Type guard to check if a value implements the SchemaType interface.
37
- *
38
- * @param value - Value to check
39
- * @returns True if value has a getZod method
40
- *
41
- * @example
42
- * ```typescript
43
- * if (isSchemaType(field.type)) {
44
- * const zodSchema = field.type.getZod();
45
- * }
46
- * ```
47
- */
48
- //#endregion
49
- export { SchemaModelType };
50
- //# sourceMappingURL=SchemaModelType.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SchemaModelType.d.ts","names":["z$1","SchemaFormat","SchemaModelType","T","ZodType","getZod","getPothos","getJsonSchema","isSchemaType","value","AnySchemaType"],"sources":["../../../../schema/dist/SchemaModelType.d.ts"],"mappings":";;;;;;;;;;;;;;;;;UAqBUE,eAAAA;;;;;EAKRG,MAAAA,IAAUL,GAAAA,CAAII,OAAAA,CAAQD,CAAAA;;;;;EAKtBG,SAAAA;;;;;EAKAC,aAAAA;AAAAA"}
@@ -1,97 +0,0 @@
1
- import { buildRepairPromptWithOutput } from "./validators.js";
2
-
3
- //#region src/ticket-pipeline-runner.ts
4
- const DEFAULT_MAX_ATTEMPTS = 2;
5
- function timestamp() {
6
- return (/* @__PURE__ */ new Date()).toISOString();
7
- }
8
- function toErrorMessage(error) {
9
- return error instanceof Error ? error.message : String(error);
10
- }
11
- async function safeLog(logger, entry) {
12
- if (!logger) return;
13
- try {
14
- await logger.log(entry);
15
- } catch {}
16
- }
17
- async function runWithValidation(options) {
18
- const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
19
- let attempt = 0;
20
- let lastError;
21
- let lastRaw = "";
22
- let currentPrompt = options.prompt;
23
- while (attempt < maxAttempts) {
24
- attempt += 1;
25
- await safeLog(options.logger, {
26
- stage: options.stage,
27
- phase: "request",
28
- attempt,
29
- prompt: currentPrompt,
30
- timestamp: timestamp()
31
- });
32
- let raw;
33
- try {
34
- raw = await options.modelRunner.generateJson(currentPrompt);
35
- } catch (error) {
36
- lastError = toErrorMessage(error);
37
- await safeLog(options.logger, {
38
- stage: options.stage,
39
- phase: "model_error",
40
- attempt,
41
- prompt: currentPrompt,
42
- error: lastError,
43
- timestamp: timestamp()
44
- });
45
- throw new Error(`[${options.stage}] Model error: ${lastError}`);
46
- }
47
- await safeLog(options.logger, {
48
- stage: options.stage,
49
- phase: "response",
50
- attempt,
51
- prompt: currentPrompt,
52
- response: raw,
53
- timestamp: timestamp()
54
- });
55
- try {
56
- return options.validate(raw);
57
- } catch (error) {
58
- lastError = toErrorMessage(error);
59
- lastRaw = raw;
60
- if (options.repair) {
61
- const repaired = options.repair(raw, lastError);
62
- if (repaired && repaired !== raw) {
63
- await safeLog(options.logger, {
64
- stage: options.stage,
65
- phase: "repair",
66
- attempt,
67
- prompt: currentPrompt,
68
- response: repaired,
69
- error: lastError,
70
- timestamp: timestamp()
71
- });
72
- try {
73
- return options.validate(repaired);
74
- } catch (repairError) {
75
- lastError = toErrorMessage(repairError);
76
- lastRaw = repaired;
77
- }
78
- }
79
- }
80
- await safeLog(options.logger, {
81
- stage: options.stage,
82
- phase: "validation_error",
83
- attempt,
84
- prompt: currentPrompt,
85
- response: lastRaw,
86
- error: lastError,
87
- timestamp: timestamp()
88
- });
89
- currentPrompt = [options.prompt, buildRepairPromptWithOutput(lastError, lastRaw)].join("\n\n");
90
- }
91
- }
92
- throw new Error(`[${options.stage}] Validation failed after ${maxAttempts} attempt(s): ${lastError ?? "unknown error"}`);
93
- }
94
-
95
- //#endregion
96
- export { runWithValidation };
97
- //# sourceMappingURL=ticket-pipeline-runner.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ticket-pipeline-runner.js","names":[],"sources":["../src/ticket-pipeline-runner.ts"],"sourcesContent":["import { buildRepairPromptWithOutput } from './validators';\n\nexport interface TicketPipelineModelRunner {\n generateJson: (prompt: string) => Promise<string>;\n}\n\nexport type TicketPipelineStage =\n | 'extractEvidence'\n | 'groupProblems'\n | 'generateTickets'\n | 'suggestPatch';\n\nexport interface TicketPipelineLogEntry {\n stage: TicketPipelineStage;\n phase: 'request' | 'response' | 'validation_error' | 'model_error' | 'repair';\n attempt: number;\n prompt: string;\n response?: string;\n error?: string;\n timestamp: string;\n}\n\nexport interface TicketPipelineLogger {\n log: (entry: TicketPipelineLogEntry) => void | Promise<void>;\n}\n\nexport interface TicketPipelineRunOptions<T> {\n stage: TicketPipelineStage;\n prompt: string;\n modelRunner: TicketPipelineModelRunner;\n validate: (raw: string) => T;\n logger?: TicketPipelineLogger;\n maxAttempts?: number;\n repair?: (raw: string, error: string) => string | null;\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 2;\n\nfunction timestamp(): string {\n return new Date().toISOString();\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function safeLog(\n logger: TicketPipelineLogger | undefined,\n entry: TicketPipelineLogEntry\n): Promise<void> {\n if (!logger) return;\n try {\n await logger.log(entry);\n } catch {\n // Ignore logging failures.\n }\n}\n\nexport async function runWithValidation<T>(\n options: TicketPipelineRunOptions<T>\n): Promise<T> {\n const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);\n let attempt = 0;\n let lastError: string | undefined;\n let lastRaw = '';\n let currentPrompt = options.prompt;\n\n while (attempt < maxAttempts) {\n attempt += 1;\n await safeLog(options.logger, {\n stage: options.stage,\n phase: 'request',\n attempt,\n prompt: currentPrompt,\n timestamp: timestamp(),\n });\n\n let raw: string;\n try {\n raw = await options.modelRunner.generateJson(currentPrompt);\n } catch (error) {\n lastError = toErrorMessage(error);\n await safeLog(options.logger, {\n stage: options.stage,\n phase: 'model_error',\n attempt,\n prompt: currentPrompt,\n error: lastError,\n timestamp: timestamp(),\n });\n throw new Error(`[${options.stage}] Model error: ${lastError}`);\n }\n\n await safeLog(options.logger, {\n stage: options.stage,\n phase: 'response',\n attempt,\n prompt: currentPrompt,\n response: raw,\n timestamp: timestamp(),\n });\n\n try {\n return options.validate(raw);\n } catch (error) {\n lastError = toErrorMessage(error);\n lastRaw = raw;\n\n if (options.repair) {\n const repaired = options.repair(raw, lastError);\n if (repaired && repaired !== raw) {\n await safeLog(options.logger, {\n stage: options.stage,\n phase: 'repair',\n attempt,\n prompt: currentPrompt,\n response: repaired,\n error: lastError,\n timestamp: timestamp(),\n });\n try {\n return options.validate(repaired);\n } catch (repairError) {\n lastError = toErrorMessage(repairError);\n lastRaw = repaired;\n }\n }\n }\n\n await safeLog(options.logger, {\n stage: options.stage,\n phase: 'validation_error',\n attempt,\n prompt: currentPrompt,\n response: lastRaw,\n error: lastError,\n timestamp: timestamp(),\n });\n\n currentPrompt = [\n options.prompt,\n buildRepairPromptWithOutput(lastError, lastRaw),\n ].join('\\n\\n');\n }\n }\n\n throw new Error(\n `[${options.stage}] Validation failed after ${maxAttempts} attempt(s): ${\n lastError ?? 'unknown error'\n }`\n );\n}\n"],"mappings":";;;AAoCA,MAAM,uBAAuB;AAE7B,SAAS,YAAoB;AAC3B,yBAAO,IAAI,MAAM,EAAC,aAAa;;AAGjC,SAAS,eAAe,OAAwB;AAC9C,QAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;;AAG/D,eAAe,QACb,QACA,OACe;AACf,KAAI,CAAC,OAAQ;AACb,KAAI;AACF,QAAM,OAAO,IAAI,MAAM;SACjB;;AAKV,eAAsB,kBACpB,SACY;CACZ,MAAM,cAAc,KAAK,IAAI,GAAG,QAAQ,eAAe,qBAAqB;CAC5E,IAAI,UAAU;CACd,IAAI;CACJ,IAAI,UAAU;CACd,IAAI,gBAAgB,QAAQ;AAE5B,QAAO,UAAU,aAAa;AAC5B,aAAW;AACX,QAAM,QAAQ,QAAQ,QAAQ;GAC5B,OAAO,QAAQ;GACf,OAAO;GACP;GACA,QAAQ;GACR,WAAW,WAAW;GACvB,CAAC;EAEF,IAAI;AACJ,MAAI;AACF,SAAM,MAAM,QAAQ,YAAY,aAAa,cAAc;WACpD,OAAO;AACd,eAAY,eAAe,MAAM;AACjC,SAAM,QAAQ,QAAQ,QAAQ;IAC5B,OAAO,QAAQ;IACf,OAAO;IACP;IACA,QAAQ;IACR,OAAO;IACP,WAAW,WAAW;IACvB,CAAC;AACF,SAAM,IAAI,MAAM,IAAI,QAAQ,MAAM,iBAAiB,YAAY;;AAGjE,QAAM,QAAQ,QAAQ,QAAQ;GAC5B,OAAO,QAAQ;GACf,OAAO;GACP;GACA,QAAQ;GACR,UAAU;GACV,WAAW,WAAW;GACvB,CAAC;AAEF,MAAI;AACF,UAAO,QAAQ,SAAS,IAAI;WACrB,OAAO;AACd,eAAY,eAAe,MAAM;AACjC,aAAU;AAEV,OAAI,QAAQ,QAAQ;IAClB,MAAM,WAAW,QAAQ,OAAO,KAAK,UAAU;AAC/C,QAAI,YAAY,aAAa,KAAK;AAChC,WAAM,QAAQ,QAAQ,QAAQ;MAC5B,OAAO,QAAQ;MACf,OAAO;MACP;MACA,QAAQ;MACR,UAAU;MACV,OAAO;MACP,WAAW,WAAW;MACvB,CAAC;AACF,SAAI;AACF,aAAO,QAAQ,SAAS,SAAS;cAC1B,aAAa;AACpB,kBAAY,eAAe,YAAY;AACvC,gBAAU;;;;AAKhB,SAAM,QAAQ,QAAQ,QAAQ;IAC5B,OAAO,QAAQ;IACf,OAAO;IACP;IACA,QAAQ;IACR,UAAU;IACV,OAAO;IACP,WAAW,WAAW;IACvB,CAAC;AAEF,mBAAgB,CACd,QAAQ,QACR,4BAA4B,WAAW,QAAQ,CAChD,CAAC,KAAK,OAAO;;;AAIlB,OAAM,IAAI,MACR,IAAI,QAAQ,MAAM,4BAA4B,YAAY,eACxD,aAAa,kBAEhB"}
@@ -1,425 +0,0 @@
1
- import { formatEvidenceForModel } from "./prompts.js";
2
- import { promptExtractEvidenceFindings, promptGenerateTickets, promptGroupProblems, promptSuggestPatchIntent } from "./ticket-prompts.js";
3
- import { buildChunkIndex, parseStrictJSON, validatePatchIntent } from "./validators.js";
4
- import { validateEvidenceFindingExtraction, validateProblemGrouping, validateTicketCollection } from "./ticket-validators.js";
5
- import { runWithValidation } from "./ticket-pipeline-runner.js";
6
- import { ContractPatchIntentModel, EvidenceFindingExtractionModel, ProblemGroupingModel, TicketCollectionModel } from "@contractspec/lib.contracts/product-intent/types";
7
-
8
- //#region src/ticket-pipeline.ts
9
- const TAG_HINTS = {
10
- onboarding: [
11
- "onboarding",
12
- "setup",
13
- "activation"
14
- ],
15
- pricing: [
16
- "pricing",
17
- "cost",
18
- "billing"
19
- ],
20
- security: [
21
- "security",
22
- "compliance",
23
- "audit"
24
- ],
25
- support: [
26
- "support",
27
- "ticket",
28
- "helpdesk"
29
- ],
30
- analytics: [
31
- "analytics",
32
- "report",
33
- "dashboard"
34
- ],
35
- performance: [
36
- "slow",
37
- "latency",
38
- "performance"
39
- ],
40
- integrations: [
41
- "integration",
42
- "api",
43
- "webhook"
44
- ]
45
- };
46
- function slugify(value) {
47
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "");
48
- }
49
- function pickQuote(text, maxLen = 220) {
50
- const trimmed = text.trim();
51
- const sentenceEnd = trimmed.search(/[.!?]\s/);
52
- const sentence = sentenceEnd === -1 ? trimmed : trimmed.slice(0, sentenceEnd + 1);
53
- return (sentence.length > maxLen ? sentence.slice(0, maxLen) : sentence).trim();
54
- }
55
- function deriveTags(text) {
56
- const lower = text.toLowerCase();
57
- return Object.entries(TAG_HINTS).filter(([, hints]) => hints.some((hint) => lower.includes(hint))).map(([tag]) => tag).slice(0, 3);
58
- }
59
- function truncateToMax(value, maxChars) {
60
- if (value.length <= maxChars) return value;
61
- if (maxChars <= 3) return value.slice(0, maxChars);
62
- return `${value.slice(0, maxChars - 3).trimEnd()}...`;
63
- }
64
- const QUOTE_HYPHENS = new Set([
65
- "-",
66
- "‐",
67
- "‑",
68
- "‒",
69
- "–",
70
- "—"
71
- ]);
72
- const QUOTE_SINGLE = new Set([
73
- "'",
74
- "’",
75
- "‘"
76
- ]);
77
- const QUOTE_DOUBLE = new Set([
78
- "\"",
79
- "“",
80
- "”"
81
- ]);
82
- function escapeRegex(value) {
83
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
84
- }
85
- function buildLooseQuotePattern(quote) {
86
- let pattern = "";
87
- for (let i = 0; i < quote.length; i += 1) {
88
- const char = quote[i] ?? "";
89
- if (char === "." && quote.slice(i, i + 3) === "...") {
90
- pattern += "(?:\\.\\.\\.|…)";
91
- i += 2;
92
- continue;
93
- }
94
- if (char === "…") {
95
- pattern += "(?:\\.\\.\\.|…)";
96
- continue;
97
- }
98
- if (/\s/.test(char)) {
99
- pattern += "\\s+";
100
- while (i + 1 < quote.length && /\s/.test(quote[i + 1] ?? "")) i += 1;
101
- continue;
102
- }
103
- if (QUOTE_HYPHENS.has(char)) {
104
- pattern += "[-‐‑‒–—]";
105
- continue;
106
- }
107
- if (QUOTE_SINGLE.has(char)) {
108
- pattern += "['‘’]";
109
- continue;
110
- }
111
- if (QUOTE_DOUBLE.has(char)) {
112
- pattern += "[\"“”]";
113
- continue;
114
- }
115
- pattern += escapeRegex(char);
116
- }
117
- return pattern;
118
- }
119
- function findQuoteInChunk(quote, chunkText) {
120
- if (chunkText.includes(quote)) return quote;
121
- const pattern = buildLooseQuotePattern(quote);
122
- return chunkText.match(new RegExp(pattern))?.[0] ?? null;
123
- }
124
- function normalizeForTokens(value) {
125
- return value.replace(/[“”]/g, "\"").replace(/[‘’]/g, "'").replace(/[‐‑‒–—]/g, "-").replace(/\s+/g, " ").trim();
126
- }
127
- function tokenize(value) {
128
- return normalizeForTokens(value).toLowerCase().match(/[a-z0-9]+/g) ?? [];
129
- }
130
- function splitIntoSegments(text) {
131
- const matches = text.match(/[^.!?\n]+[.!?]?/g);
132
- if (!matches) return [text];
133
- return matches.map((segment) => segment.trim()).filter(Boolean);
134
- }
135
- function selectBestQuoteFromChunk(quote, chunkText, maxLen = 240) {
136
- const quoteTokens = tokenize(quote);
137
- if (!quoteTokens.length) return null;
138
- const quoteTokenSet = new Set(quoteTokens);
139
- let best = null;
140
- for (const segment of splitIntoSegments(chunkText)) {
141
- if (!segment) continue;
142
- const segmentTokens = new Set(tokenize(segment));
143
- if (!segmentTokens.size) continue;
144
- let overlap = 0;
145
- for (const token of quoteTokenSet) if (segmentTokens.has(token)) overlap += 1;
146
- if (!overlap) continue;
147
- const score = overlap / quoteTokenSet.size;
148
- if (!best || score > best.score) best = {
149
- segment,
150
- score,
151
- overlap
152
- };
153
- }
154
- if (!best) return null;
155
- if (best.overlap < 2 && quoteTokens.length > 2) return null;
156
- const trimmed = best.segment.trim();
157
- return trimmed.length > maxLen ? trimmed.slice(0, maxLen).trimEnd() : trimmed;
158
- }
159
- function fallbackQuoteFromChunk(chunkText, maxLen = 240) {
160
- const trimmed = chunkText.trim();
161
- if (!trimmed) return null;
162
- return (trimmed.length > maxLen ? trimmed.slice(0, maxLen) : trimmed).trimEnd();
163
- }
164
- function findQuoteAcrossChunks(quote, chunkIndex) {
165
- for (const [chunkId, chunk] of chunkIndex.entries()) {
166
- if (chunk.text.includes(quote)) return {
167
- chunkId,
168
- quote
169
- };
170
- const repaired = findQuoteInChunk(quote, chunk.text);
171
- if (repaired) return {
172
- chunkId,
173
- quote: repaired
174
- };
175
- }
176
- return null;
177
- }
178
- function repairEvidenceFindingExtraction(raw, chunks) {
179
- let data;
180
- try {
181
- data = parseStrictJSON(EvidenceFindingExtractionModel, raw);
182
- } catch {
183
- return null;
184
- }
185
- const chunkIndex = buildChunkIndex(chunks);
186
- let updated = false;
187
- for (const finding of data.findings) for (const citation of finding.citations) {
188
- const chunk = chunkIndex.get(citation.chunkId);
189
- if (chunk) {
190
- if (chunk.text.includes(citation.quote)) continue;
191
- const repaired = findQuoteInChunk(citation.quote, chunk.text);
192
- if (repaired) {
193
- citation.quote = repaired;
194
- updated = true;
195
- continue;
196
- }
197
- }
198
- const other = findQuoteAcrossChunks(citation.quote, chunkIndex);
199
- if (other) {
200
- citation.chunkId = other.chunkId;
201
- citation.quote = other.quote;
202
- updated = true;
203
- continue;
204
- }
205
- if (chunk) {
206
- const best = selectBestQuoteFromChunk(citation.quote, chunk.text);
207
- if (best) {
208
- citation.quote = best;
209
- updated = true;
210
- continue;
211
- }
212
- const fallback = fallbackQuoteFromChunk(chunk.text);
213
- if (fallback) {
214
- citation.quote = fallback;
215
- updated = true;
216
- continue;
217
- }
218
- }
219
- }
220
- return updated ? JSON.stringify(data, null, 2) : null;
221
- }
222
- function repairProblemGrouping(raw) {
223
- let data;
224
- try {
225
- data = parseStrictJSON(ProblemGroupingModel, raw);
226
- } catch {
227
- return null;
228
- }
229
- let updated = false;
230
- for (const problem of data.problems) {
231
- const statement = truncateToMax(problem.statement, 320);
232
- if (statement !== problem.statement) {
233
- problem.statement = statement;
234
- updated = true;
235
- }
236
- }
237
- return updated ? JSON.stringify(data, null, 2) : null;
238
- }
239
- function repairTicketCollection(raw) {
240
- let data;
241
- try {
242
- data = parseStrictJSON(TicketCollectionModel, raw);
243
- } catch {
244
- return null;
245
- }
246
- let updated = false;
247
- for (const ticket of data.tickets) {
248
- const title = truncateToMax(ticket.title, 120);
249
- const summary = truncateToMax(ticket.summary, 320);
250
- if (title !== ticket.title) {
251
- ticket.title = title;
252
- updated = true;
253
- }
254
- if (summary !== ticket.summary) {
255
- ticket.summary = summary;
256
- updated = true;
257
- }
258
- ticket.acceptanceCriteria = ticket.acceptanceCriteria.map((criterion) => {
259
- const next = truncateToMax(criterion, 160);
260
- if (next !== criterion) updated = true;
261
- return next;
262
- });
263
- }
264
- return updated ? JSON.stringify(data, null, 2) : null;
265
- }
266
- function repairPatchIntent(raw) {
267
- let data;
268
- try {
269
- data = parseStrictJSON(ContractPatchIntentModel, raw);
270
- } catch {
271
- return null;
272
- }
273
- let updated = false;
274
- const featureKey = truncateToMax(data.featureKey, 80);
275
- if (featureKey !== data.featureKey) {
276
- data.featureKey = featureKey;
277
- updated = true;
278
- }
279
- data.acceptanceCriteria = data.acceptanceCriteria.map((criterion) => {
280
- const next = truncateToMax(criterion, 140);
281
- if (next !== criterion) updated = true;
282
- return next;
283
- });
284
- return updated ? JSON.stringify(data, null, 2) : null;
285
- }
286
- function retrieveChunks(transcript, question, options = {}) {
287
- const chunkSize = options.chunkSize ?? 800;
288
- const sourceId = options.sourceId ?? slugify(question || "transcript");
289
- const clean = transcript.trim();
290
- const chunks = [];
291
- for (let offset = 0, idx = 0; offset < clean.length; idx += 1) {
292
- const slice = clean.slice(offset, offset + chunkSize);
293
- chunks.push({
294
- chunkId: `${sourceId}#c_${String(idx).padStart(2, "0")}`,
295
- text: slice,
296
- meta: {
297
- sourceId,
298
- ...options.meta
299
- }
300
- });
301
- offset += chunkSize;
302
- }
303
- return chunks;
304
- }
305
- async function extractEvidence(chunks, question, options = {}) {
306
- if (options.modelRunner) return runWithValidation({
307
- stage: "extractEvidence",
308
- prompt: promptExtractEvidenceFindings({
309
- question,
310
- evidenceJSON: formatEvidenceForModel(chunks, 900)
311
- }),
312
- modelRunner: options.modelRunner,
313
- logger: options.logger,
314
- maxAttempts: options.maxAttempts,
315
- repair: (raw) => repairEvidenceFindingExtraction(raw, chunks),
316
- validate: (raw) => validateEvidenceFindingExtraction(raw, chunks).findings
317
- });
318
- const maxFindings = options.maxFindings ?? 12;
319
- const findings = [];
320
- for (const chunk of chunks) {
321
- if (findings.length >= maxFindings) break;
322
- const quote = pickQuote(chunk.text);
323
- findings.push({
324
- findingId: `find_${String(findings.length + 1).padStart(3, "0")}`,
325
- summary: quote.length > 160 ? `${quote.slice(0, 160)}...` : quote,
326
- tags: deriveTags(chunk.text),
327
- citations: [{
328
- chunkId: chunk.chunkId,
329
- quote
330
- }]
331
- });
332
- }
333
- return validateEvidenceFindingExtraction(JSON.stringify({ findings }, null, 2), chunks).findings;
334
- }
335
- async function groupProblems(findings, question, options = {}) {
336
- if (options.modelRunner) return runWithValidation({
337
- stage: "groupProblems",
338
- prompt: promptGroupProblems({
339
- question,
340
- findingsJSON: JSON.stringify({ findings }, null, 2),
341
- findingIds: findings.map((finding) => finding.findingId)
342
- }),
343
- modelRunner: options.modelRunner,
344
- logger: options.logger,
345
- maxAttempts: options.maxAttempts,
346
- repair: (raw) => repairProblemGrouping(raw),
347
- validate: (raw) => validateProblemGrouping(raw, findings).problems
348
- });
349
- const grouped = /* @__PURE__ */ new Map();
350
- for (const finding of findings) {
351
- const tag = finding.tags?.[0] ?? "general";
352
- if (!grouped.has(tag)) grouped.set(tag, []);
353
- grouped.get(tag)?.push(finding);
354
- }
355
- const problems = [];
356
- for (const [tag, items] of grouped.entries()) {
357
- const count = items.length;
358
- const severity = count >= 4 ? "high" : count >= 2 ? "medium" : "low";
359
- const statement = tag === "general" ? "Users report friction that slows adoption." : `Users report ${tag} friction that blocks progress.`;
360
- problems.push({
361
- problemId: `prob_${String(problems.length + 1).padStart(3, "0")}`,
362
- statement,
363
- evidenceIds: items.map((item) => item.findingId),
364
- tags: tag === "general" ? void 0 : [tag],
365
- severity
366
- });
367
- }
368
- return validateProblemGrouping(JSON.stringify({ problems }, null, 2), findings).problems;
369
- }
370
- async function generateTickets(problems, findings, question, options = {}) {
371
- if (options.modelRunner) return runWithValidation({
372
- stage: "generateTickets",
373
- prompt: promptGenerateTickets({
374
- question,
375
- problemsJSON: JSON.stringify({ problems }, null, 2),
376
- findingsJSON: JSON.stringify({ findings }, null, 2)
377
- }),
378
- modelRunner: options.modelRunner,
379
- logger: options.logger,
380
- maxAttempts: options.maxAttempts,
381
- repair: (raw) => repairTicketCollection(raw),
382
- validate: (raw) => validateTicketCollection(raw, findings).tickets
383
- });
384
- const tickets = problems.map((problem, idx) => {
385
- const tag = problem.tags?.[0];
386
- const title = tag ? `Improve ${tag} flow` : "Reduce user friction";
387
- const summary = problem.statement;
388
- return {
389
- ticketId: `t_${String(idx + 1).padStart(3, "0")}`,
390
- title,
391
- summary,
392
- evidenceIds: problem.evidenceIds.slice(0, 4),
393
- acceptanceCriteria: ["Acceptance criteria maps to the evidence findings", "Success metrics are tracked for the change"],
394
- tags: problem.tags,
395
- priority: problem.severity === "high" ? "high" : "medium"
396
- };
397
- });
398
- return validateTicketCollection(JSON.stringify({ tickets }, null, 2), findings).tickets;
399
- }
400
- async function suggestPatch(ticket, options = {}) {
401
- if (options.modelRunner) return runWithValidation({
402
- stage: "suggestPatch",
403
- prompt: promptSuggestPatchIntent({ ticketJSON: JSON.stringify(ticket, null, 2) }),
404
- modelRunner: options.modelRunner,
405
- logger: options.logger,
406
- maxAttempts: options.maxAttempts,
407
- repair: (raw) => repairPatchIntent(raw),
408
- validate: (raw) => validatePatchIntent(raw)
409
- });
410
- const featureKey = slugify(ticket.title) || "product_intent_ticket";
411
- const intent = {
412
- featureKey,
413
- changes: [{
414
- type: "update_operation",
415
- target: `productIntent.${featureKey}`,
416
- detail: ticket.summary
417
- }],
418
- acceptanceCriteria: ticket.acceptanceCriteria
419
- };
420
- return validatePatchIntent(JSON.stringify(intent, null, 2));
421
- }
422
-
423
- //#endregion
424
- export { extractEvidence, generateTickets, groupProblems, retrieveChunks, suggestPatch };
425
- //# sourceMappingURL=ticket-pipeline.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ticket-pipeline.js","names":[],"sources":["../src/ticket-pipeline.ts"],"sourcesContent":["import {\n ContractPatchIntentModel,\n EvidenceFindingExtractionModel,\n ProblemGroupingModel,\n TicketCollectionModel,\n type ContractPatchIntent,\n type EvidenceChunk,\n type EvidenceFinding,\n type EvidenceFindingExtraction,\n type ProblemGrouping,\n type ProblemStatement,\n type Ticket,\n type TicketCollection,\n} from '@contractspec/lib.contracts/product-intent/types';\nimport { formatEvidenceForModel } from './prompts';\nimport {\n promptExtractEvidenceFindings,\n promptGenerateTickets,\n promptGroupProblems,\n promptSuggestPatchIntent,\n} from './ticket-prompts';\nimport {\n validateEvidenceFindingExtraction,\n validateProblemGrouping,\n validateTicketCollection,\n} from './ticket-validators';\nimport {\n buildChunkIndex,\n parseStrictJSON,\n validatePatchIntent,\n} from './validators';\nimport {\n runWithValidation,\n type TicketPipelineLogger,\n type TicketPipelineModelRunner,\n} from './ticket-pipeline-runner';\n\nexport type { TicketPipelineLogger, TicketPipelineModelRunner };\n\nexport interface RetrieveChunksOptions {\n chunkSize?: number;\n sourceId?: string;\n meta?: Record<string, unknown>;\n}\n\nexport interface ExtractEvidenceOptions {\n modelRunner?: TicketPipelineModelRunner;\n maxFindings?: number;\n logger?: TicketPipelineLogger;\n maxAttempts?: number;\n}\n\nexport interface GroupProblemsOptions {\n modelRunner?: TicketPipelineModelRunner;\n logger?: TicketPipelineLogger;\n maxAttempts?: number;\n}\n\nexport interface GenerateTicketsOptions {\n modelRunner?: TicketPipelineModelRunner;\n logger?: TicketPipelineLogger;\n maxAttempts?: number;\n}\n\nexport interface SuggestPatchOptions {\n modelRunner?: TicketPipelineModelRunner;\n logger?: TicketPipelineLogger;\n maxAttempts?: number;\n}\n\nconst TAG_HINTS: Record<string, string[]> = {\n onboarding: ['onboarding', 'setup', 'activation'],\n pricing: ['pricing', 'cost', 'billing'],\n security: ['security', 'compliance', 'audit'],\n support: ['support', 'ticket', 'helpdesk'],\n analytics: ['analytics', 'report', 'dashboard'],\n performance: ['slow', 'latency', 'performance'],\n integrations: ['integration', 'api', 'webhook'],\n};\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)+/g, '');\n}\n\nfunction pickQuote(text: string, maxLen = 220): string {\n const trimmed = text.trim();\n const sentenceEnd = trimmed.search(/[.!?]\\s/);\n const sentence =\n sentenceEnd === -1 ? trimmed : trimmed.slice(0, sentenceEnd + 1);\n const quote = sentence.length > maxLen ? sentence.slice(0, maxLen) : sentence;\n return quote.trim();\n}\n\nfunction deriveTags(text: string): string[] {\n const lower = text.toLowerCase();\n const tags = Object.entries(TAG_HINTS)\n .filter(([, hints]) => hints.some((hint) => lower.includes(hint)))\n .map(([tag]) => tag);\n return tags.slice(0, 3);\n}\n\nfunction truncateToMax(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n if (maxChars <= 3) return value.slice(0, maxChars);\n return `${value.slice(0, maxChars - 3).trimEnd()}...`;\n}\n\nconst QUOTE_HYPHENS = new Set(['-', '‐', '‑', '‒', '–', '—']);\nconst QUOTE_SINGLE = new Set([\"'\", '’', '‘']);\nconst QUOTE_DOUBLE = new Set(['\"', '“', '”']);\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction buildLooseQuotePattern(quote: string): string {\n let pattern = '';\n for (let i = 0; i < quote.length; i += 1) {\n const char = quote[i] ?? '';\n if (char === '.' && quote.slice(i, i + 3) === '...') {\n pattern += '(?:\\\\.\\\\.\\\\.|…)';\n i += 2;\n continue;\n }\n if (char === '…') {\n pattern += '(?:\\\\.\\\\.\\\\.|…)';\n continue;\n }\n if (/\\s/.test(char)) {\n pattern += '\\\\s+';\n while (i + 1 < quote.length && /\\s/.test(quote[i + 1] ?? '')) {\n i += 1;\n }\n continue;\n }\n if (QUOTE_HYPHENS.has(char)) {\n pattern += '[-‐‑‒–—]';\n continue;\n }\n if (QUOTE_SINGLE.has(char)) {\n pattern += \"['‘’]\";\n continue;\n }\n if (QUOTE_DOUBLE.has(char)) {\n pattern += '[\"“”]';\n continue;\n }\n pattern += escapeRegex(char);\n }\n return pattern;\n}\n\nfunction findQuoteInChunk(quote: string, chunkText: string): string | null {\n if (chunkText.includes(quote)) return quote;\n const pattern = buildLooseQuotePattern(quote);\n const match = chunkText.match(new RegExp(pattern));\n return match?.[0] ?? null;\n}\n\nfunction normalizeForTokens(value: string): string {\n return value\n .replace(/[“”]/g, '\"')\n .replace(/[‘’]/g, \"'\")\n .replace(/[‐‑‒–—]/g, '-')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction tokenize(value: string): string[] {\n const normalized = normalizeForTokens(value).toLowerCase();\n return normalized.match(/[a-z0-9]+/g) ?? [];\n}\n\nfunction splitIntoSegments(text: string): string[] {\n const matches = text.match(/[^.!?\\n]+[.!?]?/g);\n if (!matches) return [text];\n return matches.map((segment) => segment.trim()).filter(Boolean);\n}\n\nfunction selectBestQuoteFromChunk(\n quote: string,\n chunkText: string,\n maxLen = 240\n): string | null {\n const quoteTokens = tokenize(quote);\n if (!quoteTokens.length) return null;\n\n const quoteTokenSet = new Set(quoteTokens);\n let best: { segment: string; score: number; overlap: number } | null = null;\n\n for (const segment of splitIntoSegments(chunkText)) {\n if (!segment) continue;\n const segmentTokens = new Set(tokenize(segment));\n if (!segmentTokens.size) continue;\n let overlap = 0;\n for (const token of quoteTokenSet) {\n if (segmentTokens.has(token)) overlap += 1;\n }\n if (!overlap) continue;\n const score = overlap / quoteTokenSet.size;\n if (!best || score > best.score) {\n best = { segment, score, overlap };\n }\n }\n\n if (!best) return null;\n if (best.overlap < 2 && quoteTokens.length > 2) return null;\n const trimmed = best.segment.trim();\n return trimmed.length > maxLen ? trimmed.slice(0, maxLen).trimEnd() : trimmed;\n}\n\nfunction fallbackQuoteFromChunk(\n chunkText: string,\n maxLen = 240\n): string | null {\n const trimmed = chunkText.trim();\n if (!trimmed) return null;\n const slice = trimmed.length > maxLen ? trimmed.slice(0, maxLen) : trimmed;\n return slice.trimEnd();\n}\n\nfunction findQuoteAcrossChunks(\n quote: string,\n chunkIndex: Map<string, EvidenceChunk>\n): { chunkId: string; quote: string } | null {\n for (const [chunkId, chunk] of chunkIndex.entries()) {\n if (chunk.text.includes(quote)) {\n return { chunkId, quote };\n }\n const repaired = findQuoteInChunk(quote, chunk.text);\n if (repaired) {\n return { chunkId, quote: repaired };\n }\n }\n return null;\n}\n\nfunction repairEvidenceFindingExtraction(\n raw: string,\n chunks: EvidenceChunk[]\n): string | null {\n let data: EvidenceFindingExtraction;\n try {\n data = parseStrictJSON<EvidenceFindingExtraction>(\n EvidenceFindingExtractionModel,\n raw\n );\n } catch {\n return null;\n }\n\n const chunkIndex = buildChunkIndex(chunks);\n let updated = false;\n\n for (const finding of data.findings) {\n for (const citation of finding.citations) {\n const chunk = chunkIndex.get(citation.chunkId);\n if (chunk) {\n if (chunk.text.includes(citation.quote)) continue;\n const repaired = findQuoteInChunk(citation.quote, chunk.text);\n if (repaired) {\n citation.quote = repaired;\n updated = true;\n continue;\n }\n }\n\n const other = findQuoteAcrossChunks(citation.quote, chunkIndex);\n if (other) {\n citation.chunkId = other.chunkId;\n citation.quote = other.quote;\n updated = true;\n continue;\n }\n\n if (chunk) {\n const best = selectBestQuoteFromChunk(citation.quote, chunk.text);\n if (best) {\n citation.quote = best;\n updated = true;\n continue;\n }\n const fallback = fallbackQuoteFromChunk(chunk.text);\n if (fallback) {\n citation.quote = fallback;\n updated = true;\n continue;\n }\n }\n }\n }\n\n return updated ? JSON.stringify(data, null, 2) : null;\n}\n\nfunction repairProblemGrouping(raw: string): string | null {\n let data: ProblemGrouping;\n try {\n data = parseStrictJSON<ProblemGrouping>(ProblemGroupingModel, raw);\n } catch {\n return null;\n }\n\n let updated = false;\n for (const problem of data.problems) {\n const statement = truncateToMax(problem.statement, 320);\n if (statement !== problem.statement) {\n problem.statement = statement;\n updated = true;\n }\n }\n\n return updated ? JSON.stringify(data, null, 2) : null;\n}\n\nfunction repairTicketCollection(raw: string): string | null {\n let data: TicketCollection;\n try {\n data = parseStrictJSON<TicketCollection>(TicketCollectionModel, raw);\n } catch {\n return null;\n }\n\n let updated = false;\n for (const ticket of data.tickets) {\n const title = truncateToMax(ticket.title, 120);\n const summary = truncateToMax(ticket.summary, 320);\n if (title !== ticket.title) {\n ticket.title = title;\n updated = true;\n }\n if (summary !== ticket.summary) {\n ticket.summary = summary;\n updated = true;\n }\n ticket.acceptanceCriteria = ticket.acceptanceCriteria.map((criterion) => {\n const next = truncateToMax(criterion, 160);\n if (next !== criterion) updated = true;\n return next;\n });\n }\n\n return updated ? JSON.stringify(data, null, 2) : null;\n}\n\nfunction repairPatchIntent(raw: string): string | null {\n let data: ContractPatchIntent;\n try {\n data = parseStrictJSON<ContractPatchIntent>(ContractPatchIntentModel, raw);\n } catch {\n return null;\n }\n\n let updated = false;\n const featureKey = truncateToMax(data.featureKey, 80);\n if (featureKey !== data.featureKey) {\n data.featureKey = featureKey;\n updated = true;\n }\n data.acceptanceCriteria = data.acceptanceCriteria.map((criterion) => {\n const next = truncateToMax(criterion, 140);\n if (next !== criterion) updated = true;\n return next;\n });\n\n return updated ? JSON.stringify(data, null, 2) : null;\n}\n\nexport function retrieveChunks(\n transcript: string,\n question: string,\n options: RetrieveChunksOptions = {}\n): EvidenceChunk[] {\n const chunkSize = options.chunkSize ?? 800;\n const sourceId = options.sourceId ?? slugify(question || 'transcript');\n const clean = transcript.trim();\n const chunks: EvidenceChunk[] = [];\n\n for (let offset = 0, idx = 0; offset < clean.length; idx += 1) {\n const slice = clean.slice(offset, offset + chunkSize);\n chunks.push({\n chunkId: `${sourceId}#c_${String(idx).padStart(2, '0')}`,\n text: slice,\n meta: { sourceId, ...options.meta },\n });\n offset += chunkSize;\n }\n\n return chunks;\n}\n\nexport async function extractEvidence(\n chunks: EvidenceChunk[],\n question: string,\n options: ExtractEvidenceOptions = {}\n): Promise<EvidenceFinding[]> {\n if (options.modelRunner) {\n const evidenceJSON = formatEvidenceForModel(chunks, 900);\n const prompt = promptExtractEvidenceFindings({ question, evidenceJSON });\n return runWithValidation({\n stage: 'extractEvidence',\n prompt,\n modelRunner: options.modelRunner,\n logger: options.logger,\n maxAttempts: options.maxAttempts,\n repair: (raw) => repairEvidenceFindingExtraction(raw, chunks),\n validate: (raw) =>\n validateEvidenceFindingExtraction(raw, chunks).findings,\n });\n }\n\n const maxFindings = options.maxFindings ?? 12;\n const findings: EvidenceFinding[] = [];\n for (const chunk of chunks) {\n if (findings.length >= maxFindings) break;\n const quote = pickQuote(chunk.text);\n findings.push({\n findingId: `find_${String(findings.length + 1).padStart(3, '0')}`,\n summary: quote.length > 160 ? `${quote.slice(0, 160)}...` : quote,\n tags: deriveTags(chunk.text),\n citations: [{ chunkId: chunk.chunkId, quote }],\n });\n }\n\n const raw = JSON.stringify({ findings }, null, 2);\n return validateEvidenceFindingExtraction(raw, chunks).findings;\n}\n\nexport async function groupProblems(\n findings: EvidenceFinding[],\n question: string,\n options: GroupProblemsOptions = {}\n): Promise<ProblemStatement[]> {\n if (options.modelRunner) {\n const findingsJSON = JSON.stringify({ findings }, null, 2);\n const prompt = promptGroupProblems({\n question,\n findingsJSON,\n findingIds: findings.map((finding) => finding.findingId),\n });\n return runWithValidation({\n stage: 'groupProblems',\n prompt,\n modelRunner: options.modelRunner,\n logger: options.logger,\n maxAttempts: options.maxAttempts,\n repair: (raw) => repairProblemGrouping(raw),\n validate: (raw) => validateProblemGrouping(raw, findings).problems,\n });\n }\n\n const grouped = new Map<string, EvidenceFinding[]>();\n for (const finding of findings) {\n const tag = finding.tags?.[0] ?? 'general';\n if (!grouped.has(tag)) grouped.set(tag, []);\n grouped.get(tag)?.push(finding);\n }\n\n const problems: ProblemStatement[] = [];\n for (const [tag, items] of grouped.entries()) {\n const count = items.length;\n const severity = count >= 4 ? 'high' : count >= 2 ? 'medium' : 'low';\n const statement =\n tag === 'general'\n ? 'Users report friction that slows adoption.'\n : `Users report ${tag} friction that blocks progress.`;\n problems.push({\n problemId: `prob_${String(problems.length + 1).padStart(3, '0')}`,\n statement,\n evidenceIds: items.map((item) => item.findingId),\n tags: tag === 'general' ? undefined : [tag],\n severity,\n });\n }\n\n const raw = JSON.stringify({ problems }, null, 2);\n return validateProblemGrouping(raw, findings).problems;\n}\n\nexport async function generateTickets(\n problems: ProblemStatement[],\n findings: EvidenceFinding[],\n question: string,\n options: GenerateTicketsOptions = {}\n): Promise<Ticket[]> {\n if (options.modelRunner) {\n const problemsJSON = JSON.stringify({ problems }, null, 2);\n const findingsJSON = JSON.stringify({ findings }, null, 2);\n const prompt = promptGenerateTickets({\n question,\n problemsJSON,\n findingsJSON,\n });\n return runWithValidation({\n stage: 'generateTickets',\n prompt,\n modelRunner: options.modelRunner,\n logger: options.logger,\n maxAttempts: options.maxAttempts,\n repair: (raw) => repairTicketCollection(raw),\n validate: (raw) => validateTicketCollection(raw, findings).tickets,\n });\n }\n\n const tickets: Ticket[] = problems.map((problem, idx) => {\n const tag = problem.tags?.[0];\n const title = tag ? `Improve ${tag} flow` : 'Reduce user friction';\n const summary = problem.statement;\n return {\n ticketId: `t_${String(idx + 1).padStart(3, '0')}`,\n title,\n summary,\n evidenceIds: problem.evidenceIds.slice(0, 4),\n acceptanceCriteria: [\n 'Acceptance criteria maps to the evidence findings',\n 'Success metrics are tracked for the change',\n ],\n tags: problem.tags,\n priority: problem.severity === 'high' ? 'high' : 'medium',\n };\n });\n\n const raw = JSON.stringify({ tickets }, null, 2);\n return validateTicketCollection(raw, findings).tickets;\n}\n\nexport async function suggestPatch(\n ticket: Ticket,\n options: SuggestPatchOptions = {}\n): Promise<ContractPatchIntent> {\n if (options.modelRunner) {\n const ticketJSON = JSON.stringify(ticket, null, 2);\n const prompt = promptSuggestPatchIntent({ ticketJSON });\n return runWithValidation({\n stage: 'suggestPatch',\n prompt,\n modelRunner: options.modelRunner,\n logger: options.logger,\n maxAttempts: options.maxAttempts,\n repair: (raw) => repairPatchIntent(raw),\n validate: (raw) => validatePatchIntent(raw),\n });\n }\n\n const featureKey = slugify(ticket.title) || 'product_intent_ticket';\n const intent: ContractPatchIntent = {\n featureKey,\n changes: [\n {\n type: 'update_operation',\n target: `productIntent.${featureKey}`,\n detail: ticket.summary,\n },\n ],\n acceptanceCriteria: ticket.acceptanceCriteria,\n };\n\n return validatePatchIntent(JSON.stringify(intent, null, 2));\n}\n"],"mappings":"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"}