@contractspec/lib.product-intent-utils 3.7.16 → 3.7.18

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.
@@ -1,221 +1,12 @@
1
- // src/impact-engine.ts
2
- var SURFACE_MAP = {
3
- add_field: ["api", "db", "ui", "docs", "tests"],
4
- remove_field: ["api", "db", "ui", "docs", "tests"],
5
- rename_field: ["api", "db", "ui", "docs", "tests"],
6
- add_event: ["api", "workflows", "docs", "tests"],
7
- update_event: ["api", "workflows", "docs", "tests"],
8
- add_operation: ["api", "ui", "workflows", "docs", "tests"],
9
- update_operation: ["api", "ui", "workflows", "docs", "tests"],
10
- update_form: ["ui", "docs", "tests"],
11
- update_policy: ["policy", "api", "workflows", "docs", "tests"],
12
- add_enum_value: ["api", "db", "ui", "docs", "tests"],
13
- remove_enum_value: ["api", "db", "ui", "docs", "tests"],
14
- other: ["docs", "tests"]
15
- };
16
- var BUCKET_MAP = {
17
- remove_field: "breaks",
18
- rename_field: "breaks",
19
- remove_enum_value: "breaks",
20
- update_operation: "mustChange",
21
- update_event: "mustChange",
22
- update_policy: "mustChange",
23
- update_form: "risky",
24
- add_field: "risky",
25
- add_event: "risky",
26
- add_operation: "risky",
27
- add_enum_value: "risky",
28
- other: "risky"
29
- };
30
- function slugify(value) {
31
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "");
32
- }
33
- function buildTokens(change) {
34
- const combined = `${change.type} ${change.target} ${change.detail}`;
35
- const tokens = combined.split(/[^a-zA-Z0-9]+/).map((token) => token.trim()).filter((token) => token.length >= 3);
36
- return Array.from(new Set(tokens.map((token) => token.toLowerCase()))).slice(0, 8);
37
- }
38
- function scanTokens(tokens, files, maxHits) {
39
- const hits = [];
40
- const lowerTokens = tokens.map((token) => token.toLowerCase());
41
- for (const file of files) {
42
- const haystack = file.content.toLowerCase();
43
- if (lowerTokens.some((token) => haystack.includes(token))) {
44
- hits.push(file.path);
45
- }
46
- if (hits.length >= maxHits)
47
- break;
48
- }
49
- return hits;
50
- }
51
- function formatRefs(tokens, repoFiles, maxHits = 3) {
52
- if (!repoFiles || repoFiles.length === 0) {
53
- return "refs: (no repo scan)";
54
- }
55
- const hits = scanTokens(tokens, repoFiles, maxHits);
56
- if (!hits.length)
57
- return "refs: none";
58
- return `refs: ${hits.join(", ")}`;
59
- }
60
- function humanizeChange(change) {
61
- const label = change.type.replace(/_/g, " ");
62
- return `${label} ${change.target}`;
63
- }
64
- function buildStatement(change, refs, surfaces) {
65
- const reason = change.detail || `touches ${surfaces.join(", ")}`;
66
- return `${humanizeChange(change)} because ${reason} (${refs})`;
67
- }
68
- function impactEngine(intent, options = {}) {
69
- const reportId = options.reportId ?? `impact-${slugify(intent.featureKey)}`;
70
- const patchId = options.patchId ?? `patch-${slugify(intent.featureKey)}`;
71
- const maxHitsPerChange = options.maxHitsPerChange ?? 3;
72
- const breaks = [];
73
- const mustChange = [];
74
- const risky = [];
75
- const surfaces = {
76
- api: [],
77
- db: [],
78
- ui: [],
79
- workflows: [],
80
- policy: [],
81
- docs: [],
82
- tests: []
83
- };
84
- for (const change of intent.changes) {
85
- const bucket = BUCKET_MAP[change.type] ?? "risky";
86
- const surfaceTargets = SURFACE_MAP[change.type] ?? ["docs", "tests"];
87
- const tokens = buildTokens(change);
88
- const refs = formatRefs(tokens, options.repoFiles, maxHitsPerChange);
89
- const statement = buildStatement(change, refs, surfaceTargets);
90
- if (bucket === "breaks")
91
- breaks.push(statement);
92
- if (bucket === "mustChange")
93
- mustChange.push(statement);
94
- if (bucket === "risky")
95
- risky.push(statement);
96
- for (const surface of surfaceTargets) {
97
- const list = surfaces[surface];
98
- if (Array.isArray(list)) {
99
- list.push(statement);
100
- }
101
- }
102
- }
103
- const summary = [
104
- `Analyzed ${intent.changes.length} change(s).`,
105
- `Breaks: ${breaks.length}.`,
106
- `Must change: ${mustChange.length}.`,
107
- `Risky: ${risky.length}.`
108
- ].join(" ");
109
- return {
110
- reportId,
111
- patchId,
112
- summary,
113
- breaks,
114
- mustChange,
115
- risky,
116
- surfaces
117
- };
118
- }
119
- // src/project-management-sync.ts
120
- function buildProjectManagementSyncPayload(params) {
121
- const options = params.options ?? {};
122
- const items = buildWorkItemsFromTickets(params.tickets, options);
123
- const summary = options.includeSummary ? buildSummaryWorkItem({
124
- question: params.question,
125
- tickets: params.tickets,
126
- patchIntent: params.patchIntent,
127
- impact: params.impact,
128
- title: options.summaryTitle,
129
- baseTags: options.baseTags
130
- }) : undefined;
131
- return { summary, items };
132
- }
133
- function buildWorkItemsFromTickets(tickets, options = {}) {
134
- return tickets.map((ticket) => ({
135
- title: ticket.title,
136
- description: renderTicketDescription(ticket),
137
- type: "task",
138
- priority: mapPriority(ticket.priority, options.defaultPriority),
139
- tags: mergeTags(options.baseTags, ticket.tags),
140
- externalId: ticket.ticketId
141
- }));
142
- }
143
- function buildSummaryWorkItem(params) {
144
- return {
145
- title: params.title ?? "Product Intent Summary",
146
- description: renderSummaryMarkdown(params),
147
- type: "summary",
148
- tags: mergeTags(params.baseTags, ["product-intent", "summary"])
149
- };
150
- }
151
- function renderTicketDescription(ticket) {
152
- const lines = [
153
- ticket.summary,
154
- "",
155
- "Acceptance Criteria:",
156
- ...ticket.acceptanceCriteria.map((criterion) => `- ${criterion}`)
157
- ];
158
- if (ticket.evidenceIds.length > 0) {
159
- lines.push("", `Evidence: ${ticket.evidenceIds.join(", ")}`);
160
- }
161
- return lines.join(`
162
- `);
163
- }
164
- function renderSummaryMarkdown(params) {
165
- const lines = [`# ${params.question}`, "", "## Top Tickets"];
166
- for (const ticket of params.tickets) {
167
- lines.push(`- ${ticket.title}`);
168
- }
169
- if (params.patchIntent) {
170
- lines.push("", "## Patch Intent", `Feature: ${params.patchIntent.featureKey}`);
171
- params.patchIntent.changes.forEach((change) => {
172
- lines.push(`- ${change.type}: ${change.target}`);
173
- });
174
- }
175
- if (params.impact) {
176
- lines.push("", "## Impact Summary", params.impact.summary);
177
- }
178
- return lines.join(`
179
- `);
180
- }
181
- function mapPriority(priority, fallback) {
182
- if (!priority)
183
- return fallback;
184
- switch (priority) {
185
- case "high":
186
- return "high";
187
- case "medium":
188
- return "medium";
189
- case "low":
190
- return "low";
191
- default:
192
- return fallback;
193
- }
194
- }
195
- function mergeTags(baseTags, tags) {
196
- const merged = new Set;
197
- (baseTags ?? []).forEach((tag) => merged.add(tag));
198
- (tags ?? []).forEach((tag) => merged.add(tag));
199
- const result = [...merged];
200
- return result.length > 0 ? result : undefined;
201
- }
202
- // src/prompts.ts
203
- function formatEvidenceForModel(chunks, maxChars = 900) {
204
- const safe = chunks.map((chunk) => ({
205
- chunkId: chunk.chunkId,
206
- text: chunk.text.length > maxChars ? `${chunk.text.slice(0, maxChars)}...` : chunk.text,
207
- meta: chunk.meta ?? {}
208
- }));
209
- return JSON.stringify({ evidenceChunks: safe }, null, 2);
210
- }
211
- var JSON_ONLY_RULES = `
1
+ var u={add_field:["api","db","ui","docs","tests"],remove_field:["api","db","ui","docs","tests"],rename_field:["api","db","ui","docs","tests"],add_event:["api","workflows","docs","tests"],update_event:["api","workflows","docs","tests"],add_operation:["api","ui","workflows","docs","tests"],update_operation:["api","ui","workflows","docs","tests"],update_form:["ui","docs","tests"],update_policy:["policy","api","workflows","docs","tests"],add_enum_value:["api","db","ui","docs","tests"],remove_enum_value:["api","db","ui","docs","tests"],other:["docs","tests"]},h={remove_field:"breaks",rename_field:"breaks",remove_enum_value:"breaks",update_operation:"mustChange",update_event:"mustChange",update_policy:"mustChange",update_form:"risky",add_field:"risky",add_event:"risky",add_operation:"risky",add_enum_value:"risky",other:"risky"};function Q(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)+/g,"")}function s(e){let c=`${e.type} ${e.target} ${e.detail}`.split(/[^a-zA-Z0-9]+/).map((P)=>P.trim()).filter((P)=>P.length>=3);return Array.from(new Set(c.map((P)=>P.toLowerCase()))).slice(0,8)}function a(e,y,c){let P=[],f=e.map((r)=>r.toLowerCase());for(let r of y){let $=r.content.toLowerCase();if(f.some((_)=>$.includes(_)))P.push(r.path);if(P.length>=c)break}return P}function ee(e,y,c=3){if(!y||y.length===0)return"refs: (no repo scan)";let P=a(e,y,c);if(!P.length)return"refs: none";return`refs: ${P.join(", ")}`}function ce(e){return`${e.type.replace(/_/g," ")} ${e.target}`}function ye(e,y,c){let P=e.detail||`touches ${c.join(", ")}`;return`${ce(e)} because ${P} (${y})`}function qe(e,y={}){let c=y.reportId??`impact-${Q(e.featureKey)}`,P=y.patchId??`patch-${Q(e.featureKey)}`,f=y.maxHitsPerChange??3,r=[],$=[],_=[],m={api:[],db:[],ui:[],workflows:[],policy:[],docs:[],tests:[]};for(let R of e.changes){let g=h[R.type]??"risky",C=u[R.type]??["docs","tests"],b=s(R),L=ee(b,y.repoFiles,f),H=ye(R,L,C);if(g==="breaks")r.push(H);if(g==="mustChange")$.push(H);if(g==="risky")_.push(H);for(let d of C){let G=m[d];if(Array.isArray(G))G.push(H)}}let j=[`Analyzed ${e.changes.length} change(s).`,`Breaks: ${r.length}.`,`Must change: ${$.length}.`,`Risky: ${_.length}.`].join(" ");return{reportId:c,patchId:P,summary:j,breaks:r,mustChange:$,risky:_,surfaces:m}}function ve(e){let y=e.options??{},c=Pe(e.tickets,y);return{summary:y.includeSummary?fe({question:e.question,tickets:e.tickets,patchIntent:e.patchIntent,impact:e.impact,title:y.summaryTitle,baseTags:y.baseTags}):void 0,items:c}}function Pe(e,y={}){return e.map((c)=>({title:c.title,description:re(c),type:"task",priority:_e(c.priority,y.defaultPriority),tags:J(y.baseTags,c.tags),externalId:c.ticketId}))}function fe(e){return{title:e.title??"Product Intent Summary",description:$e(e),type:"summary",tags:J(e.baseTags,["product-intent","summary"])}}function re(e){let y=[e.summary,"","Acceptance Criteria:",...e.acceptanceCriteria.map((c)=>`- ${c}`)];if(e.evidenceIds.length>0)y.push("",`Evidence: ${e.evidenceIds.join(", ")}`);return y.join(`
2
+ `)}function $e(e){let y=[`# ${e.question}`,"","## Top Tickets"];for(let c of e.tickets)y.push(`- ${c.title}`);if(e.patchIntent)y.push("","## Patch Intent",`Feature: ${e.patchIntent.featureKey}`),e.patchIntent.changes.forEach((c)=>{y.push(`- ${c.type}: ${c.target}`)});if(e.impact)y.push("","## Impact Summary",e.impact.summary);return y.join(`
3
+ `)}function _e(e,y){if(!e)return y;switch(e){case"high":return"high";case"medium":return"medium";case"low":return"low";default:return y}}function J(e,y){let c=new Set;(e??[]).forEach((f)=>c.add(f)),(y??[]).forEach((f)=>c.add(f));let P=[...c];return P.length>0?P:void 0}function U(e,y=900){let c=e.map((P)=>({chunkId:P.chunkId,text:P.text.length>y?`${P.text.slice(0,y)}...`:P.text,meta:P.meta??{}}));return JSON.stringify({evidenceChunks:c},null,2)}var t=`
212
4
  You MUST output valid JSON ONLY.
