@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,131 +0,0 @@
1
- import { CITATION_RULES, JSON_ONLY_RULES } from "./prompts.js";
2
-
3
- //#region src/ticket-prompts.ts
4
- function promptExtractEvidenceFindings(params) {
5
- return `
6
- You are extracting evidence findings grounded in transcript excerpts.
7
-
8
- Question:
9
- ${params.question}
10
-
11
- Evidence:
12
- ${params.evidenceJSON}
13
-
14
- Return JSON:
15
- {
16
- "findings": [
17
- {
18
- "findingId": "find_001",
19
- "summary": "...",
20
- "tags": ["..."],
21
- "citations": [{ "chunkId": "...", "quote": "..." }]
22
- }
23
- ]
24
- }
25
-
26
- Rules:
27
- - Produce 8 to 18 findings.
28
- - Each finding must include at least 1 citation.
29
- - Summaries must be specific and short.
30
- - Quotes must be copied character-for-character from the chunk text (no paraphrasing, no ellipses).
31
- - Preserve punctuation, smart quotes, and special hyphens exactly as shown in the chunk text.
32
- ${CITATION_RULES}
33
- ${JSON_ONLY_RULES}
34
- `.trim();
35
- }
36
- function promptGroupProblems(params) {
37
- const allowed = JSON.stringify({ findingIds: params.findingIds }, null, 2);
38
- return `
39
- You are grouping evidence findings into problem statements.
40
-
41
- Question:
42
- ${params.question}
43
-
44
- Findings:
45
- ${params.findingsJSON}
46
-
47
- Allowed finding IDs:
48
- ${allowed}
49
-
50
- Return JSON:
51
- {
52
- "problems": [
53
- {
54
- "problemId": "prob_001",
55
- "statement": "...",
56
- "evidenceIds": ["find_001"],
57
- "tags": ["..."],
58
- "severity": "low|medium|high"
59
- }
60
- ]
61
- }
62
-
63
- Rules:
64
- - Each problem must reference 1 to 6 evidenceIds.
65
- - evidenceIds must be drawn from the allowed finding IDs.
66
- - Keep statements short and actionable.
67
- ${JSON_ONLY_RULES}
68
- `.trim();
69
- }
70
- function promptGenerateTickets(params) {
71
- return `
72
- You are generating implementation tickets grounded in evidence.
73
-
74
- Question:
75
- ${params.question}
76
-
77
- Problems:
78
- ${params.problemsJSON}
79
-
80
- Evidence findings:
81
- ${params.findingsJSON}
82
-
83
- Return JSON:
84
- {
85
- "tickets": [
86
- {
87
- "ticketId": "t_001",
88
- "title": "...",
89
- "summary": "...",
90
- "evidenceIds": ["find_001"],
91
- "acceptanceCriteria": ["..."]
92
- }
93
- ]
94
- }
95
-
96
- Rules:
97
- - 1 to 2 tickets per problem.
98
- - Every ticket must include evidenceIds and acceptanceCriteria.
99
- - Acceptance criteria must be testable.
100
- - Each acceptanceCriteria item must be <= 160 characters.
101
- ${JSON_ONLY_RULES}
102
- `.trim();
103
- }
104
- function promptSuggestPatchIntent(params) {
105
- return `
106
- You are generating a ContractPatchIntent from an evidence-backed ticket.
107
-
108
- Ticket:
109
- ${params.ticketJSON}
110
-
111
- Return JSON:
112
- {
113
- "featureKey": "feature_slug",
114
- "changes": [
115
- { "type": "add_field|remove_field|rename_field|add_event|update_event|add_operation|update_operation|update_form|update_policy|add_enum_value|remove_enum_value|other", "target": "string", "detail": "string" }
116
- ],
117
- "acceptanceCriteria": ["..."]
118
- }
119
-
120
- Rules:
121
- - Keep changes <= 8.
122
- - Each change must be concrete and scoped.
123
- - Acceptance criteria must be testable and derived from the ticket.
124
- - Each acceptanceCriteria item must be <= 140 characters.
125
- ${JSON_ONLY_RULES}
126
- `.trim();
127
- }
128
-
129
- //#endregion
130
- export { promptExtractEvidenceFindings, promptGenerateTickets, promptGroupProblems, promptSuggestPatchIntent };
131
- //# sourceMappingURL=ticket-prompts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ticket-prompts.js","names":[],"sources":["../src/ticket-prompts.ts"],"sourcesContent":["import { CITATION_RULES, JSON_ONLY_RULES } from './prompts';\n\nexport function promptExtractEvidenceFindings(params: {\n question: string;\n evidenceJSON: string;\n}): string {\n return `\nYou are extracting evidence findings grounded in transcript excerpts.\n\nQuestion:\n${params.question}\n\nEvidence:\n${params.evidenceJSON}\n\nReturn JSON:\n{\n \"findings\": [\n {\n \"findingId\": \"find_001\",\n \"summary\": \"...\",\n \"tags\": [\"...\"],\n \"citations\": [{ \"chunkId\": \"...\", \"quote\": \"...\" }]\n }\n ]\n}\n\nRules:\n- Produce 8 to 18 findings.\n- Each finding must include at least 1 citation.\n- Summaries must be specific and short.\n- Quotes must be copied character-for-character from the chunk text (no paraphrasing, no ellipses).\n- Preserve punctuation, smart quotes, and special hyphens exactly as shown in the chunk text.\n${CITATION_RULES}\n${JSON_ONLY_RULES}\n `.trim();\n}\n\nexport function promptGroupProblems(params: {\n question: string;\n findingsJSON: string;\n findingIds: string[];\n}): string {\n const allowed = JSON.stringify({ findingIds: params.findingIds }, null, 2);\n return `\nYou are grouping evidence findings into problem statements.\n\nQuestion:\n${params.question}\n\nFindings:\n${params.findingsJSON}\n\nAllowed finding IDs:\n${allowed}\n\nReturn JSON:\n{\n \"problems\": [\n {\n \"problemId\": \"prob_001\",\n \"statement\": \"...\",\n \"evidenceIds\": [\"find_001\"],\n \"tags\": [\"...\"],\n \"severity\": \"low|medium|high\"\n }\n ]\n}\n\nRules:\n- Each problem must reference 1 to 6 evidenceIds.\n- evidenceIds must be drawn from the allowed finding IDs.\n- Keep statements short and actionable.\n${JSON_ONLY_RULES}\n `.trim();\n}\n\nexport function promptGenerateTickets(params: {\n question: string;\n problemsJSON: string;\n findingsJSON: string;\n}): string {\n return `\nYou are generating implementation tickets grounded in evidence.\n\nQuestion:\n${params.question}\n\nProblems:\n${params.problemsJSON}\n\nEvidence findings:\n${params.findingsJSON}\n\nReturn JSON:\n{\n \"tickets\": [\n {\n \"ticketId\": \"t_001\",\n \"title\": \"...\",\n \"summary\": \"...\",\n \"evidenceIds\": [\"find_001\"],\n \"acceptanceCriteria\": [\"...\"]\n }\n ]\n}\n\nRules:\n- 1 to 2 tickets per problem.\n- Every ticket must include evidenceIds and acceptanceCriteria.\n- Acceptance criteria must be testable.\n- Each acceptanceCriteria item must be <= 160 characters.\n${JSON_ONLY_RULES}\n `.trim();\n}\n\nexport function promptSuggestPatchIntent(params: {\n ticketJSON: string;\n}): string {\n return `\nYou are generating a ContractPatchIntent from an evidence-backed ticket.\n\nTicket:\n${params.ticketJSON}\n\nReturn JSON:\n{\n \"featureKey\": \"feature_slug\",\n \"changes\": [\n { \"type\": \"add_field|remove_field|rename_field|add_event|update_event|add_operation|update_operation|update_form|update_policy|add_enum_value|remove_enum_value|other\", \"target\": \"string\", \"detail\": \"string\" }\n ],\n \"acceptanceCriteria\": [\"...\"]\n}\n\nRules:\n- Keep changes <= 8.\n- Each change must be concrete and scoped.\n- Acceptance criteria must be testable and derived from the ticket.\n- Each acceptanceCriteria item must be <= 140 characters.\n${JSON_ONLY_RULES}\n `.trim();\n}\n"],"mappings":";;;AAEA,SAAgB,8BAA8B,QAGnC;AACT,QAAO;;;;EAIP,OAAO,SAAS;;;EAGhB,OAAO,aAAa;;;;;;;;;;;;;;;;;;;;EAoBpB,eAAe;EACf,gBAAgB;IACd,MAAM;;AAGV,SAAgB,oBAAoB,QAIzB;CACT,MAAM,UAAU,KAAK,UAAU,EAAE,YAAY,OAAO,YAAY,EAAE,MAAM,EAAE;AAC1E,QAAO;;;;EAIP,OAAO,SAAS;;;EAGhB,OAAO,aAAa;;;EAGpB,QAAQ;;;;;;;;;;;;;;;;;;;EAmBR,gBAAgB;IACd,MAAM;;AAGV,SAAgB,sBAAsB,QAI3B;AACT,QAAO;;;;EAIP,OAAO,SAAS;;;EAGhB,OAAO,aAAa;;;EAGpB,OAAO,aAAa;;;;;;;;;;;;;;;;;;;;EAoBpB,gBAAgB;IACd,MAAM;;AAGV,SAAgB,yBAAyB,QAE9B;AACT,QAAO;;;;EAIP,OAAO,WAAW;;;;;;;;;;;;;;;;EAgBlB,gBAAgB;IACd,MAAM"}
@@ -1,106 +0,0 @@
1
- import { buildChunkIndex, parseStrictJSON, validateCitation } from "./validators.js";
2
- import { EvidenceFindingExtractionModel, ProblemGroupingModel, TicketCollectionModel } from "@contractspec/lib.contracts/product-intent/types";
3
-
4
- //#region src/ticket-validators.ts
5
- function assertStringLength(value, path, bounds) {
6
- if (bounds.min !== void 0 && value.length < bounds.min) throw new Error(`Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`);
7
- if (bounds.max !== void 0 && value.length > bounds.max) throw new Error(`Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`);
8
- }
9
- function assertArrayLength(value, path, bounds) {
10
- if (bounds.min !== void 0 && value.length < bounds.min) throw new Error(`Expected ${path} to have at least ${bounds.min} items, got ${value.length}`);
11
- if (bounds.max !== void 0 && value.length > bounds.max) throw new Error(`Expected ${path} to have at most ${bounds.max} items, got ${value.length}`);
12
- }
13
- function assertIdsExist(ids, allowed, path) {
14
- for (const id of ids) if (!allowed.has(id)) throw new Error(`Unknown ${path} reference: ${id}`);
15
- }
16
- function parseJSON(schema, raw) {
17
- return parseStrictJSON(schema, raw);
18
- }
19
- function validateEvidenceFindingExtraction(raw, chunks) {
20
- const chunkIndex = buildChunkIndex(chunks);
21
- const data = parseJSON(EvidenceFindingExtractionModel, raw);
22
- assertArrayLength(data.findings, "findings", {
23
- min: 1,
24
- max: 40
25
- });
26
- for (const finding of data.findings) {
27
- assertStringLength(finding.findingId, "findings[].findingId", { min: 1 });
28
- assertStringLength(finding.summary, "findings[].summary", {
29
- min: 1,
30
- max: 320
31
- });
32
- if (finding.tags) for (const tag of finding.tags) assertStringLength(tag, "findings[].tags[]", {
33
- min: 1,
34
- max: 48
35
- });
36
- assertArrayLength(finding.citations, "findings[].citations", { min: 1 });
37
- for (const citation of finding.citations) validateCitation(citation, chunkIndex);
38
- }
39
- return data;
40
- }
41
- function validateProblemGrouping(raw, findings) {
42
- const data = parseJSON(ProblemGroupingModel, raw);
43
- const allowedIds = new Set(findings.map((finding) => finding.findingId));
44
- assertArrayLength(data.problems, "problems", {
45
- min: 1,
46
- max: 20
47
- });
48
- for (const problem of data.problems) {
49
- assertStringLength(problem.problemId, "problems[].problemId", { min: 1 });
50
- assertStringLength(problem.statement, "problems[].statement", {
51
- min: 1,
52
- max: 320
53
- });
54
- assertArrayLength(problem.evidenceIds, "problems[].evidenceIds", {
55
- min: 1,
56
- max: 8
57
- });
58
- assertIdsExist(problem.evidenceIds, allowedIds, "evidenceId");
59
- if (problem.tags) for (const tag of problem.tags) assertStringLength(tag, "problems[].tags[]", {
60
- min: 1,
61
- max: 48
62
- });
63
- }
64
- return data;
65
- }
66
- function validateTicketCollection(raw, findings) {
67
- const data = parseJSON(TicketCollectionModel, raw);
68
- const allowedIds = new Set(findings.map((finding) => finding.findingId));
69
- assertArrayLength(data.tickets, "tickets", {
70
- min: 1,
71
- max: 30
72
- });
73
- for (const ticket of data.tickets) {
74
- assertStringLength(ticket.ticketId, "tickets[].ticketId", { min: 1 });
75
- assertStringLength(ticket.title, "tickets[].title", {
76
- min: 1,
77
- max: 120
78
- });
79
- assertStringLength(ticket.summary, "tickets[].summary", {
80
- min: 1,
81
- max: 320
82
- });
83
- assertArrayLength(ticket.evidenceIds, "tickets[].evidenceIds", {
84
- min: 1,
85
- max: 8
86
- });
87
- assertIdsExist(ticket.evidenceIds, allowedIds, "evidenceId");
88
- assertArrayLength(ticket.acceptanceCriteria, "tickets[].acceptanceCriteria", {
89
- min: 1,
90
- max: 8
91
- });
92
- for (const criterion of ticket.acceptanceCriteria) assertStringLength(criterion, "tickets[].acceptanceCriteria[]", {
93
- min: 1,
94
- max: 280
95
- });
96
- if (ticket.tags) for (const tag of ticket.tags) assertStringLength(tag, "tickets[].tags[]", {
97
- min: 1,
98
- max: 48
99
- });
100
- }
101
- return data;
102
- }
103
-
104
- //#endregion
105
- export { validateEvidenceFindingExtraction, validateProblemGrouping, validateTicketCollection };
106
- //# sourceMappingURL=ticket-validators.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ticket-validators.js","names":[],"sources":["../src/ticket-validators.ts"],"sourcesContent":["import type { SchemaModelType } from '@contractspec/lib.schema';\nimport {\n type EvidenceChunk,\n type EvidenceFinding,\n type EvidenceFindingExtraction,\n EvidenceFindingExtractionModel,\n type ProblemGrouping,\n ProblemGroupingModel,\n type TicketCollection,\n TicketCollectionModel,\n} from '@contractspec/lib.contracts/product-intent/types';\nimport {\n buildChunkIndex,\n parseStrictJSON,\n validateCitation,\n} from './validators';\n\ninterface LengthBounds {\n min?: number;\n max?: number;\n}\n\nfunction assertStringLength(\n value: string,\n path: string,\n bounds: LengthBounds\n): void {\n if (bounds.min !== undefined && value.length < bounds.min) {\n throw new Error(\n `Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`\n );\n }\n if (bounds.max !== undefined && value.length > bounds.max) {\n throw new Error(\n `Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`\n );\n }\n}\n\nfunction assertArrayLength<T>(\n value: T[],\n path: string,\n bounds: LengthBounds\n): void {\n if (bounds.min !== undefined && value.length < bounds.min) {\n throw new Error(\n `Expected ${path} to have at least ${bounds.min} items, got ${value.length}`\n );\n }\n if (bounds.max !== undefined && value.length > bounds.max) {\n throw new Error(\n `Expected ${path} to have at most ${bounds.max} items, got ${value.length}`\n );\n }\n}\n\nfunction assertIdsExist(\n ids: string[],\n allowed: Set<string>,\n path: string\n): void {\n for (const id of ids) {\n if (!allowed.has(id)) {\n throw new Error(`Unknown ${path} reference: ${id}`);\n }\n }\n}\n\nfunction parseJSON<T>(schema: SchemaModelType, raw: string): T {\n return parseStrictJSON<T>(schema, raw);\n}\n\nexport function validateEvidenceFindingExtraction(\n raw: string,\n chunks: EvidenceChunk[]\n): EvidenceFindingExtraction {\n const chunkIndex = buildChunkIndex(chunks);\n const data = parseJSON<EvidenceFindingExtraction>(\n EvidenceFindingExtractionModel,\n raw\n );\n\n assertArrayLength(data.findings, 'findings', { min: 1, max: 40 });\n for (const finding of data.findings) {\n assertStringLength(finding.findingId, 'findings[].findingId', { min: 1 });\n assertStringLength(finding.summary, 'findings[].summary', {\n min: 1,\n max: 320,\n });\n if (finding.tags) {\n for (const tag of finding.tags) {\n assertStringLength(tag, 'findings[].tags[]', { min: 1, max: 48 });\n }\n }\n assertArrayLength(finding.citations, 'findings[].citations', { min: 1 });\n for (const citation of finding.citations) {\n validateCitation(citation, chunkIndex);\n }\n }\n return data;\n}\n\nexport function validateProblemGrouping(\n raw: string,\n findings: EvidenceFinding[]\n): ProblemGrouping {\n const data = parseJSON<ProblemGrouping>(ProblemGroupingModel, raw);\n const allowedIds = new Set(findings.map((finding) => finding.findingId));\n\n assertArrayLength(data.problems, 'problems', { min: 1, max: 20 });\n for (const problem of data.problems) {\n assertStringLength(problem.problemId, 'problems[].problemId', { min: 1 });\n assertStringLength(problem.statement, 'problems[].statement', {\n min: 1,\n max: 320,\n });\n assertArrayLength(problem.evidenceIds, 'problems[].evidenceIds', {\n min: 1,\n max: 8,\n });\n assertIdsExist(problem.evidenceIds, allowedIds, 'evidenceId');\n if (problem.tags) {\n for (const tag of problem.tags) {\n assertStringLength(tag, 'problems[].tags[]', { min: 1, max: 48 });\n }\n }\n }\n return data;\n}\n\nexport function validateTicketCollection(\n raw: string,\n findings: EvidenceFinding[]\n): TicketCollection {\n const data = parseJSON<TicketCollection>(TicketCollectionModel, raw);\n const allowedIds = new Set(findings.map((finding) => finding.findingId));\n\n assertArrayLength(data.tickets, 'tickets', { min: 1, max: 30 });\n for (const ticket of data.tickets) {\n assertStringLength(ticket.ticketId, 'tickets[].ticketId', { min: 1 });\n assertStringLength(ticket.title, 'tickets[].title', { min: 1, max: 120 });\n assertStringLength(ticket.summary, 'tickets[].summary', {\n min: 1,\n max: 320,\n });\n assertArrayLength(ticket.evidenceIds, 'tickets[].evidenceIds', {\n min: 1,\n max: 8,\n });\n assertIdsExist(ticket.evidenceIds, allowedIds, 'evidenceId');\n assertArrayLength(\n ticket.acceptanceCriteria,\n 'tickets[].acceptanceCriteria',\n {\n min: 1,\n max: 8,\n }\n );\n for (const criterion of ticket.acceptanceCriteria) {\n assertStringLength(criterion, 'tickets[].acceptanceCriteria[]', {\n min: 1,\n // max: 160,\n max: 280,\n });\n }\n if (ticket.tags) {\n for (const tag of ticket.tags) {\n assertStringLength(tag, 'tickets[].tags[]', { min: 1, max: 48 });\n }\n }\n }\n return data;\n}\n"],"mappings":"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"}
@@ -1,277 +0,0 @@
1
- import { CitationModel, ContractPatchIntentModel, ImpactReportModel, InsightExtractionModel, OpportunityBriefModel, TaskPackModel } from "@contractspec/lib.contracts/product-intent/types";
2
-
3
- //#region src/validators.ts
4
- function assertStringLength(value, path, bounds) {
5
- if (bounds.min !== void 0 && value.length < bounds.min) throw new Error(`Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`);
6
- if (bounds.max !== void 0 && value.length > bounds.max) throw new Error(`Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`);
7
- }
8
- function assertArrayLength(value, path, bounds) {
9
- if (bounds.min !== void 0 && value.length < bounds.min) throw new Error(`Expected ${path} to have at least ${bounds.min} items, got ${value.length}`);
10
- if (bounds.max !== void 0 && value.length > bounds.max) throw new Error(`Expected ${path} to have at most ${bounds.max} items, got ${value.length}`);
11
- }
12
- function assertNumberRange(value, path, bounds) {
13
- if (bounds.min !== void 0 && value < bounds.min) throw new Error(`Expected ${path} to be >= ${bounds.min}, got ${value}`);
14
- if (bounds.max !== void 0 && value > bounds.max) throw new Error(`Expected ${path} to be <= ${bounds.max}, got ${value}`);
15
- }
16
- /**
17
- * Parse a raw string as JSON and validate it against a SchemaModelType.
18
- */
19
- function parseStrictJSON(schema, raw) {
20
- const trimmed = raw.trim();
21
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) throw new Error("Model did not return JSON (missing leading { or [)");
22
- let parsed;
23
- try {
24
- parsed = JSON.parse(trimmed);
25
- } catch (error) {
26
- const message = error instanceof Error ? error.message : String(error);
27
- throw new Error(`Invalid JSON: ${message}`);
28
- }
29
- return schema.getZod().parse(parsed);
30
- }
31
- /**
32
- * Build an index of evidence chunks keyed by chunkId.
33
- */
34
- function buildChunkIndex(chunks) {
35
- const map = /* @__PURE__ */ new Map();
36
- for (const chunk of chunks) map.set(chunk.chunkId, chunk);
37
- return map;
38
- }
39
- /**
40
- * Validate a single citation. Ensures the referenced chunk exists and
41
- * the quoted text is an exact substring of the chunk text.
42
- */
43
- function validateCitation(citation, chunkIndex, opts) {
44
- const maxQuoteLen = opts?.maxQuoteLen ?? 240;
45
- const requireExactSubstring = opts?.requireExactSubstring ?? true;
46
- const parsed = CitationModel.getZod().parse(citation);
47
- assertStringLength(parsed.quote, "citation.quote", {
48
- min: 1,
49
- max: maxQuoteLen
50
- });
51
- const chunk = chunkIndex.get(parsed.chunkId);
52
- if (!chunk) throw new Error(`Citation references unknown chunkId: ${parsed.chunkId}`);
53
- if (requireExactSubstring && !chunk.text.includes(parsed.quote)) throw new Error(`Citation quote is not an exact substring of chunk ${parsed.chunkId}`);
54
- return parsed;
55
- }
56
- /**
57
- * Validate citations embedded within a text block.
58
- */
59
- function validateCitationsInTextBlock(block, chunkIndex) {
60
- assertStringLength(block.text, "textBlock.text", { min: 1 });
61
- if (!block.citations?.length) throw new Error("Missing required citations");
62
- const citations = block.citations.map((c) => validateCitation(c, chunkIndex));
63
- return {
64
- text: block.text,
65
- citations
66
- };
67
- }
68
- /**
69
- * Validate a raw JSON string as an OpportunityBrief, ensuring citations
70
- * reference actual chunks and quotes are exact substrings.
71
- */
72
- function validateOpportunityBrief(raw, chunks) {
73
- const chunkIndex = buildChunkIndex(chunks);
74
- const brief = parseStrictJSON(OpportunityBriefModel, raw);
75
- assertStringLength(brief.opportunityId, "opportunityId", { min: 1 });
76
- assertStringLength(brief.title, "title", {
77
- min: 1,
78
- max: 120
79
- });
80
- validateCitationsInTextBlock(brief.problem, chunkIndex);
81
- validateCitationsInTextBlock(brief.who, chunkIndex);
82
- validateCitationsInTextBlock(brief.proposedChange, chunkIndex);
83
- assertStringLength(brief.expectedImpact.metric, "expectedImpact.metric", {
84
- min: 1,
85
- max: 64
86
- });
87
- if (brief.expectedImpact.magnitudeHint) assertStringLength(brief.expectedImpact.magnitudeHint, "expectedImpact.magnitudeHint", { max: 64 });
88
- if (brief.expectedImpact.timeframeHint) assertStringLength(brief.expectedImpact.timeframeHint, "expectedImpact.timeframeHint", { max: 64 });
89
- if (brief.risks) for (const risk of brief.risks) {
90
- assertStringLength(risk.text, "risks[].text", {
91
- min: 1,
92
- max: 240
93
- });
94
- if (risk.citations) for (const c of risk.citations) validateCitation(c, chunkIndex);
95
- }
96
- return brief;
97
- }
98
- /**
99
- * Validate a raw JSON string as an InsightExtraction.
100
- */
101
- function validateInsightExtraction(raw, chunks) {
102
- const chunkIndex = buildChunkIndex(chunks);
103
- const data = parseStrictJSON(InsightExtractionModel, raw);
104
- assertArrayLength(data.insights, "insights", {
105
- min: 1,
106
- max: 30
107
- });
108
- for (const insight of data.insights) {
109
- assertStringLength(insight.insightId, "insights[].insightId", { min: 1 });
110
- assertStringLength(insight.claim, "insights[].claim", {
111
- min: 1,
112
- max: 320
113
- });
114
- if (insight.tags) for (const tag of insight.tags) assertStringLength(tag, "insights[].tags[]", { min: 1 });
115
- if (insight.confidence !== void 0) assertNumberRange(insight.confidence, "insights[].confidence", {
116
- min: 0,
117
- max: 1
118
- });
119
- assertArrayLength(insight.citations, "insights[].citations", { min: 1 });
120
- for (const c of insight.citations) validateCitation(c, chunkIndex);
121
- }
122
- return data;
123
- }
124
- /**
125
- * Validate a raw JSON string as a ContractPatchIntent.
126
- */
127
- function validatePatchIntent(raw) {
128
- const data = parseStrictJSON(ContractPatchIntentModel, raw);
129
- assertStringLength(data.featureKey, "featureKey", {
130
- min: 1,
131
- max: 80
132
- });
133
- assertArrayLength(data.changes, "changes", {
134
- min: 1,
135
- max: 25
136
- });
137
- for (const change of data.changes) {
138
- assertStringLength(change.target, "changes[].target", { min: 1 });
139
- assertStringLength(change.detail, "changes[].detail", { min: 1 });
140
- }
141
- assertArrayLength(data.acceptanceCriteria, "acceptanceCriteria", {
142
- min: 1,
143
- max: 12
144
- });
145
- for (const item of data.acceptanceCriteria) assertStringLength(item, "acceptanceCriteria[]", {
146
- min: 1,
147
- max: 140
148
- });
149
- return data;
150
- }
151
- /**
152
- * Validate a raw JSON string as an ImpactReport.
153
- */
154
- function validateImpactReport(raw) {
155
- const data = parseStrictJSON(ImpactReportModel, raw);
156
- assertStringLength(data.reportId, "reportId", { min: 1 });
157
- assertStringLength(data.patchId, "patchId", { min: 1 });
158
- assertStringLength(data.summary, "summary", {
159
- min: 1,
160
- max: 200
161
- });
162
- if (data.breaks) for (const item of data.breaks) assertStringLength(item, "breaks[]", {
163
- min: 1,
164
- max: 160
165
- });
166
- if (data.mustChange) for (const item of data.mustChange) assertStringLength(item, "mustChange[]", {
167
- min: 1,
168
- max: 160
169
- });
170
- if (data.risky) for (const item of data.risky) assertStringLength(item, "risky[]", {
171
- min: 1,
172
- max: 160
173
- });
174
- const surfaces = data.surfaces;
175
- if (surfaces.api) for (const item of surfaces.api) assertStringLength(item, "surfaces.api[]", {
176
- min: 1,
177
- max: 140
178
- });
179
- if (surfaces.db) for (const item of surfaces.db) assertStringLength(item, "surfaces.db[]", {
180
- min: 1,
181
- max: 140
182
- });
183
- if (surfaces.ui) for (const item of surfaces.ui) assertStringLength(item, "surfaces.ui[]", {
184
- min: 1,
185
- max: 140
186
- });
187
- if (surfaces.workflows) for (const item of surfaces.workflows) assertStringLength(item, "surfaces.workflows[]", {
188
- min: 1,
189
- max: 140
190
- });
191
- if (surfaces.policy) for (const item of surfaces.policy) assertStringLength(item, "surfaces.policy[]", {
192
- min: 1,
193
- max: 140
194
- });
195
- if (surfaces.docs) for (const item of surfaces.docs) assertStringLength(item, "surfaces.docs[]", {
196
- min: 1,
197
- max: 140
198
- });
199
- if (surfaces.tests) for (const item of surfaces.tests) assertStringLength(item, "surfaces.tests[]", {
200
- min: 1,
201
- max: 140
202
- });
203
- return data;
204
- }
205
- /**
206
- * Validate a raw JSON string as a TaskPack.
207
- */
208
- function validateTaskPack(raw) {
209
- const data = parseStrictJSON(TaskPackModel, raw);
210
- assertStringLength(data.packId, "packId", { min: 1 });
211
- assertStringLength(data.patchId, "patchId", { min: 1 });
212
- assertStringLength(data.overview, "overview", {
213
- min: 1,
214
- max: 240
215
- });
216
- assertArrayLength(data.tasks, "tasks", {
217
- min: 3,
218
- max: 14
219
- });
220
- for (const task of data.tasks) {
221
- assertStringLength(task.id, "tasks[].id", { min: 1 });
222
- assertStringLength(task.title, "tasks[].title", {
223
- min: 1,
224
- max: 120
225
- });
226
- assertArrayLength(task.surface, "tasks[].surface", { min: 1 });
227
- assertStringLength(task.why, "tasks[].why", {
228
- min: 1,
229
- max: 200
230
- });
231
- assertArrayLength(task.acceptance, "tasks[].acceptance", {
232
- min: 1,
233
- max: 10
234
- });
235
- for (const criterion of task.acceptance) assertStringLength(criterion, "tasks[].acceptance[]", {
236
- min: 1,
237
- max: 160
238
- });
239
- assertStringLength(task.agentPrompt, "tasks[].agentPrompt", {
240
- min: 1,
241
- max: 1400
242
- });
243
- if (task.dependsOn) for (const dep of task.dependsOn) assertStringLength(dep, "tasks[].dependsOn[]", { min: 1 });
244
- }
245
- return data;
246
- }
247
- /**
248
- * Build a repair prompt from a validation error.
249
- */
250
- function buildRepairPrompt(error) {
251
- return [
252
- "Your previous output failed validation.",
253
- "Fix the output and return JSON ONLY (no markdown, no commentary).",
254
- "Validation error:",
255
- error
256
- ].join("\n");
257
- }
258
- function truncateText(value, maxChars) {
259
- if (value.length <= maxChars) return value;
260
- return `${value.slice(0, maxChars)}\n...(truncated)`;
261
- }
262
- function buildRepairPromptWithOutput(error, previousOutput, maxOutputChars = 4e3) {
263
- return [
264
- "Your previous output failed validation.",
265
- "Fix the output and return JSON ONLY (no markdown, no commentary).",
266
- "Do not change the JSON shape or rename fields.",
267
- "If a citation quote is invalid, replace it with an exact substring from the referenced chunk.",
268
- "Validation error:",
269
- error,
270
- "Previous output:",
271
- truncateText(previousOutput, maxOutputChars)
272
- ].join("\n");
273
- }
274
-
275
- //#endregion
276
- export { buildChunkIndex, buildRepairPrompt, buildRepairPromptWithOutput, parseStrictJSON, validateCitation, validateCitationsInTextBlock, validateImpactReport, validateInsightExtraction, validateOpportunityBrief, validatePatchIntent, validateTaskPack };
277
- //# sourceMappingURL=validators.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validators.js","names":[],"sources":["../src/validators.ts"],"sourcesContent":["import type { SchemaModelType } from '@contractspec/lib.schema';\nimport {\n CitationModel,\n type ContractPatchIntent,\n ContractPatchIntentModel,\n type EvidenceChunk,\n type ImpactReport,\n ImpactReportModel,\n type InsightExtraction,\n InsightExtractionModel,\n type OpportunityBrief,\n OpportunityBriefModel,\n type TaskPack,\n TaskPackModel,\n} from '@contractspec/lib.contracts/product-intent/types';\n\ninterface LengthBounds {\n min?: number;\n max?: number;\n}\n\nfunction assertStringLength(\n value: string,\n path: string,\n bounds: LengthBounds\n): void {\n if (bounds.min !== undefined && value.length < bounds.min) {\n throw new Error(\n `Expected ${path} to be at least ${bounds.min} characters, got ${value.length}`\n );\n }\n if (bounds.max !== undefined && value.length > bounds.max) {\n throw new Error(\n `Expected ${path} to be at most ${bounds.max} characters, got ${value.length}`\n );\n }\n}\n\nfunction assertArrayLength<T>(\n value: T[],\n path: string,\n bounds: LengthBounds\n): void {\n if (bounds.min !== undefined && value.length < bounds.min) {\n throw new Error(\n `Expected ${path} to have at least ${bounds.min} items, got ${value.length}`\n );\n }\n if (bounds.max !== undefined && value.length > bounds.max) {\n throw new Error(\n `Expected ${path} to have at most ${bounds.max} items, got ${value.length}`\n );\n }\n}\n\nfunction assertNumberRange(\n value: number,\n path: string,\n bounds: { min?: number; max?: number }\n): void {\n if (bounds.min !== undefined && value < bounds.min) {\n throw new Error(`Expected ${path} to be >= ${bounds.min}, got ${value}`);\n }\n if (bounds.max !== undefined && value > bounds.max) {\n throw new Error(`Expected ${path} to be <= ${bounds.max}, got ${value}`);\n }\n}\n\n/**\n * Parse a raw string as JSON and validate it against a SchemaModelType.\n */\nexport function parseStrictJSON<T>(schema: SchemaModelType, raw: string): T {\n const trimmed = raw.trim();\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n throw new Error('Model did not return JSON (missing leading { or [)');\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid JSON: ${message}`);\n }\n return schema.getZod().parse(parsed) as T;\n}\n\n/**\n * Build an index of evidence chunks keyed by chunkId.\n */\nexport function buildChunkIndex(\n chunks: EvidenceChunk[]\n): Map<string, EvidenceChunk> {\n const map = new Map<string, EvidenceChunk>();\n for (const chunk of chunks) {\n map.set(chunk.chunkId, chunk);\n }\n return map;\n}\n\n/**\n * Validate a single citation. Ensures the referenced chunk exists and\n * the quoted text is an exact substring of the chunk text.\n */\nexport function validateCitation(\n citation: unknown,\n chunkIndex: Map<string, EvidenceChunk>,\n opts?: { maxQuoteLen?: number; requireExactSubstring?: boolean }\n) {\n const maxQuoteLen = opts?.maxQuoteLen ?? 240;\n const requireExactSubstring = opts?.requireExactSubstring ?? true;\n const parsed = CitationModel.getZod().parse(citation);\n\n assertStringLength(parsed.quote, 'citation.quote', {\n min: 1,\n max: maxQuoteLen,\n });\n\n const chunk = chunkIndex.get(parsed.chunkId);\n if (!chunk) {\n throw new Error(`Citation references unknown chunkId: ${parsed.chunkId}`);\n }\n if (requireExactSubstring && !chunk.text.includes(parsed.quote)) {\n throw new Error(\n `Citation quote is not an exact substring of chunk ${parsed.chunkId}`\n );\n }\n return parsed;\n}\n\n/**\n * Validate citations embedded within a text block.\n */\nexport function validateCitationsInTextBlock(\n block: { text: string; citations: unknown[] },\n chunkIndex: Map<string, EvidenceChunk>\n) {\n assertStringLength(block.text, 'textBlock.text', { min: 1 });\n if (!block.citations?.length) {\n throw new Error('Missing required citations');\n }\n const citations = block.citations.map((c) => validateCitation(c, chunkIndex));\n return { text: block.text, citations };\n}\n\n/**\n * Validate a raw JSON string as an OpportunityBrief, ensuring citations\n * reference actual chunks and quotes are exact substrings.\n */\nexport function validateOpportunityBrief(\n raw: string,\n chunks: EvidenceChunk[]\n): OpportunityBrief {\n const chunkIndex = buildChunkIndex(chunks);\n const brief = parseStrictJSON<OpportunityBrief>(OpportunityBriefModel, raw);\n\n assertStringLength(brief.opportunityId, 'opportunityId', { min: 1 });\n assertStringLength(brief.title, 'title', { min: 1, max: 120 });\n\n validateCitationsInTextBlock(brief.problem, chunkIndex);\n validateCitationsInTextBlock(brief.who, chunkIndex);\n validateCitationsInTextBlock(brief.proposedChange, chunkIndex);\n\n assertStringLength(brief.expectedImpact.metric, 'expectedImpact.metric', {\n min: 1,\n max: 64,\n });\n if (brief.expectedImpact.magnitudeHint) {\n assertStringLength(\n brief.expectedImpact.magnitudeHint,\n 'expectedImpact.magnitudeHint',\n { max: 64 }\n );\n }\n if (brief.expectedImpact.timeframeHint) {\n assertStringLength(\n brief.expectedImpact.timeframeHint,\n 'expectedImpact.timeframeHint',\n { max: 64 }\n );\n }\n\n if (brief.risks) {\n for (const risk of brief.risks) {\n assertStringLength(risk.text, 'risks[].text', { min: 1, max: 240 });\n if (risk.citations) {\n for (const c of risk.citations) {\n validateCitation(c, chunkIndex);\n }\n }\n }\n }\n return brief;\n}\n\n/**\n * Validate a raw JSON string as an InsightExtraction.\n */\nexport function validateInsightExtraction(\n raw: string,\n chunks: EvidenceChunk[]\n): InsightExtraction {\n const chunkIndex = buildChunkIndex(chunks);\n const data = parseStrictJSON<InsightExtraction>(InsightExtractionModel, raw);\n\n assertArrayLength(data.insights, 'insights', { min: 1, max: 30 });\n for (const insight of data.insights) {\n assertStringLength(insight.insightId, 'insights[].insightId', { min: 1 });\n assertStringLength(insight.claim, 'insights[].claim', {\n min: 1,\n max: 320,\n });\n if (insight.tags) {\n for (const tag of insight.tags) {\n assertStringLength(tag, 'insights[].tags[]', { min: 1 });\n }\n }\n if (insight.confidence !== undefined) {\n assertNumberRange(insight.confidence, 'insights[].confidence', {\n min: 0,\n max: 1,\n });\n }\n assertArrayLength(insight.citations, 'insights[].citations', { min: 1 });\n for (const c of insight.citations) {\n validateCitation(c, chunkIndex);\n }\n }\n return data;\n}\n\n/**\n * Validate a raw JSON string as a ContractPatchIntent.\n */\nexport function validatePatchIntent(raw: string): ContractPatchIntent {\n const data = parseStrictJSON<ContractPatchIntent>(\n ContractPatchIntentModel,\n raw\n );\n\n assertStringLength(data.featureKey, 'featureKey', { min: 1, max: 80 });\n assertArrayLength(data.changes, 'changes', { min: 1, max: 25 });\n for (const change of data.changes) {\n assertStringLength(change.target, 'changes[].target', { min: 1 });\n assertStringLength(change.detail, 'changes[].detail', { min: 1 });\n }\n assertArrayLength(data.acceptanceCriteria, 'acceptanceCriteria', {\n min: 1,\n max: 12,\n });\n for (const item of data.acceptanceCriteria) {\n assertStringLength(item, 'acceptanceCriteria[]', { min: 1, max: 140 });\n }\n return data;\n}\n\n/**\n * Validate a raw JSON string as an ImpactReport.\n */\nexport function validateImpactReport(raw: string): ImpactReport {\n const data = parseStrictJSON<ImpactReport>(ImpactReportModel, raw);\n\n assertStringLength(data.reportId, 'reportId', { min: 1 });\n assertStringLength(data.patchId, 'patchId', { min: 1 });\n assertStringLength(data.summary, 'summary', { min: 1, max: 200 });\n\n if (data.breaks) {\n for (const item of data.breaks) {\n assertStringLength(item, 'breaks[]', { min: 1, max: 160 });\n }\n }\n if (data.mustChange) {\n for (const item of data.mustChange) {\n assertStringLength(item, 'mustChange[]', { min: 1, max: 160 });\n }\n }\n if (data.risky) {\n for (const item of data.risky) {\n assertStringLength(item, 'risky[]', { min: 1, max: 160 });\n }\n }\n const surfaces = data.surfaces;\n if (surfaces.api) {\n for (const item of surfaces.api) {\n assertStringLength(item, 'surfaces.api[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.db) {\n for (const item of surfaces.db) {\n assertStringLength(item, 'surfaces.db[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.ui) {\n for (const item of surfaces.ui) {\n assertStringLength(item, 'surfaces.ui[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.workflows) {\n for (const item of surfaces.workflows) {\n assertStringLength(item, 'surfaces.workflows[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.policy) {\n for (const item of surfaces.policy) {\n assertStringLength(item, 'surfaces.policy[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.docs) {\n for (const item of surfaces.docs) {\n assertStringLength(item, 'surfaces.docs[]', { min: 1, max: 140 });\n }\n }\n if (surfaces.tests) {\n for (const item of surfaces.tests) {\n assertStringLength(item, 'surfaces.tests[]', { min: 1, max: 140 });\n }\n }\n return data;\n}\n\n/**\n * Validate a raw JSON string as a TaskPack.\n */\nexport function validateTaskPack(raw: string): TaskPack {\n const data = parseStrictJSON<TaskPack>(TaskPackModel, raw);\n\n assertStringLength(data.packId, 'packId', { min: 1 });\n assertStringLength(data.patchId, 'patchId', { min: 1 });\n assertStringLength(data.overview, 'overview', { min: 1, max: 240 });\n assertArrayLength(data.tasks, 'tasks', { min: 3, max: 14 });\n\n for (const task of data.tasks) {\n assertStringLength(task.id, 'tasks[].id', { min: 1 });\n assertStringLength(task.title, 'tasks[].title', { min: 1, max: 120 });\n assertArrayLength(task.surface, 'tasks[].surface', { min: 1 });\n assertStringLength(task.why, 'tasks[].why', { min: 1, max: 200 });\n assertArrayLength(task.acceptance, 'tasks[].acceptance', {\n min: 1,\n max: 10,\n });\n for (const criterion of task.acceptance) {\n assertStringLength(criterion, 'tasks[].acceptance[]', {\n min: 1,\n max: 160,\n });\n }\n assertStringLength(task.agentPrompt, 'tasks[].agentPrompt', {\n min: 1,\n max: 1400,\n });\n if (task.dependsOn) {\n for (const dep of task.dependsOn) {\n assertStringLength(dep, 'tasks[].dependsOn[]', { min: 1 });\n }\n }\n }\n return data;\n}\n\n/**\n * Build a repair prompt from a validation error.\n */\nexport function buildRepairPrompt(error: string): string {\n return [\n 'Your previous output failed validation.',\n 'Fix the output and return JSON ONLY (no markdown, no commentary).',\n 'Validation error:',\n error,\n ].join('\\n');\n}\n\nfunction truncateText(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, maxChars)}\\n...(truncated)`;\n}\n\nexport function buildRepairPromptWithOutput(\n error: string,\n previousOutput: string,\n maxOutputChars = 4000\n): string {\n return [\n 'Your previous output failed validation.',\n 'Fix the output and return JSON ONLY (no markdown, no commentary).',\n 'Do not change the JSON shape or rename fields.',\n 'If a citation quote is invalid, replace it with an exact substring from the referenced chunk.',\n 'Validation error:',\n error,\n 'Previous output:',\n truncateText(previousOutput, maxOutputChars),\n ].join('\\n');\n}\n"],"mappings":"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"}