@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.
- package/dist/browser/index.js +1592 -0
- package/dist/impact-engine.d.ts +10 -14
- package/dist/impact-engine.d.ts.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1593 -10
- package/dist/node/index.js +1592 -0
- package/dist/project-management-sync.d.ts +17 -21
- package/dist/project-management-sync.d.ts.map +1 -1
- package/dist/prompts.d.ts +41 -45
- package/dist/prompts.d.ts.map +1 -1
- package/dist/ticket-pipeline-runner.d.ts +22 -25
- package/dist/ticket-pipeline-runner.d.ts.map +1 -1
- package/dist/ticket-pipeline.d.ts +29 -32
- package/dist/ticket-pipeline.d.ts.map +1 -1
- package/dist/ticket-prompts.d.ts +13 -16
- package/dist/ticket-prompts.d.ts.map +1 -1
- package/dist/ticket-validators.d.ts +4 -8
- package/dist/ticket-validators.d.ts.map +1 -1
- package/dist/validators.d.ts +24 -28
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.test.d.ts +2 -0
- package/dist/validators.test.d.ts.map +1 -0
- package/package.json +20 -15
- package/dist/impact-engine.js +0 -168
- package/dist/impact-engine.js.map +0 -1
- package/dist/project-management-sync.js +0 -80
- package/dist/project-management-sync.js.map +0 -1
- package/dist/prompts.js +0 -372
- package/dist/prompts.js.map +0 -1
- package/dist/schema/dist/SchemaModelType.d.ts +0 -50
- package/dist/schema/dist/SchemaModelType.d.ts.map +0 -1
- package/dist/ticket-pipeline-runner.js +0 -97
- package/dist/ticket-pipeline-runner.js.map +0 -1
- package/dist/ticket-pipeline.js +0 -425
- package/dist/ticket-pipeline.js.map +0 -1
- package/dist/ticket-prompts.js +0 -131
- package/dist/ticket-prompts.js.map +0 -1
- package/dist/ticket-validators.js +0 -106
- package/dist/ticket-validators.js.map +0 -1
- package/dist/validators.js +0 -277
- package/dist/validators.js.map +0 -1
package/dist/ticket-prompts.js
DELETED
|
@@ -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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"}
|
package/dist/validators.js
DELETED
|
@@ -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
|
package/dist/validators.js.map
DELETED
|
@@ -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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"}
|