213
5
  - Do not wrap in markdown fences.
214
6
  - Do not include any commentary.
215
7
  - Do not include trailing commas.
216
8
  - Use double quotes for all keys and string values.
217
- `;
218
- var CITATION_RULES = `
9
+ `,z=`
219
10
  CITATION RULES (strict):
220
11
  - You may ONLY cite from the provided evidenceChunks.
221
12
  - Each citation must include:
@@ -224,16 +15,14 @@ CITATION RULES (strict):
224
15
  - Do NOT invent quotes.
225
16
  - Keep quotes short (<= 240 chars).
226
17
  - If you cannot support a claim with evidence, do not make the claim.
227
- `;
228
- function promptExtractInsights(params) {
229
- return `
18
+ `;function Se(e){return`
230
19
  You are extracting ATOMIC, EVIDENCE-GROUNDED insights to answer a product discovery question.
231
20
 
232
21
  Question:
233
- ${params.question}
22
+ ${e.question}
234
23
 
235
24
  Evidence:
236
- ${params.evidenceJSON}
25
+ ${e.evidenceJSON}
237
26
 
238
27
  Task:
239
28
  Return JSON with:
@@ -255,23 +44,19 @@ Guidelines:
255
44
  - Each insight must be supported by 1 to 3 citations.
256
45
  - Prefer user pain, blockers, confusions, workarounds, requests, and measurable outcomes.
257
46
  - If evidence conflicts, include both sides as separate insights.
258
- ${CITATION_RULES}
259
- ${JSON_ONLY_RULES}
260
- `.trim();
261
- }
262
- function promptSynthesizeBrief(params) {
263
- const allowed = JSON.stringify({ allowedChunkIds: params.allowedChunkIds }, null, 2);
264
- return `
47
+ ${z}
48
+ ${t}
49
+ `.trim()}function ke(e){let y=JSON.stringify({allowedChunkIds:e.allowedChunkIds},null,2);return`
265
50
  You are synthesizing a product opportunity brief that is STRICTLY grounded in evidence.
266
51
 
267
52
  Question:
268
- ${params.question}
53
+ ${e.question}
269
54
 
270
55
  Extracted insights (already grounded):
271
- ${params.insightsJSON}
56
+ ${e.insightsJSON}
272
57
 
273
58
  Allowed citations:
274
- ${allowed}
59
+ ${y}
275
60
 
276
61
  Return JSON with exactly this shape:
277
62
  {
@@ -289,19 +74,16 @@ Rules:
289
74
  - The fields problem/who/proposedChange MUST each have >=1 citation.
290
75
  - All citations must use allowedChunkIds and include exact quotes.
291
76
  - Keep the brief concise and specific.
292
- ${CITATION_RULES}
293
- ${JSON_ONLY_RULES}
294
- `.trim();
295
- }
296
- function promptSkepticCheck(params) {
297
- return `
77
+ ${z}
78
+ ${t}
79
+ `.trim()}function be(e){return`
298
80
  You are auditing a brief for unsupported claims and citation misuse.
299
81
 
300
82
  Brief:
301
- ${params.briefJSON}
83
+ ${e.briefJSON}
302
84
 
303
85
  Evidence:
304
- ${params.evidenceJSON}
86
+ ${e.evidenceJSON}
305
87
 
306
88
  Return JSON:
307
89
  {
@@ -317,15 +99,12 @@ Return JSON:
317
99
  Rules:
318
100
  - If everything is supported, return {"issues": []}.
319
101
  - Be strict. If a statement is not clearly supported by citations, flag it.
320
- ${JSON_ONLY_RULES}
321
- `.trim();
322
- }
323
- function promptGeneratePatchIntent(params) {
324
- return `
102
+ ${t}
103
+ `.trim()}function Le(e){return`
325
104
  You are generating a ContractPatchIntent from an OpportunityBrief.
326
105
 
327
106
  OpportunityBrief:
328
- ${params.briefJSON}
107
+ ${e.briefJSON}
329
108
 
330
109
  Return JSON:
331
110
  {
@@ -340,19 +119,16 @@ Rules:
340
119
  - Keep changes <= 12.
341
120
  - Detail should be minimal and explicit.
342
121
  - Acceptance criteria must be testable and verifiable.
343
- ${JSON_ONLY_RULES}
344
- `.trim();
345
- }
346
- function promptGenerateGenericSpecOverlay(params) {
347
- return `
122
+ ${t}
123
+ `.trim()}function de(e){return`
348
124
  You are generating a GENERIC spec overlay patch based on PatchIntent.
349
125
  You must respect the base spec snippet.
350
126
 
351
127
  Base spec snippet (context):
352
- ${params.baseSpecSnippet}
128
+ ${e.baseSpecSnippet}
353
129
 
354
130
  PatchIntent:
355
- ${params.patchIntentJSON}
131
+ ${e.patchIntentJSON}
356
132
 
357
133
  Return JSON:
358
134
  {
@@ -366,21 +142,18 @@ Return JSON:
366
142
  Rules:
367
143
  - Only reference paths that plausibly exist in the base spec snippet or add new ones under reasonable roots.
368
144
  - Keep values small. Avoid massive blobs.
369
- ${JSON_ONLY_RULES}
370
- `.trim();
371
- }
372
- function promptGenerateImpactReport(params) {
373
- return `
145
+ ${t}
146
+ `.trim()}function ue(e){return`
374
147
  You are generating an Impact Report for a spec patch.
375
148
 
376
149
  PatchIntent:
377
- ${params.patchIntentJSON}
150
+ ${e.patchIntentJSON}
378
151
 
379
152
  Overlay:
380
- ${params.overlayJSON}
153
+ ${e.overlayJSON}
381
154
 
382
155
  Compiler output (if present):
383
- ${params.compilerOutputText ?? "(none)"}
156
+ ${e.compilerOutputText??"(none)"}
384
157
 
385
158
  Return JSON:
386
159
  {
@@ -405,24 +178,21 @@ Rules:
405
178
  - Be concrete: name what changes and why.
406
179
  - If unsure, put it under "risky" not "breaks".
407
180
  - Keep each item short.
408
- ${JSON_ONLY_RULES}
409
- `.trim();
410
- }
411
- function promptGenerateTaskPack(params) {
412
- return `
181
+ ${t}
182
+ `.trim()}function he(e){return`
413
183
  You are generating an agent-ready Task Pack to implement a product change safely.
414
184
 
415
185
  Repo context:
416
- ${params.repoContext ?? "(none)"}
186
+ ${e.repoContext??"(none)"}
417
187
 
418
188
  OpportunityBrief:
419
- ${params.briefJSON}
189
+ ${e.briefJSON}
420
190
 
421
191
  PatchIntent:
422
- ${params.patchIntentJSON}
192
+ ${e.patchIntentJSON}
423
193
 
424
194
  Impact report:
425
- ${params.impactJSON}
195
+ ${e.impactJSON}
426
196
 
427
197
  Return JSON:
428
198
  {
@@ -447,12 +217,9 @@ Rules:
447
217
  - Each task must have testable acceptance criteria.
448
218
  - Agent prompts must be copy-paste friendly and mention expected files/surfaces.
449
219
  - Include at least one tests task.
450
- ${JSON_ONLY_RULES}
451
- `.trim();
452
- }
453
- function promptWireframeImage(params) {
454
- return `
455
- Create a minimal grayscale wireframe (${params.device}) for screen: "${params.screenName}".
220
+ ${t}
221
+ `.trim()}function se(e){return`
222
+ Create a minimal grayscale wireframe (${e.device}) for screen: "${e.screenName}".
456
223
 
457
224
  Style rules:
458
225
  - Wireframe only, grayscale, no brand colors, no gradients
@@ -461,22 +228,19 @@ Style rules:
461
228
  - No decorative illustrations
462
229
 
463
230
  Current screen summary:
464
- ${params.currentScreenSummary}
231
+ ${e.currentScreenSummary}
465
232
 
466
233
  Proposed changes (must be reflected in the wireframe):
467
- - ${params.proposedChanges.join(`
234
+ - ${e.proposedChanges.join(`
468
235
  - `)}
469
236
 
470
237
  Output: a single wireframe image that clearly shows the updated layout.
471
- `.trim();
472
- }
473
- function promptWireframeLayoutJSON(params) {
474
- return `
238
+ `.trim()}function ae(e){return`
475
239
  You are generating a simple UI wireframe layout JSON (NOT an image).
476
- Screen: "${params.screenName}" (${params.device})
240
+ Screen: "${e.screenName}" (${e.device})
477
241
 
478
242
  Proposed changes:
479
- - ${params.proposedChanges.join(`
243
+ - ${e.proposedChanges.join(`
480
244
  - `)}
481
245
 
482
246
  Return JSON:
@@ -493,16 +257,13 @@ Rules:
493
257
  - 8 to 18 elements.
494
258
  - Must reflect proposed changes.
495
259
  - Labels should be clear and specific.
496
- ${JSON_ONLY_RULES}
497
- `.trim();
498
- }
499
- function promptGenerateSyntheticInterviews(params) {
500
- return `
501
- Generate ${params.count} synthetic customer interview transcripts for this product context:
502
- ${params.productContext}
260
+ ${t}
261
+ `.trim()}function ec(e){return`
262
+ Generate ${e.count} synthetic customer interview transcripts for this product context:
263
+ ${e.productContext}
503
264
 
504
265
  Personas to cover:
505
- - ${params.personas.join(`
266
+ - ${e.personas.join(`
506
267
  - `)}
507
268
 
508
269
  Requirements:
@@ -519,399 +280,20 @@ Return JSON:
519
280
  ]
520
281
  }
521
282
 
522
- ${JSON_ONLY_RULES}
523
- `.trim();
524
- }
525
- // src/ticket-pipeline.ts
526
- import {
527
- ContractPatchIntentModel as ContractPatchIntentModel2,
528
- EvidenceFindingExtractionModel as EvidenceFindingExtractionModel2,
529
- ProblemGroupingModel as ProblemGroupingModel2,
530
- TicketCollectionModel as TicketCollectionModel2
531
- } from "@contractspec/lib.contracts-spec/product-intent/types";
283
+ ${t}
284
+ `.trim()}import{ContractPatchIntentModel as Ke,EvidenceFindingExtractionModel as Ae,ProblemGroupingModel as Xe,TicketCollectionModel as Ze}from"@contractspec/lib.contracts-spec/product-intent/types";import{CitationModel as Ie,ContractPatchIntentModel as me,ImpactReportModel as je,InsightExtractionModel as Re,OpportunityBriefModel as te,TaskPackModel as ie}from"@contractspec/lib.contracts-spec/product-intent/types";function I(e,y,c){if(c.min!==void 0&&e.length<c.min)throw Error(`Expected ${y} to be at least ${c.min} characters, got ${e.length}`);if(c.max!==void 0&&e.length>c.max)throw Error(`Expected ${y} to be at most ${c.max} characters, got ${e.length}`)}function W(e,y,c){if(c.min!==void 0&&e.length<c.min)throw Error(`Expected ${y} to have at least ${c.min} items, got ${e.length}`);if(c.max!==void 0&&e.length>c.max)throw Error(`Expected ${y} to have at most ${c.max} items, got ${e.length}`)}function Te(e,y,c){if(c.min!==void 0&&e<c.min)throw Error(`Expected ${y} to be >= ${c.min}, got ${e}`);if(c.max!==void 0&&e>c.max)throw Error(`Expected ${y} to be <= ${c.max}, got ${e}`)}function i(e,y){let c=y.trim();if(!c.startsWith("{")&&!c.startsWith("["))throw Error("Model did not return JSON (missing leading { or [)");let P;try{P=JSON.parse(c)}catch(f){let r=f instanceof Error?f.message:String(f);throw Error(`Invalid JSON: ${r}`)}return e.getZod().parse(P)}function D(e){let y=new Map;for(let c of e)y.set(c.chunkId,c);return y}function K(e,y,c){let P=c?.maxQuoteLen??240,f=c?.requireExactSubstring??!0,r=Ie.getZod().parse(e);I(r.quote,"citation.quote",{min:1,max:P});let $=y.get(r.chunkId);if(!$)throw Error(`Citation references unknown chunkId: ${r.chunkId}`);if(f&&!$.text.includes(r.quote))throw Error(`Citation quote is not an exact substring of chunk ${r.chunkId}`);return r}function F(e,y){if(I(e.text,"textBlock.text",{min:1}),!e.citations?.length)throw Error("Missing required citations");let c=e.citations.map((P)=>K(P,y));return{text:e.text,citations:c}}function Pc(e,y){let c=D(y),P=i(te,e);if(I(P.opportunityId,"opportunityId",{min:1}),I(P.title,"title",{min:1,max:120}),F(P.problem,c),F(P.who,c),F(P.proposedChange,c),I(P.expectedImpact.metric,"expectedImpact.metric",{min:1,max:64}),P.expectedImpact.magnitudeHint)I(P.expectedImpact.magnitudeHint,"expectedImpact.magnitudeHint",{max:64});if(P.expectedImpact.timeframeHint)I(P.expectedImpact.timeframeHint,"expectedImpact.timeframeHint",{max:64});if(P.risks){for(let f of P.risks)if(I(f.text,"risks[].text",{min:1,max:240}),f.citations)for(let r of f.citations)K(r,c)}return P}function fc(e,y){let c=D(y),P=i(Re,e);W(P.insights,"insights",{min:1,max:30});for(let f of P.insights){if(I(f.insightId,"insights[].insightId",{min:1}),I(f.claim,"insights[].claim",{min:1,max:320}),f.tags)for(let r of f.tags)I(r,"insights[].tags[]",{min:1});if(f.confidence!==void 0)Te(f.confidence,"insights[].confidence",{min:0,max:1});W(f.citations,"insights[].citations",{min:1});for(let r of f.citations)K(r,c)}return P}function B(e){let y=i(me,e);I(y.featureKey,"featureKey",{min:1,max:80}),W(y.changes,"changes",{min:1,max:25});for(let c of y.changes)I(c.target,"changes[].target",{min:1}),I(c.detail,"changes[].detail",{min:1});W(y.acceptanceCriteria,"acceptanceCriteria",{min:1,max:12});for(let c of y.acceptanceCriteria)I(c,"acceptanceCriteria[]",{min:1,max:140});return y}function rc(e){let y=i(je,e);if(I(y.reportId,"reportId",{min:1}),I(y.patchId,"patchId",{min:1}),I(y.summary,"summary",{min:1,max:200}),y.breaks)for(let P of y.breaks)I(P,"breaks[]",{min:1,max:160});if(y.mustChange)for(let P of y.mustChange)I(P,"mustChange[]",{min:1,max:160});if(y.risky)for(let P of y.risky)I(P,"risky[]",{min:1,max:160});let c=y.surfaces;if(c.api)for(let P of c.api)I(P,"surfaces.api[]",{min:1,max:140});if(c.db)for(let P of c.db)I(P,"surfaces.db[]",{min:1,max:140});if(c.ui)for(let P of c.ui)I(P,"surfaces.ui[]",{min:1,max:140});if(c.workflows)for(let P of c.workflows)I(P,"surfaces.workflows[]",{min:1,max:140});if(c.policy)for(let P of c.policy)I(P,"surfaces.policy[]",{min:1,max:140});if(c.docs)for(let P of c.docs)I(P,"surfaces.docs[]",{min:1,max:140});if(c.tests)for(let P of c.tests)I(P,"surfaces.tests[]",{min:1,max:140});return y}function $c(e){let y=i(ie,e);I(y.packId,"packId",{min:1}),I(y.patchId,"patchId",{min:1}),I(y.overview,"overview",{min:1,max:240}),W(y.tasks,"tasks",{min:3,max:14});for(let c of y.tasks){I(c.id,"tasks[].id",{min:1}),I(c.title,"tasks[].title",{min:1,max:120}),W(c.surface,"tasks[].surface",{min:1}),I(c.why,"tasks[].why",{min:1,max:200}),W(c.acceptance,"tasks[].acceptance",{min:1,max:10});for(let P of c.acceptance)I(P,"tasks[].acceptance[]",{min:1,max:160});if(I(c.agentPrompt,"tasks[].agentPrompt",{min:1,max:1400}),c.dependsOn)for(let P of c.dependsOn)I(P,"tasks[].dependsOn[]",{min:1})}return y}function _c(e){return["Your previous output failed validation.","Fix the output and return JSON ONLY (no markdown, no commentary).","Validation error:",e].join(`
285
+ `)}function ge(e,y){if(e.length<=y)return e;return`${e.slice(0,y)}
286
+ ...(truncated)`}function l(e,y,c=4000){return["Your previous output failed validation.","Fix the output and return JSON ONLY (no markdown, no commentary).","Do not change the JSON shape or rename fields.","If a citation quote is invalid, replace it with an exact substring from the referenced chunk.","Validation error:",e,"Previous output:",ge(y,c)].join(`
287
+ `)}var We=2;function A(){return new Date().toISOString()}function V(e){return e instanceof Error?e.message:String(e)}async function X(e,y){if(!e)return;try{await e.log(y)}catch{}}async function Z(e){let y=Math.max(1,e.maxAttempts??We),c=0,P,f="",r=e.prompt;while(c<y){c+=1,await X(e.logger,{stage:e.stage,phase:"request",attempt:c,prompt:r,timestamp:A()});let $;try{$=await e.modelRunner.generateJson(r)}catch(_){throw P=V(_),await X(e.logger,{stage:e.stage,phase:"model_error",attempt:c,prompt:r,error:P,timestamp:A()}),Error(`[${e.stage}] Model error: ${P}`)}await X(e.logger,{stage:e.stage,phase:"response",attempt:c,prompt:r,response:$,timestamp:A()});try{return e.validate($)}catch(_){if(P=V(_),f=$,e.repair){let m=e.repair($,P);if(m&&m!==$){await X(e.logger,{stage:e.stage,phase:"repair",attempt:c,prompt:r,response:m,error:P,timestamp:A()});try{return e.validate(m)}catch(j){P=V(j),f=m}}}await X(e.logger,{stage:e.stage,phase:"validation_error",attempt:c,prompt:r,response:f,error:P,timestamp:A()}),r=[e.prompt,l(P,f)].join(`
532
288
 
533
- // src/validators.ts
534
- import {
535
- CitationModel,
536
- ContractPatchIntentModel,
537
- ImpactReportModel,
538
- InsightExtractionModel,
539
- OpportunityBriefModel,
540
- TaskPackModel
541
- } from "@contractspec/lib.contracts-spec/product-intent/types";
542
- function assertStringLength(value, path, bounds) {
543
- if (bounds.min !== undefined && value.length < bounds.min) {
544
- throw new Error(`Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`);
545
- }
546
- if (bounds.max !== undefined && value.length > bounds.max) {
547
- throw new Error(`Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`);
548
- }
549
- }
550
- function assertArrayLength(value, path, bounds) {
551
- if (bounds.min !== undefined && value.length < bounds.min) {
552
- throw new Error(`Expected ${path} to have at least ${bounds.min} items, got ${value.length}`);
553
- }
554
- if (bounds.max !== undefined && value.length > bounds.max) {
555
- throw new Error(`Expected ${path} to have at most ${bounds.max} items, got ${value.length}`);
556
- }
557
- }
558
- function assertNumberRange(value, path, bounds) {
559
- if (bounds.min !== undefined && value < bounds.min) {
560
- throw new Error(`Expected ${path} to be >= ${bounds.min}, got ${value}`);
561
- }
562
- if (bounds.max !== undefined && value > bounds.max) {
563
- throw new Error(`Expected ${path} to be <= ${bounds.max}, got ${value}`);
564
- }
565
- }
566
- function parseStrictJSON(schema, raw) {
567
- const trimmed = raw.trim();
568
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
569
- throw new Error("Model did not return JSON (missing leading { or [)");
570
- }
571
- let parsed;
572
- try {
573
- parsed = JSON.parse(trimmed);
574
- } catch (error) {
575
- const message = error instanceof Error ? error.message : String(error);
576
- throw new Error(`Invalid JSON: ${message}`);
577
- }
578
- return schema.getZod().parse(parsed);
579
- }
580
- function buildChunkIndex(chunks) {
581
- const map = new Map;
582
- for (const chunk of chunks) {
583
- map.set(chunk.chunkId, chunk);
584
- }
585
- return map;
586
- }
587
- function validateCitation(citation, chunkIndex, opts) {
588
- const maxQuoteLen = opts?.maxQuoteLen ?? 240;
589
- const requireExactSubstring = opts?.requireExactSubstring ?? true;
590
- const parsed = CitationModel.getZod().parse(citation);
591
- assertStringLength(parsed.quote, "citation.quote", {
592
- min: 1,
593
- max: maxQuoteLen
594
- });
595
- const chunk = chunkIndex.get(parsed.chunkId);
596
- if (!chunk) {
597
- throw new Error(`Citation references unknown chunkId: ${parsed.chunkId}`);
598
- }
599
- if (requireExactSubstring && !chunk.text.includes(parsed.quote)) {
600
- throw new Error(`Citation quote is not an exact substring of chunk ${parsed.chunkId}`);
601
- }
602
- return parsed;
603
- }
604
- function validateCitationsInTextBlock(block, chunkIndex) {
605
- assertStringLength(block.text, "textBlock.text", { min: 1 });
606
- if (!block.citations?.length) {
607
- throw new Error("Missing required citations");
608
- }
609
- const citations = block.citations.map((c) => validateCitation(c, chunkIndex));
610
- return { text: block.text, citations };
611
- }
612
- function validateOpportunityBrief(raw, chunks) {
613
- const chunkIndex = buildChunkIndex(chunks);
614
- const brief = parseStrictJSON(OpportunityBriefModel, raw);
615
- assertStringLength(brief.opportunityId, "opportunityId", { min: 1 });
616
- assertStringLength(brief.title, "title", { min: 1, max: 120 });
617
- validateCitationsInTextBlock(brief.problem, chunkIndex);
618
- validateCitationsInTextBlock(brief.who, chunkIndex);
619
- validateCitationsInTextBlock(brief.proposedChange, chunkIndex);
620
- assertStringLength(brief.expectedImpact.metric, "expectedImpact.metric", {
621
- min: 1,
622
- max: 64
623
- });
624
- if (brief.expectedImpact.magnitudeHint) {
625
- assertStringLength(brief.expectedImpact.magnitudeHint, "expectedImpact.magnitudeHint", { max: 64 });
626
- }
627
- if (brief.expectedImpact.timeframeHint) {
628
- assertStringLength(brief.expectedImpact.timeframeHint, "expectedImpact.timeframeHint", { max: 64 });
629
- }
630
- if (brief.risks) {
631
- for (const risk of brief.risks) {
632
- assertStringLength(risk.text, "risks[].text", { min: 1, max: 240 });
633
- if (risk.citations) {
634
- for (const c of risk.citations) {
635
- validateCitation(c, chunkIndex);
636
- }
637
- }
638
- }
639
- }
640
- return brief;
641
- }
642
- function validateInsightExtraction(raw, chunks) {
643
- const chunkIndex = buildChunkIndex(chunks);
644
- const data = parseStrictJSON(InsightExtractionModel, raw);
645
- assertArrayLength(data.insights, "insights", { min: 1, max: 30 });
646
- for (const insight of data.insights) {
647
- assertStringLength(insight.insightId, "insights[].insightId", { min: 1 });
648
- assertStringLength(insight.claim, "insights[].claim", {
649
- min: 1,
650
- max: 320
651
- });
652
- if (insight.tags) {
653
- for (const tag of insight.tags) {
654
- assertStringLength(tag, "insights[].tags[]", { min: 1 });
655
- }
656
- }
657
- if (insight.confidence !== undefined) {
658
- assertNumberRange(insight.confidence, "insights[].confidence", {
659
- min: 0,
660
- max: 1
661
- });
662
- }
663
- assertArrayLength(insight.citations, "insights[].citations", { min: 1 });
664
- for (const c of insight.citations) {
665
- validateCitation(c, chunkIndex);
666
- }
667
- }
668
- return data;
669
- }
670
- function validatePatchIntent(raw) {
671
- const data = parseStrictJSON(ContractPatchIntentModel, raw);
672
- assertStringLength(data.featureKey, "featureKey", { min: 1, max: 80 });
673
- assertArrayLength(data.changes, "changes", { min: 1, max: 25 });
674
- for (const change of data.changes) {
675
- assertStringLength(change.target, "changes[].target", { min: 1 });
676
- assertStringLength(change.detail, "changes[].detail", { min: 1 });
677
- }
678
- assertArrayLength(data.acceptanceCriteria, "acceptanceCriteria", {
679
- min: 1,
680
- max: 12
681
- });
682
- for (const item of data.acceptanceCriteria) {
683
- assertStringLength(item, "acceptanceCriteria[]", { min: 1, max: 140 });
684
- }
685
- return data;
686
- }
687
- function validateImpactReport(raw) {
688
- const data = parseStrictJSON(ImpactReportModel, raw);
689
- assertStringLength(data.reportId, "reportId", { min: 1 });
690
- assertStringLength(data.patchId, "patchId", { min: 1 });
691
- assertStringLength(data.summary, "summary", { min: 1, max: 200 });
692
- if (data.breaks) {
693
- for (const item of data.breaks) {
694
- assertStringLength(item, "breaks[]", { min: 1, max: 160 });
695
- }
696
- }
697
- if (data.mustChange) {
698
- for (const item of data.mustChange) {
699
- assertStringLength(item, "mustChange[]", { min: 1, max: 160 });
700
- }
701
- }
702
- if (data.risky) {
703
- for (const item of data.risky) {
704
- assertStringLength(item, "risky[]", { min: 1, max: 160 });
705
- }
706
- }
707
- const surfaces = data.surfaces;
708
- if (surfaces.api) {
709
- for (const item of surfaces.api) {
710
- assertStringLength(item, "surfaces.api[]", { min: 1, max: 140 });
711
- }
712
- }
713
- if (surfaces.db) {
714
- for (const item of surfaces.db) {
715
- assertStringLength(item, "surfaces.db[]", { min: 1, max: 140 });
716
- }
717
- }
718
- if (surfaces.ui) {
719
- for (const item of surfaces.ui) {
720
- assertStringLength(item, "surfaces.ui[]", { min: 1, max: 140 });
721
- }
722
- }
723
- if (surfaces.workflows) {
724
- for (const item of surfaces.workflows) {
725
- assertStringLength(item, "surfaces.workflows[]", { min: 1, max: 140 });
726
- }
727
- }
728
- if (surfaces.policy) {
729
- for (const item of surfaces.policy) {
730
- assertStringLength(item, "surfaces.policy[]", { min: 1, max: 140 });
731
- }
732
- }
733
- if (surfaces.docs) {
734
- for (const item of surfaces.docs) {
735
- assertStringLength(item, "surfaces.docs[]", { min: 1, max: 140 });
736
- }
737
- }
738
- if (surfaces.tests) {
739
- for (const item of surfaces.tests) {
740
- assertStringLength(item, "surfaces.tests[]", { min: 1, max: 140 });
741
- }
742
- }
743
- return data;
744
- }
745
- function validateTaskPack(raw) {
746
- const data = parseStrictJSON(TaskPackModel, raw);
747
- assertStringLength(data.packId, "packId", { min: 1 });
748
- assertStringLength(data.patchId, "patchId", { min: 1 });
749
- assertStringLength(data.overview, "overview", { min: 1, max: 240 });
750
- assertArrayLength(data.tasks, "tasks", { min: 3, max: 14 });
751
- for (const task of data.tasks) {
752
- assertStringLength(task.id, "tasks[].id", { min: 1 });
753
- assertStringLength(task.title, "tasks[].title", { min: 1, max: 120 });
754
- assertArrayLength(task.surface, "tasks[].surface", { min: 1 });
755
- assertStringLength(task.why, "tasks[].why", { min: 1, max: 200 });
756
- assertArrayLength(task.acceptance, "tasks[].acceptance", {
757
- min: 1,
758
- max: 10
759
- });
760
- for (const criterion of task.acceptance) {
761
- assertStringLength(criterion, "tasks[].acceptance[]", {
762
- min: 1,
763
- max: 160
764
- });
765
- }
766
- assertStringLength(task.agentPrompt, "tasks[].agentPrompt", {
767
- min: 1,
768
- max: 1400
769
- });
770
- if (task.dependsOn) {
771
- for (const dep of task.dependsOn) {
772
- assertStringLength(dep, "tasks[].dependsOn[]", { min: 1 });
773
- }
774
- }
775
- }
776
- return data;
777
- }
778
- function buildRepairPrompt(error) {
779
- return [
780
- "Your previous output failed validation.",
781
- "Fix the output and return JSON ONLY (no markdown, no commentary).",
782
- "Validation error:",
783
- error
784
- ].join(`
785
- `);
786
- }
787
- function truncateText(value, maxChars) {
788
- if (value.length <= maxChars)
789
- return value;
790
- return `${value.slice(0, maxChars)}
791
- ...(truncated)`;
792
- }
793
- function buildRepairPromptWithOutput(error, previousOutput, maxOutputChars = 4000) {
794
- return [
795
- "Your previous output failed validation.",
796
- "Fix the output and return JSON ONLY (no markdown, no commentary).",
797
- "Do not change the JSON shape or rename fields.",
798
- "If a citation quote is invalid, replace it with an exact substring from the referenced chunk.",
799
- "Validation error:",
800
- error,
801
- "Previous output:",
802
- truncateText(previousOutput, maxOutputChars)
803
- ].join(`
804
- `);
805
- }
806
-
807
- // src/ticket-pipeline-runner.ts
808
- var DEFAULT_MAX_ATTEMPTS = 2;
809
- function timestamp() {
810
- return new Date().toISOString();
811
- }
812
- function toErrorMessage(error) {
813
- return error instanceof Error ? error.message : String(error);
814
- }
815
- async function safeLog(logger, entry) {
816
- if (!logger)
817
- return;
818
- try {
819
- await logger.log(entry);
820
- } catch {}
821
- }
822
- async function runWithValidation(options) {
823
- const maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
824
- let attempt = 0;
825
- let lastError;
826
- let lastRaw = "";
827
- let currentPrompt = options.prompt;
828
- while (attempt < maxAttempts) {
829
- attempt += 1;
830
- await safeLog(options.logger, {
831
- stage: options.stage,
832
- phase: "request",
833
- attempt,
834
- prompt: currentPrompt,
835
- timestamp: timestamp()
836
- });
837
- let raw;
838
- try {
839
- raw = await options.modelRunner.generateJson(currentPrompt);
840
- } catch (error) {
841
- lastError = toErrorMessage(error);
842
- await safeLog(options.logger, {
843
- stage: options.stage,
844
- phase: "model_error",
845
- attempt,
846
- prompt: currentPrompt,
847
- error: lastError,
848
- timestamp: timestamp()
849
- });
850
- throw new Error(`[${options.stage}] Model error: ${lastError}`);
851
- }
852
- await safeLog(options.logger, {
853
- stage: options.stage,
854
- phase: "response",
855
- attempt,
856
- prompt: currentPrompt,
857
- response: raw,
858
- timestamp: timestamp()
859
- });
860
- try {
861
- return options.validate(raw);
862
- } catch (error) {
863
- lastError = toErrorMessage(error);
864
- lastRaw = raw;
865
- if (options.repair) {
866
- const repaired = options.repair(raw, lastError);
867
- if (repaired && repaired !== raw) {
868
- await safeLog(options.logger, {
869
- stage: options.stage,
870
- phase: "repair",
871
- attempt,
872
- prompt: currentPrompt,
873
- response: repaired,
874
- error: lastError,
875
- timestamp: timestamp()
876
- });
877
- try {
878
- return options.validate(repaired);
879
- } catch (repairError) {
880
- lastError = toErrorMessage(repairError);
881
- lastRaw = repaired;
882
- }
883
- }
884
- }
885
- await safeLog(options.logger, {
886
- stage: options.stage,
887
- phase: "validation_error",
888
- attempt,
889
- prompt: currentPrompt,
890
- response: lastRaw,
891
- error: lastError,
892
- timestamp: timestamp()
893
- });
894
- currentPrompt = [
895
- options.prompt,
896
- buildRepairPromptWithOutput(lastError, lastRaw)
897
- ].join(`
898
-
899
- `);
900
- }
901
- }
902
- throw new Error(`[${options.stage}] Validation failed after ${maxAttempts} attempt(s): ${lastError ?? "unknown error"}`);
903
- }
904
-
905
- // src/ticket-prompts.ts
906
- function promptExtractEvidenceFindings(params) {
907
- return `
289
+ `)}}throw Error(`[${e.stage}] Validation failed after ${y} attempt(s): ${P??"unknown error"}`)}function O(e){return`
908
290
  You are extracting evidence findings grounded in transcript excerpts.
909
291
 
910
292
  Question:
911
- ${params.question}
293
+ ${e.question}
912
294
 
913
295
  Evidence:
914
- ${params.evidenceJSON}
296
+ ${e.evidenceJSON}
915
297
 
916
298
  Return JSON:
917
299
  {
@@ -931,23 +313,19 @@ Rules:
931
313
  - Summaries must be specific and short.
932
314
  - Quotes must be copied character-for-character from the chunk text (no paraphrasing, no ellipses).
933
315
  - Preserve punctuation, smart quotes, and special hyphens exactly as shown in the chunk text.
934
- ${CITATION_RULES}
935
- ${JSON_ONLY_RULES}
936
- `.trim();
937
- }
938
- function promptGroupProblems(params) {
939
- const allowed = JSON.stringify({ findingIds: params.findingIds }, null, 2);
940
- return `
316
+ ${z}
317
+ ${t}
318
+ `.trim()}function N(e){let y=JSON.stringify({findingIds:e.findingIds},null,2);return`
941
319
  You are grouping evidence findings into problem statements.
942
320
 
943
321
  Question:
944
- ${params.question}
322
+ ${e.question}
945
323
 
946
324
  Findings:
947
- ${params.findingsJSON}
325
+ ${e.findingsJSON}
948
326
 
949
327
  Allowed finding IDs:
950
- ${allowed}
328
+ ${y}
951
329
 
952
330
  Return JSON:
953
331
  {
@@ -966,21 +344,18 @@ Rules:
966
344
  - Each problem must reference 1 to 6 evidenceIds.
967
345
  - evidenceIds must be drawn from the allowed finding IDs.
968
346
  - Keep statements short and actionable.
969
- ${JSON_ONLY_RULES}
970
- `.trim();
971
- }
972
- function promptGenerateTickets(params) {
973
- return `
347
+ ${t}
348
+ `.trim()}function q(e){return`
974
349
  You are generating implementation tickets grounded in evidence.
975
350
 
976
351
  Question:
977
- ${params.question}
352
+ ${e.question}
978
353
 
979
354
  Problems:
980
- ${params.problemsJSON}
355
+ ${e.problemsJSON}
981
356
 
982
357
  Evidence findings:
983
- ${params.findingsJSON}
358
+ ${e.findingsJSON}
984
359
 
985
360
  Return JSON:
986
361
  {
@@ -1000,15 +375,12 @@ Rules:
1000
375
  - Every ticket must include evidenceIds and acceptanceCriteria.
1001
376
  - Acceptance criteria must be testable.
1002
377
  - Each acceptanceCriteria item must be <= 160 characters.
1003
- ${JSON_ONLY_RULES}
1004
- `.trim();
1005
- }
1006
- function promptSuggestPatchIntent(params) {
1007
- return `
378
+ ${t}
379
+ `.trim()}function n(e){return`
1008
380
  You are generating a ContractPatchIntent from an evidence-backed ticket.
1009
381
 
1010
382
  Ticket:
1011
- ${params.ticketJSON}
383
+ ${e.ticketJSON}
1012
384
 
1013
385
  Return JSON:
1014
386
  {
@@ -1024,572 +396,5 @@ Rules:
1024
396
  - Each change must be concrete and scoped.
1025
397
  - Acceptance criteria must be testable and derived from the ticket.
1026
398
  - Each acceptanceCriteria item must be <= 140 characters.
1027
- ${JSON_ONLY_RULES}
1028
- `.trim();
1029
- }
1030
-
1031
- // src/ticket-validators.ts
1032
- import {
1033
- EvidenceFindingExtractionModel,
1034
- ProblemGroupingModel,
1035
- TicketCollectionModel
1036
- } from "@contractspec/lib.contracts-spec/product-intent/types";
1037
- function assertStringLength2(value, path, bounds) {
1038
- if (bounds.min !== undefined && value.length < bounds.min) {
1039
- throw new Error(`Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`);
1040
- }
1041
- if (bounds.max !== undefined && value.length > bounds.max) {
1042
- throw new Error(`Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`);
1043
- }
1044
- }
1045
- function assertArrayLength2(value, path, bounds) {
1046
- if (bounds.min !== undefined && value.length < bounds.min) {
1047
- throw new Error(`Expected ${path} to have at least ${bounds.min} items, got ${value.length}`);
1048
- }
1049
- if (bounds.max !== undefined && value.length > bounds.max) {
1050
- throw new Error(`Expected ${path} to have at most ${bounds.max} items, got ${value.length}`);
1051
- }
1052
- }
1053
- function assertIdsExist(ids, allowed, path) {
1054
- for (const id of ids) {
1055
- if (!allowed.has(id)) {
1056
- throw new Error(`Unknown ${path} reference: ${id}`);
1057
- }
1058
- }
1059
- }
1060
- function parseJSON(schema, raw) {
1061
- return parseStrictJSON(schema, raw);
1062
- }
1063
- function validateEvidenceFindingExtraction(raw, chunks) {
1064
- const chunkIndex = buildChunkIndex(chunks);
1065
- const data = parseJSON(EvidenceFindingExtractionModel, raw);
1066
- assertArrayLength2(data.findings, "findings", { min: 1, max: 40 });
1067
- for (const finding of data.findings) {
1068
- assertStringLength2(finding.findingId, "findings[].findingId", { min: 1 });
1069
- assertStringLength2(finding.summary, "findings[].summary", {
1070
- min: 1,
1071
- max: 320
1072
- });
1073
- if (finding.tags) {
1074
- for (const tag of finding.tags) {
1075
- assertStringLength2(tag, "findings[].tags[]", { min: 1, max: 48 });
1076
- }
1077
- }
1078
- assertArrayLength2(finding.citations, "findings[].citations", { min: 1 });
1079
- for (const citation of finding.citations) {
1080
- validateCitation(citation, chunkIndex);
1081
- }
1082
- }
1083
- return data;
1084
- }
1085
- function validateProblemGrouping(raw, findings) {
1086
- const data = parseJSON(ProblemGroupingModel, raw);
1087
- const allowedIds = new Set(findings.map((finding) => finding.findingId));
1088
- assertArrayLength2(data.problems, "problems", { min: 1, max: 20 });
1089
- for (const problem of data.problems) {
1090
- assertStringLength2(problem.problemId, "problems[].problemId", { min: 1 });
1091
- assertStringLength2(problem.statement, "problems[].statement", {
1092
- min: 1,
1093
- max: 320
1094
- });
1095
- assertArrayLength2(problem.evidenceIds, "problems[].evidenceIds", {
1096
- min: 1,
1097
- max: 8
1098
- });
1099
- assertIdsExist(problem.evidenceIds, allowedIds, "evidenceId");
1100
- if (problem.tags) {
1101
- for (const tag of problem.tags) {
1102
- assertStringLength2(tag, "problems[].tags[]", { min: 1, max: 48 });
1103
- }
1104
- }
1105
- }
1106
- return data;
1107
- }
1108
- function validateTicketCollection(raw, findings) {
1109
- const data = parseJSON(TicketCollectionModel, raw);
1110
- const allowedIds = new Set(findings.map((finding) => finding.findingId));
1111
- assertArrayLength2(data.tickets, "tickets", { min: 1, max: 30 });
1112
- for (const ticket of data.tickets) {
1113
- assertStringLength2(ticket.ticketId, "tickets[].ticketId", { min: 1 });
1114
- assertStringLength2(ticket.title, "tickets[].title", { min: 1, max: 120 });
1115
- assertStringLength2(ticket.summary, "tickets[].summary", {
1116
- min: 1,
1117
- max: 320
1118
- });
1119
- assertArrayLength2(ticket.evidenceIds, "tickets[].evidenceIds", {
1120
- min: 1,
1121
- max: 8
1122
- });
1123
- assertIdsExist(ticket.evidenceIds, allowedIds, "evidenceId");
1124
- assertArrayLength2(ticket.acceptanceCriteria, "tickets[].acceptanceCriteria", {
1125
- min: 1,
1126
- max: 8
1127
- });
1128
- for (const criterion of ticket.acceptanceCriteria) {
1129
- assertStringLength2(criterion, "tickets[].acceptanceCriteria[]", {
1130
- min: 1,
1131
- max: 280
1132
- });
1133
- }
1134
- if (ticket.tags) {
1135
- for (const tag of ticket.tags) {
1136
- assertStringLength2(tag, "tickets[].tags[]", { min: 1, max: 48 });
1137
- }
1138
- }
1139
- }
1140
- return data;
1141
- }
1142
-
1143
- // src/ticket-pipeline.ts
1144
- var TAG_HINTS = {
1145
- onboarding: ["onboarding", "setup", "activation"],
1146
- pricing: ["pricing", "cost", "billing"],
1147
- security: ["security", "compliance", "audit"],
1148
- support: ["support", "ticket", "helpdesk"],
1149
- analytics: ["analytics", "report", "dashboard"],
1150
- performance: ["slow", "latency", "performance"],
1151
- integrations: ["integration", "api", "webhook"]
1152
- };
1153
- function slugify2(value) {
1154
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "");
1155
- }
1156
- function pickQuote(text, maxLen = 220) {
1157
- const trimmed = text.trim();
1158
- const sentenceEnd = trimmed.search(/[.!?]\s/);
1159
- const sentence = sentenceEnd === -1 ? trimmed : trimmed.slice(0, sentenceEnd + 1);
1160
- const quote = sentence.length > maxLen ? sentence.slice(0, maxLen) : sentence;
1161
- return quote.trim();
1162
- }
1163
- function deriveTags(text) {
1164
- const lower = text.toLowerCase();
1165
- const tags = Object.entries(TAG_HINTS).filter(([, hints]) => hints.some((hint) => lower.includes(hint))).map(([tag]) => tag);
1166
- return tags.slice(0, 3);
1167
- }
1168
- function truncateToMax(value, maxChars) {
1169
- if (value.length <= maxChars)
1170
- return value;
1171
- if (maxChars <= 3)
1172
- return value.slice(0, maxChars);
1173
- return `${value.slice(0, maxChars - 3).trimEnd()}...`;
1174
- }
1175
- var QUOTE_HYPHENS = new Set(["-", "‐", "‑", "‒", "–", "—"]);
1176
- var QUOTE_SINGLE = new Set(["'", "’", "‘"]);
1177
- var QUOTE_DOUBLE = new Set(['"', "“", "”"]);
1178
- function escapeRegex(value) {
1179
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1180
- }
1181
- function buildLooseQuotePattern(quote) {
1182
- let pattern = "";
1183
- for (let i = 0;i < quote.length; i += 1) {
1184
- const char = quote[i] ?? "";
1185
- if (char === "." && quote.slice(i, i + 3) === "...") {
1186
- pattern += "(?:\\.\\.\\.|…)";
1187
- i += 2;
1188
- continue;
1189
- }
1190
- if (char === "…") {
1191
- pattern += "(?:\\.\\.\\.|…)";
1192
- continue;
1193
- }
1194
- if (/\s/.test(char)) {
1195
- pattern += "\\s+";
1196
- while (i + 1 < quote.length && /\s/.test(quote[i + 1] ?? "")) {
1197
- i += 1;
1198
- }
1199
- continue;
1200
- }
1201
- if (QUOTE_HYPHENS.has(char)) {
1202
- pattern += "[-‐‑‒–—]";
1203
- continue;
1204
- }
1205
- if (QUOTE_SINGLE.has(char)) {
1206
- pattern += "['‘’]";
1207
- continue;
1208
- }
1209
- if (QUOTE_DOUBLE.has(char)) {
1210
- pattern += '["“”]';
1211
- continue;
1212
- }
1213
- pattern += escapeRegex(char);
1214
- }
1215
- return pattern;
1216
- }
1217
- function findQuoteInChunk(quote, chunkText) {
1218
- if (chunkText.includes(quote))
1219
- return quote;
1220
- const pattern = buildLooseQuotePattern(quote);
1221
- const match = chunkText.match(new RegExp(pattern));
1222
- return match?.[0] ?? null;
1223
- }
1224
- function normalizeForTokens(value) {
1225
- return value.replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/[‐‑‒–—]/g, "-").replace(/\s+/g, " ").trim();
1226
- }
1227
- function tokenize(value) {
1228
- const normalized = normalizeForTokens(value).toLowerCase();
1229
- return normalized.match(/[a-z0-9]+/g) ?? [];
1230
- }
1231
- function splitIntoSegments(text) {
1232
- const matches = text.match(/[^.!?\n]+[.!?]?/g);
1233
- if (!matches)
1234
- return [text];
1235
- return matches.map((segment) => segment.trim()).filter(Boolean);
1236
- }
1237
- function selectBestQuoteFromChunk(quote, chunkText, maxLen = 240) {
1238
- const quoteTokens = tokenize(quote);
1239
- if (!quoteTokens.length)
1240
- return null;
1241
- const quoteTokenSet = new Set(quoteTokens);
1242
- let best = null;
1243
- for (const segment of splitIntoSegments(chunkText)) {
1244
- if (!segment)
1245
- continue;
1246
- const segmentTokens = new Set(tokenize(segment));
1247
- if (!segmentTokens.size)
1248
- continue;
1249
- let overlap = 0;
1250
- for (const token of quoteTokenSet) {
1251
- if (segmentTokens.has(token))
1252
- overlap += 1;
1253
- }
1254
- if (!overlap)
1255
- continue;
1256
- const score = overlap / quoteTokenSet.size;
1257
- if (!best || score > best.score) {
1258
- best = { segment, score, overlap };
1259
- }
1260
- }
1261
- if (!best)
1262
- return null;
1263
- if (best.overlap < 2 && quoteTokens.length > 2)
1264
- return null;
1265
- const trimmed = best.segment.trim();
1266
- return trimmed.length > maxLen ? trimmed.slice(0, maxLen).trimEnd() : trimmed;
1267
- }
1268
- function fallbackQuoteFromChunk(chunkText, maxLen = 240) {
1269
- const trimmed = chunkText.trim();
1270
- if (!trimmed)
1271
- return null;
1272
- const slice = trimmed.length > maxLen ? trimmed.slice(0, maxLen) : trimmed;
1273
- return slice.trimEnd();
1274
- }
1275
- function findQuoteAcrossChunks(quote, chunkIndex) {
1276
- for (const [chunkId, chunk] of chunkIndex.entries()) {
1277
- if (chunk.text.includes(quote)) {
1278
- return { chunkId, quote };
1279
- }
1280
- const repaired = findQuoteInChunk(quote, chunk.text);
1281
- if (repaired) {
1282
- return { chunkId, quote: repaired };
1283
- }
1284
- }
1285
- return null;
1286
- }
1287
- function repairEvidenceFindingExtraction(raw, chunks) {
1288
- let data;
1289
- try {
1290
- data = parseStrictJSON(EvidenceFindingExtractionModel2, raw);
1291
- } catch {
1292
- return null;
1293
- }
1294
- const chunkIndex = buildChunkIndex(chunks);
1295
- let updated = false;
1296
- for (const finding of data.findings) {
1297
- for (const citation of finding.citations) {
1298
- const chunk = chunkIndex.get(citation.chunkId);
1299
- if (chunk) {
1300
- if (chunk.text.includes(citation.quote))
1301
- continue;
1302
- const repaired = findQuoteInChunk(citation.quote, chunk.text);
1303
- if (repaired) {
1304
- citation.quote = repaired;
1305
- updated = true;
1306
- continue;
1307
- }
1308
- }
1309
- const other = findQuoteAcrossChunks(citation.quote, chunkIndex);
1310
- if (other) {
1311
- citation.chunkId = other.chunkId;
1312
- citation.quote = other.quote;
1313
- updated = true;
1314
- continue;
1315
- }
1316
- if (chunk) {
1317
- const best = selectBestQuoteFromChunk(citation.quote, chunk.text);
1318
- if (best) {
1319
- citation.quote = best;
1320
- updated = true;
1321
- continue;
1322
- }
1323
- const fallback = fallbackQuoteFromChunk(chunk.text);
1324
- if (fallback) {
1325
- citation.quote = fallback;
1326
- updated = true;
1327
- continue;
1328
- }
1329
- }
1330
- }
1331
- }
1332
- return updated ? JSON.stringify(data, null, 2) : null;
1333
- }
1334
- function repairProblemGrouping(raw) {
1335
- let data;
1336
- try {
1337
- data = parseStrictJSON(ProblemGroupingModel2, raw);
1338
- } catch {
1339
- return null;
1340
- }
1341
- let updated = false;
1342
- for (const problem of data.problems) {
1343
- const statement = truncateToMax(problem.statement, 320);
1344
- if (statement !== problem.statement) {
1345
- problem.statement = statement;
1346
- updated = true;
1347
- }
1348
- }
1349
- return updated ? JSON.stringify(data, null, 2) : null;
1350
- }
1351
- function repairTicketCollection(raw) {
1352
- let data;
1353
- try {
1354
- data = parseStrictJSON(TicketCollectionModel2, raw);
1355
- } catch {
1356
- return null;
1357
- }
1358
- let updated = false;
1359
- for (const ticket of data.tickets) {
1360
- const title = truncateToMax(ticket.title, 120);
1361
- const summary = truncateToMax(ticket.summary, 320);
1362
- if (title !== ticket.title) {
1363
- ticket.title = title;
1364
- updated = true;
1365
- }
1366
- if (summary !== ticket.summary) {
1367
- ticket.summary = summary;
1368
- updated = true;
1369
- }
1370
- ticket.acceptanceCriteria = ticket.acceptanceCriteria.map((criterion) => {
1371
- const next = truncateToMax(criterion, 160);
1372
- if (next !== criterion)
1373
- updated = true;
1374
- return next;
1375
- });
1376
- }
1377
- return updated ? JSON.stringify(data, null, 2) : null;
1378
- }
1379
- function repairPatchIntent(raw) {
1380
- let data;
1381
- try {
1382
- data = parseStrictJSON(ContractPatchIntentModel2, raw);
1383
- } catch {
1384
- return null;
1385
- }
1386
- let updated = false;
1387
- const featureKey = truncateToMax(data.featureKey, 80);
1388
- if (featureKey !== data.featureKey) {
1389
- data.featureKey = featureKey;
1390
- updated = true;
1391
- }
1392
- data.acceptanceCriteria = data.acceptanceCriteria.map((criterion) => {
1393
- const next = truncateToMax(criterion, 140);
1394
- if (next !== criterion)
1395
- updated = true;
1396
- return next;
1397
- });
1398
- return updated ? JSON.stringify(data, null, 2) : null;
1399
- }
1400
- function retrieveChunks(transcript, question, options = {}) {
1401
- const chunkSize = options.chunkSize ?? 800;
1402
- const sourceId = options.sourceId ?? slugify2(question || "transcript");
1403
- const clean = transcript.trim();
1404
- const chunks = [];
1405
- for (let offset = 0, idx = 0;offset < clean.length; idx += 1) {
1406
- const slice = clean.slice(offset, offset + chunkSize);
1407
- chunks.push({
1408
- chunkId: `${sourceId}#c_${String(idx).padStart(2, "0")}`,
1409
- text: slice,
1410
- meta: { sourceId, ...options.meta }
1411
- });
1412
- offset += chunkSize;
1413
- }
1414
- return chunks;
1415
- }
1416
- async function extractEvidence(chunks, question, options = {}) {
1417
- if (options.modelRunner) {
1418
- const evidenceJSON = formatEvidenceForModel(chunks, 900);
1419
- const prompt = promptExtractEvidenceFindings({ question, evidenceJSON });
1420
- return runWithValidation({
1421
- stage: "extractEvidence",
1422
- prompt,
1423
- modelRunner: options.modelRunner,
1424
- logger: options.logger,
1425
- maxAttempts: options.maxAttempts,
1426
- repair: (raw2) => repairEvidenceFindingExtraction(raw2, chunks),
1427
- validate: (raw2) => validateEvidenceFindingExtraction(raw2, chunks).findings
1428
- });
1429
- }
1430
- const maxFindings = options.maxFindings ?? 12;
1431
- const findings = [];
1432
- for (const chunk of chunks) {
1433
- if (findings.length >= maxFindings)
1434
- break;
1435
- const quote = pickQuote(chunk.text);
1436
- findings.push({
1437
- findingId: `find_${String(findings.length + 1).padStart(3, "0")}`,
1438
- summary: quote.length > 160 ? `${quote.slice(0, 160)}...` : quote,
1439
- tags: deriveTags(chunk.text),
1440
- citations: [{ chunkId: chunk.chunkId, quote }]
1441
- });
1442
- }
1443
- const raw = JSON.stringify({ findings }, null, 2);
1444
- return validateEvidenceFindingExtraction(raw, chunks).findings;
1445
- }
1446
- async function groupProblems(findings, question, options = {}) {
1447
- if (options.modelRunner) {
1448
- const findingsJSON = JSON.stringify({ findings }, null, 2);
1449
- const prompt = promptGroupProblems({
1450
- question,
1451
- findingsJSON,
1452
- findingIds: findings.map((finding) => finding.findingId)
1453
- });
1454
- return runWithValidation({
1455
- stage: "groupProblems",
1456
- prompt,
1457
- modelRunner: options.modelRunner,
1458
- logger: options.logger,
1459
- maxAttempts: options.maxAttempts,
1460
- repair: (raw2) => repairProblemGrouping(raw2),
1461
- validate: (raw2) => validateProblemGrouping(raw2, findings).problems
1462
- });
1463
- }
1464
- const grouped = new Map;
1465
- for (const finding of findings) {
1466
- const tag = finding.tags?.[0] ?? "general";
1467
- if (!grouped.has(tag))
1468
- grouped.set(tag, []);
1469
- grouped.get(tag)?.push(finding);
1470
- }
1471
- const problems = [];
1472
- for (const [tag, items] of grouped.entries()) {
1473
- const count = items.length;
1474
- const severity = count >= 4 ? "high" : count >= 2 ? "medium" : "low";
1475
- const statement = tag === "general" ? "Users report friction that slows adoption." : `Users report ${tag} friction that blocks progress.`;
1476
- problems.push({
1477
- problemId: `prob_${String(problems.length + 1).padStart(3, "0")}`,
1478
- statement,
1479
- evidenceIds: items.map((item) => item.findingId),
1480
- tags: tag === "general" ? undefined : [tag],
1481
- severity
1482
- });
1483
- }
1484
- const raw = JSON.stringify({ problems }, null, 2);
1485
- return validateProblemGrouping(raw, findings).problems;
1486
- }
1487
- async function generateTickets(problems, findings, question, options = {}) {
1488
- if (options.modelRunner) {
1489
- const problemsJSON = JSON.stringify({ problems }, null, 2);
1490
- const findingsJSON = JSON.stringify({ findings }, null, 2);
1491
- const prompt = promptGenerateTickets({
1492
- question,
1493
- problemsJSON,
1494
- findingsJSON
1495
- });
1496
- return runWithValidation({
1497
- stage: "generateTickets",
1498
- prompt,
1499
- modelRunner: options.modelRunner,
1500
- logger: options.logger,
1501
- maxAttempts: options.maxAttempts,
1502
- repair: (raw2) => repairTicketCollection(raw2),
1503
- validate: (raw2) => validateTicketCollection(raw2, findings).tickets
1504
- });
1505
- }
1506
- const tickets = problems.map((problem, idx) => {
1507
- const tag = problem.tags?.[0];
1508
- const title = tag ? `Improve ${tag} flow` : "Reduce user friction";
1509
- const summary = problem.statement;
1510
- return {
1511
- ticketId: `t_${String(idx + 1).padStart(3, "0")}`,
1512
- title,
1513
- summary,
1514
- evidenceIds: problem.evidenceIds.slice(0, 4),
1515
- acceptanceCriteria: [
1516
- "Acceptance criteria maps to the evidence findings",
1517
- "Success metrics are tracked for the change"
1518
- ],
1519
- tags: problem.tags,
1520
- priority: problem.severity === "high" ? "high" : "medium"
1521
- };
1522
- });
1523
- const raw = JSON.stringify({ tickets }, null, 2);
1524
- return validateTicketCollection(raw, findings).tickets;
1525
- }
1526
- async function suggestPatch(ticket, options = {}) {
1527
- if (options.modelRunner) {
1528
- const ticketJSON = JSON.stringify(ticket, null, 2);
1529
- const prompt = promptSuggestPatchIntent({ ticketJSON });
1530
- return runWithValidation({
1531
- stage: "suggestPatch",
1532
- prompt,
1533
- modelRunner: options.modelRunner,
1534
- logger: options.logger,
1535
- maxAttempts: options.maxAttempts,
1536
- repair: (raw) => repairPatchIntent(raw),
1537
- validate: (raw) => validatePatchIntent(raw)
1538
- });
1539
- }
1540
- const featureKey = slugify2(ticket.title) || "product_intent_ticket";
1541
- const intent = {
1542
- featureKey,
1543
- changes: [
1544
- {
1545
- type: "update_operation",
1546
- target: `productIntent.${featureKey}`,
1547
- detail: ticket.summary
1548
- }
1549
- ],
1550
- acceptanceCriteria: ticket.acceptanceCriteria
1551
- };
1552
- return validatePatchIntent(JSON.stringify(intent, null, 2));
1553
- }
1554
- export {
1555
- validateTicketCollection,
1556
- validateTaskPack,
1557
- validateProblemGrouping,
1558
- validatePatchIntent,
1559
- validateOpportunityBrief,
1560
- validateInsightExtraction,
1561
- validateImpactReport,
1562
- validateEvidenceFindingExtraction,
1563
- validateCitationsInTextBlock,
1564
- validateCitation,
1565
- suggestPatch,
1566
- runWithValidation,
1567
- retrieveChunks,
1568
- promptWireframeLayoutJSON,
1569
- promptWireframeImage,
1570
- promptSynthesizeBrief,
1571
- promptSuggestPatchIntent,
1572
- promptSkepticCheck,
1573
- promptGroupProblems,
1574
- promptGenerateTickets,
1575
- promptGenerateTaskPack,
1576
- promptGenerateSyntheticInterviews,
1577
- promptGeneratePatchIntent,
1578
- promptGenerateImpactReport,
1579
- promptGenerateGenericSpecOverlay,
1580
- promptExtractInsights,
1581
- promptExtractEvidenceFindings,
1582
- parseStrictJSON,
1583
- impactEngine,
1584
- groupProblems,
1585
- generateTickets,
1586
- formatEvidenceForModel,
1587
- extractEvidence,
1588
- buildWorkItemsFromTickets,
1589
- buildRepairPromptWithOutput,
1590
- buildRepairPrompt,
1591
- buildProjectManagementSyncPayload,
1592
- buildChunkIndex,
1593
- JSON_ONLY_RULES,
1594
- CITATION_RULES
1595
- };
399
+ ${t}
400
+ `.trim()}import{EvidenceFindingExtractionModel as Me,ProblemGroupingModel as De,TicketCollectionModel as oe}from"@contractspec/lib.contracts-spec/product-intent/types";function T(e,y,c){if(c.min!==void 0&&e.length<c.min)throw Error(`Expected ${y} to be at least ${c.min} characters, got ${e.length}`);if(c.max!==void 0&&e.length>c.max)throw Error(`Expected ${y} to be at most ${c.max} characters, got ${e.length}`)}function M(e,y,c){if(c.min!==void 0&&e.length<c.min)throw Error(`Expected ${y} to have at least ${c.min} items, got ${e.length}`);if(c.max!==void 0&&e.length>c.max)throw Error(`Expected ${y} to have at most ${c.max} items, got ${e.length}`)}function v(e,y,c){for(let P of e)if(!y.has(P))throw Error(`Unknown ${c} reference: ${P}`)}function E(e,y){return i(e,y)}function p(e,y){let c=D(y),P=E(Me,e);M(P.findings,"findings",{min:1,max:40});for(let f of P.findings){if(T(f.findingId,"findings[].findingId",{min:1}),T(f.summary,"findings[].summary",{min:1,max:320}),f.tags)for(let r of f.tags)T(r,"findings[].tags[]",{min:1,max:48});M(f.citations,"findings[].citations",{min:1});for(let r of f.citations)K(r,c)}return P}function Y(e,y){let c=E(De,e),P=new Set(y.map((f)=>f.findingId));M(c.problems,"problems",{min:1,max:20});for(let f of c.problems)if(T(f.problemId,"problems[].problemId",{min:1}),T(f.statement,"problems[].statement",{min:1,max:320}),M(f.evidenceIds,"problems[].evidenceIds",{min:1,max:8}),v(f.evidenceIds,P,"evidenceId"),f.tags)for(let r of f.tags)T(r,"problems[].tags[]",{min:1,max:48});return c}function w(e,y){let c=E(oe,e),P=new Set(y.map((f)=>f.findingId));M(c.tickets,"tickets",{min:1,max:30});for(let f of c.tickets){T(f.ticketId,"tickets[].ticketId",{min:1}),T(f.title,"tickets[].title",{min:1,max:120}),T(f.summary,"tickets[].summary",{min:1,max:320}),M(f.evidenceIds,"tickets[].evidenceIds",{min:1,max:8}),v(f.evidenceIds,P,"evidenceId"),M(f.acceptanceCriteria,"tickets[].acceptanceCriteria",{min:1,max:8});for(let r of f.acceptanceCriteria)T(r,"tickets[].acceptanceCriteria[]",{min:1,max:280});if(f.tags)for(let r of f.tags)T(r,"tickets[].tags[]",{min:1,max:48})}return c}var He={onboarding:["onboarding","setup","activation"],pricing:["pricing","cost","billing"],security:["security","compliance","audit"],support:["support","ticket","helpdesk"],analytics:["analytics","report","dashboard"],performance:["slow","latency","performance"],integrations:["integration","api","webhook"]};function S(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)+/g,"")}function ze(e,y=220){let c=e.trim(),P=c.search(/[.!?]\s/),f=P===-1?c:c.slice(0,P+1);return(f.length>y?f.slice(0,y):f).trim()}function Fe(e){let y=e.toLowerCase();return Object.entries(He).filter(([,P])=>P.some((f)=>y.includes(f))).map(([P])=>P).slice(0,3)}function o(e,y){if(e.length<=y)return e;if(y<=3)return e.slice(0,y);return`${e.slice(0,y-3).trimEnd()}...`}var Be=new Set(["-","‐","‑","‒","–","—"]),Ve=new Set(["'","’","‘"]),Ee=new Set(['"',"“","”"]);function pe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Ye(e){let y="";for(let c=0;c<e.length;c+=1){let P=e[c]??"";if(P==="."&&e.slice(c,c+3)==="..."){y+="(?:\\.\\.\\.|…)",c+=2;continue}if(P==="…"){y+="(?:\\.\\.\\.|…)";continue}if(/\s/.test(P)){y+="\\s+";while(c+1<e.length&&/\s/.test(e[c+1]??""))c+=1;continue}if(Be.has(P)){y+="[-‐‑‒–—]";continue}if(Ve.has(P)){y+="['‘’]";continue}if(Ee.has(P)){y+='["“”]';continue}y+=pe(P)}return y}function k(e,y){if(y.includes(e))return e;let c=Ye(e);return y.match(new RegExp(c))?.[0]??null}function we(e){return e.replace(/[“”]/g,'"').replace(/[‘’]/g,"'").replace(/[‐‑‒–—]/g,"-").replace(/\s+/g," ").trim()}function x(e){return we(e).toLowerCase().match(/[a-z0-9]+/g)??[]}function Ce(e){let y=e.match(/[^.!?\n]+[.!?]?/g);if(!y)return[e];return y.map((c)=>c.trim()).filter(Boolean)}function Ge(e,y,c=240){let P=x(e);if(!P.length)return null;let f=new Set(P),r=null;for(let _ of Ce(y)){if(!_)continue;let m=new Set(x(_));if(!m.size)continue;let j=0;for(let g of f)if(m.has(g))j+=1;if(!j)continue;let R=j/f.size;if(!r||R>r.score)r={segment:_,score:R,overlap:j}}if(!r)return null;if(r.overlap<2&&P.length>2)return null;let $=r.segment.trim();return $.length>c?$.slice(0,c).trimEnd():$}function Qe(e,y=240){let c=e.trim();if(!c)return null;return(c.length>y?c.slice(0,y):c).trimEnd()}function Je(e,y){for(let[c,P]of y.entries()){if(P.text.includes(e))return{chunkId:c,quote:e};let f=k(e,P.text);if(f)return{chunkId:c,quote:f}}return null}function Ue(e,y){let c;try{c=i(Ae,e)}catch{return null}let P=D(y),f=!1;for(let r of c.findings)for(let $ of r.citations){let _=P.get($.chunkId);if(_){if(_.text.includes($.quote))continue;let j=k($.quote,_.text);if(j){$.quote=j,f=!0;continue}}let m=Je($.quote,P);if(m){$.chunkId=m.chunkId,$.quote=m.quote,f=!0;continue}if(_){let j=Ge($.quote,_.text);if(j){$.quote=j,f=!0;continue}let R=Qe(_.text);if(R){$.quote=R,f=!0;continue}}}return f?JSON.stringify(c,null,2):null}function le(e){let y;try{y=i(Xe,e)}catch{return null}let c=!1;for(let P of y.problems){let f=o(P.statement,320);if(f!==P.statement)P.statement=f,c=!0}return c?JSON.stringify(y,null,2):null}function Oe(e){let y;try{y=i(Ze,e)}catch{return null}let c=!1;for(let P of y.tickets){let f=o(P.title,120),r=o(P.summary,320);if(f!==P.title)P.title=f,c=!0;if(r!==P.summary)P.summary=r,c=!0;P.acceptanceCriteria=P.acceptanceCriteria.map(($)=>{let _=o($,160);if(_!==$)c=!0;return _})}return c?JSON.stringify(y,null,2):null}function Ne(e){let y;try{y=i(Ke,e)}catch{return null}let c=!1,P=o(y.featureKey,80);if(P!==y.featureKey)y.featureKey=P,c=!0;return y.acceptanceCriteria=y.acceptanceCriteria.map((f)=>{let r=o(f,140);if(r!==f)c=!0;return r}),c?JSON.stringify(y,null,2):null}function Xc(e,y,c={}){let P=c.chunkSize??800,f=c.sourceId??S(y||"transcript"),r=e.trim(),$=[];for(let _=0,m=0;_<r.length;m+=1){let j=r.slice(_,_+P);$.push({chunkId:`${f}#c_${String(m).padStart(2,"0")}`,text:j,meta:{sourceId:f,...c.meta}}),_+=P}return $}async function Zc(e,y,c={}){if(c.modelRunner){let $=U(e,900),_=O({question:y,evidenceJSON:$});return Z({stage:"extractEvidence",prompt:_,modelRunner:c.modelRunner,logger:c.logger,maxAttempts:c.maxAttempts,repair:(m)=>Ue(m,e),validate:(m)=>p(m,e).findings})}let P=c.maxFindings??12,f=[];for(let $ of e){if(f.length>=P)break;let _=ze($.text);f.push({findingId:`find_${String(f.length+1).padStart(3,"0")}`,summary:_.length>160?`${_.slice(0,160)}...`:_,tags:Fe($.text),citations:[{chunkId:$.chunkId,quote:_}]})}let r=JSON.stringify({findings:f},null,2);return p(r,e).findings}async function Hc(e,y,c={}){if(c.modelRunner){let $=JSON.stringify({findings:e},null,2),_=N({question:y,findingsJSON:$,findingIds:e.map((m)=>m.findingId)});return Z({stage:"groupProblems",prompt:_,modelRunner:c.modelRunner,logger:c.logger,maxAttempts:c.maxAttempts,repair:(m)=>le(m),validate:(m)=>Y(m,e).problems})}let P=new Map;for(let $ of e){let _=$.tags?.[0]??"general";if(!P.has(_))P.set(_,[]);P.get(_)?.push($)}let f=[];for(let[$,_]of P.entries()){let m=_.length,j=m>=4?"high":m>=2?"medium":"low",R=$==="general"?"Users report friction that slows adoption.":`Users report ${$} friction that blocks progress.`;f.push({problemId:`prob_${String(f.length+1).padStart(3,"0")}`,statement:R,evidenceIds:_.map((g)=>g.findingId),tags:$==="general"?void 0:[$],severity:j})}let r=JSON.stringify({problems:f},null,2);return Y(r,e).problems}async function zc(e,y,c,P={}){if(P.modelRunner){let $=JSON.stringify({problems:e},null,2),_=JSON.stringify({findings:y},null,2),m=q({question:c,problemsJSON:$,findingsJSON:_});return Z({stage:"generateTickets",prompt:m,modelRunner:P.modelRunner,logger:P.logger,maxAttempts:P.maxAttempts,repair:(j)=>Oe(j),validate:(j)=>w(j,y).tickets})}let f=e.map(($,_)=>{let m=$.tags?.[0],j=m?`Improve ${m} flow`:"Reduce user friction",R=$.statement;return{ticketId:`t_${String(_+1).padStart(3,"0")}`,title:j,summary:R,evidenceIds:$.evidenceIds.slice(0,4),acceptanceCriteria:["Acceptance criteria maps to the evidence findings","Success metrics are tracked for the change"],tags:$.tags,priority:$.severity==="high"?"high":"medium"}}),r=JSON.stringify({tickets:f},null,2);return w(r,y).tickets}async function Fc(e,y={}){if(y.modelRunner){let f=JSON.stringify(e,null,2),r=n({ticketJSON:f});return Z({stage:"suggestPatch",prompt:r,modelRunner:y.modelRunner,logger:y.logger,maxAttempts:y.maxAttempts,repair:($)=>Ne($),validate:($)=>B($)})}let c=S(e.title)||"product_intent_ticket",P={featureKey:c,changes:[{type:"update_operation",target:`productIntent.${c}`,detail:e.summary}],acceptanceCriteria:e.acceptanceCriteria};return B(JSON.stringify(P,null,2))}export{w as validateTicketCollection,$c as validateTaskPack,Y as validateProblemGrouping,B as validatePatchIntent,Pc as validateOpportunityBrief,fc as validateInsightExtraction,rc as validateImpactReport,p as validateEvidenceFindingExtraction,F as validateCitationsInTextBlock,K as validateCitation,Fc as suggestPatch,Z as runWithValidation,Xc as retrieveChunks,ae as promptWireframeLayoutJSON,se as promptWireframeImage,ke as promptSynthesizeBrief,n as promptSuggestPatchIntent,be as promptSkepticCheck,N as promptGroupProblems,q as promptGenerateTickets,he as promptGenerateTaskPack,ec as promptGenerateSyntheticInterviews,Le as promptGeneratePatchIntent,ue as promptGenerateImpactReport,de as promptGenerateGenericSpecOverlay,Se as promptExtractInsights,O as promptExtractEvidenceFindings,i as parseStrictJSON,qe as impactEngine,Hc as groupProblems,zc as generateTickets,U as formatEvidenceForModel,Zc as extractEvidence,Pe as buildWorkItemsFromTickets,l as buildRepairPromptWithOutput,_c as buildRepairPrompt,ve as buildProjectManagementSyncPayload,D as buildChunkIndex,t as JSON_ONLY_RULES,z as CITATION_RULES};