open-agreements 0.1.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 (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/bin/open-agreements.js +2 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +102 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/commands/fill.d.ts +7 -0
  9. package/dist/commands/fill.d.ts.map +1 -0
  10. package/dist/commands/fill.js +84 -0
  11. package/dist/commands/fill.js.map +1 -0
  12. package/dist/commands/list.d.ts +6 -0
  13. package/dist/commands/list.d.ts.map +1 -0
  14. package/dist/commands/list.js +202 -0
  15. package/dist/commands/list.js.map +1 -0
  16. package/dist/commands/recipe.d.ts +21 -0
  17. package/dist/commands/recipe.d.ts.map +1 -0
  18. package/dist/commands/recipe.js +71 -0
  19. package/dist/commands/recipe.js.map +1 -0
  20. package/dist/commands/scan.d.ts +12 -0
  21. package/dist/commands/scan.d.ts.map +1 -0
  22. package/dist/commands/scan.js +122 -0
  23. package/dist/commands/scan.js.map +1 -0
  24. package/dist/commands/validate.d.ts +6 -0
  25. package/dist/commands/validate.d.ts.map +1 -0
  26. package/dist/commands/validate.js +139 -0
  27. package/dist/commands/validate.js.map +1 -0
  28. package/dist/core/command-generation/adapters/claude.d.ts +11 -0
  29. package/dist/core/command-generation/adapters/claude.d.ts.map +1 -0
  30. package/dist/core/command-generation/adapters/claude.js +85 -0
  31. package/dist/core/command-generation/adapters/claude.js.map +1 -0
  32. package/dist/core/command-generation/types.d.ts +14 -0
  33. package/dist/core/command-generation/types.d.ts.map +1 -0
  34. package/dist/core/command-generation/types.js +2 -0
  35. package/dist/core/command-generation/types.js.map +1 -0
  36. package/dist/core/engine.d.ts +13 -0
  37. package/dist/core/engine.d.ts.map +1 -0
  38. package/dist/core/engine.js +149 -0
  39. package/dist/core/engine.js.map +1 -0
  40. package/dist/core/external/index.d.ts +8 -0
  41. package/dist/core/external/index.d.ts.map +1 -0
  42. package/dist/core/external/index.js +92 -0
  43. package/dist/core/external/index.js.map +1 -0
  44. package/dist/core/external/types.d.ts +18 -0
  45. package/dist/core/external/types.d.ts.map +1 -0
  46. package/dist/core/external/types.js +2 -0
  47. package/dist/core/external/types.js.map +1 -0
  48. package/dist/core/fill-pipeline.d.ts +61 -0
  49. package/dist/core/fill-pipeline.d.ts.map +1 -0
  50. package/dist/core/fill-pipeline.js +279 -0
  51. package/dist/core/fill-pipeline.js.map +1 -0
  52. package/dist/core/fill-utils.d.ts +39 -0
  53. package/dist/core/fill-utils.d.ts.map +1 -0
  54. package/dist/core/fill-utils.js +127 -0
  55. package/dist/core/fill-utils.js.map +1 -0
  56. package/dist/core/metadata.d.ts +396 -0
  57. package/dist/core/metadata.d.ts.map +1 -0
  58. package/dist/core/metadata.js +126 -0
  59. package/dist/core/metadata.js.map +1 -0
  60. package/dist/core/recipe/cleaner.d.ts +13 -0
  61. package/dist/core/recipe/cleaner.d.ts.map +1 -0
  62. package/dist/core/recipe/cleaner.js +106 -0
  63. package/dist/core/recipe/cleaner.js.map +1 -0
  64. package/dist/core/recipe/downloader.d.ts +8 -0
  65. package/dist/core/recipe/downloader.d.ts.map +1 -0
  66. package/dist/core/recipe/downloader.js +58 -0
  67. package/dist/core/recipe/downloader.js.map +1 -0
  68. package/dist/core/recipe/index.d.ts +14 -0
  69. package/dist/core/recipe/index.d.ts.map +1 -0
  70. package/dist/core/recipe/index.js +91 -0
  71. package/dist/core/recipe/index.js.map +1 -0
  72. package/dist/core/recipe/ooxml-parts.d.ts +21 -0
  73. package/dist/core/recipe/ooxml-parts.d.ts.map +1 -0
  74. package/dist/core/recipe/ooxml-parts.js +33 -0
  75. package/dist/core/recipe/ooxml-parts.js.map +1 -0
  76. package/dist/core/recipe/patcher.d.ts +17 -0
  77. package/dist/core/recipe/patcher.d.ts.map +1 -0
  78. package/dist/core/recipe/patcher.js +240 -0
  79. package/dist/core/recipe/patcher.js.map +1 -0
  80. package/dist/core/recipe/types.d.ts +28 -0
  81. package/dist/core/recipe/types.d.ts.map +1 -0
  82. package/dist/core/recipe/types.js +2 -0
  83. package/dist/core/recipe/types.js.map +1 -0
  84. package/dist/core/recipe/verifier.d.ts +24 -0
  85. package/dist/core/recipe/verifier.d.ts.map +1 -0
  86. package/dist/core/recipe/verifier.js +143 -0
  87. package/dist/core/recipe/verifier.js.map +1 -0
  88. package/dist/core/validation/external.d.ts +16 -0
  89. package/dist/core/validation/external.d.ts.map +1 -0
  90. package/dist/core/validation/external.js +106 -0
  91. package/dist/core/validation/external.js.map +1 -0
  92. package/dist/core/validation/license.d.ts +15 -0
  93. package/dist/core/validation/license.d.ts.map +1 -0
  94. package/dist/core/validation/license.js +30 -0
  95. package/dist/core/validation/license.js.map +1 -0
  96. package/dist/core/validation/output.d.ts +12 -0
  97. package/dist/core/validation/output.d.ts.map +1 -0
  98. package/dist/core/validation/output.js +47 -0
  99. package/dist/core/validation/output.js.map +1 -0
  100. package/dist/core/validation/recipe.d.ts +19 -0
  101. package/dist/core/validation/recipe.d.ts.map +1 -0
  102. package/dist/core/validation/recipe.js +148 -0
  103. package/dist/core/validation/recipe.js.map +1 -0
  104. package/dist/core/validation/template.d.ts +11 -0
  105. package/dist/core/validation/template.d.ts.map +1 -0
  106. package/dist/core/validation/template.js +159 -0
  107. package/dist/core/validation/template.js.map +1 -0
  108. package/dist/index.d.ts +13 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +19 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/utils/paths.d.ts +15 -0
  113. package/dist/utils/paths.d.ts.map +1 -0
  114. package/dist/utils/paths.js +43 -0
  115. package/dist/utils/paths.js.map +1 -0
  116. package/external/LICENSE +27 -0
  117. package/external/README.md +38 -0
  118. package/external/yc-safe-discount/README.md +16 -0
  119. package/external/yc-safe-discount/clean.json +4 -0
  120. package/external/yc-safe-discount/metadata.yaml +71 -0
  121. package/external/yc-safe-discount/replacements.json +13 -0
  122. package/external/yc-safe-discount/template.docx +0 -0
  123. package/external/yc-safe-mfn/README.md +16 -0
  124. package/external/yc-safe-mfn/clean.json +4 -0
  125. package/external/yc-safe-mfn/metadata.yaml +64 -0
  126. package/external/yc-safe-mfn/replacements.json +12 -0
  127. package/external/yc-safe-mfn/template.docx +0 -0
  128. package/external/yc-safe-pro-rata-side-letter/README.md +16 -0
  129. package/external/yc-safe-pro-rata-side-letter/clean.json +4 -0
  130. package/external/yc-safe-pro-rata-side-letter/metadata.yaml +49 -0
  131. package/external/yc-safe-pro-rata-side-letter/replacements.json +9 -0
  132. package/external/yc-safe-pro-rata-side-letter/template.docx +0 -0
  133. package/external/yc-safe-valuation-cap/README.md +16 -0
  134. package/external/yc-safe-valuation-cap/clean.json +4 -0
  135. package/external/yc-safe-valuation-cap/metadata.yaml +64 -0
  136. package/external/yc-safe-valuation-cap/replacements.json +12 -0
  137. package/external/yc-safe-valuation-cap/template.docx +0 -0
  138. package/package.json +77 -0
  139. package/recipes/nvca-certificate-of-incorporation/clean.json +8 -0
  140. package/recipes/nvca-certificate-of-incorporation/metadata.yaml +43 -0
  141. package/recipes/nvca-certificate-of-incorporation/replacements.json +9 -0
  142. package/recipes/nvca-certificate-of-incorporation/schema.json +11 -0
  143. package/recipes/nvca-indemnification-agreement/clean.json +7 -0
  144. package/recipes/nvca-indemnification-agreement/metadata.yaml +83 -0
  145. package/recipes/nvca-indemnification-agreement/replacements.json +17 -0
  146. package/recipes/nvca-indemnification-agreement/schema.json +19 -0
  147. package/recipes/nvca-investors-rights-agreement/clean.json +12 -0
  148. package/recipes/nvca-investors-rights-agreement/metadata.yaml +75 -0
  149. package/recipes/nvca-investors-rights-agreement/replacements.json +18 -0
  150. package/recipes/nvca-investors-rights-agreement/schema.json +18 -0
  151. package/recipes/nvca-management-rights-letter/clean.json +7 -0
  152. package/recipes/nvca-management-rights-letter/metadata.yaml +50 -0
  153. package/recipes/nvca-management-rights-letter/replacements.json +11 -0
  154. package/recipes/nvca-management-rights-letter/schema.json +13 -0
  155. package/recipes/nvca-rofr-co-sale-agreement/clean.json +7 -0
  156. package/recipes/nvca-rofr-co-sale-agreement/metadata.yaml +80 -0
  157. package/recipes/nvca-rofr-co-sale-agreement/replacements.json +17 -0
  158. package/recipes/nvca-rofr-co-sale-agreement/schema.json +19 -0
  159. package/recipes/nvca-stock-purchase-agreement/clean.json +10 -0
  160. package/recipes/nvca-stock-purchase-agreement/metadata.yaml +74 -0
  161. package/recipes/nvca-stock-purchase-agreement/replacements.json +20 -0
  162. package/recipes/nvca-stock-purchase-agreement/schema.json +19 -0
  163. package/recipes/nvca-voting-agreement/README.md +53 -0
  164. package/recipes/nvca-voting-agreement/clean.json +7 -0
  165. package/recipes/nvca-voting-agreement/metadata.yaml +70 -0
  166. package/recipes/nvca-voting-agreement/replacements.json +18 -0
  167. package/recipes/nvca-voting-agreement/schema.json +28 -0
  168. package/skills/open-agreements/SKILL.md +166 -0
  169. package/templates/bonterms-mutual-nda/README.md +27 -0
  170. package/templates/bonterms-mutual-nda/metadata.yaml +58 -0
  171. package/templates/bonterms-mutual-nda/template.docx +0 -0
  172. package/templates/bonterms-professional-services-agreement/README.md +24 -0
  173. package/templates/bonterms-professional-services-agreement/metadata.yaml +40 -0
  174. package/templates/bonterms-professional-services-agreement/template.docx +0 -0
  175. package/templates/common-paper-ai-addendum/README.md +23 -0
  176. package/templates/common-paper-ai-addendum/metadata.yaml +33 -0
  177. package/templates/common-paper-ai-addendum/template.docx +0 -0
  178. package/templates/common-paper-ai-addendum-in-app/README.md +21 -0
  179. package/templates/common-paper-ai-addendum-in-app/metadata.yaml +23 -0
  180. package/templates/common-paper-ai-addendum-in-app/template.docx +0 -0
  181. package/templates/common-paper-amendment/README.md +27 -0
  182. package/templates/common-paper-amendment/metadata.yaml +53 -0
  183. package/templates/common-paper-amendment/template.docx +0 -0
  184. package/templates/common-paper-business-associate-agreement/README.md +29 -0
  185. package/templates/common-paper-business-associate-agreement/metadata.yaml +63 -0
  186. package/templates/common-paper-business-associate-agreement/template.docx +0 -0
  187. package/templates/common-paper-cloud-service-agreement/README.md +32 -0
  188. package/templates/common-paper-cloud-service-agreement/metadata.yaml +488 -0
  189. package/templates/common-paper-cloud-service-agreement/template.docx +0 -0
  190. package/templates/common-paper-csa-click-through/README.md +33 -0
  191. package/templates/common-paper-csa-click-through/metadata.yaml +83 -0
  192. package/templates/common-paper-csa-click-through/template.docx +0 -0
  193. package/templates/common-paper-csa-with-ai/README.md +49 -0
  194. package/templates/common-paper-csa-with-ai/metadata.yaml +166 -0
  195. package/templates/common-paper-csa-with-ai/template.docx +0 -0
  196. package/templates/common-paper-csa-with-sla/README.md +53 -0
  197. package/templates/common-paper-csa-with-sla/metadata.yaml +185 -0
  198. package/templates/common-paper-csa-with-sla/template.docx +0 -0
  199. package/templates/common-paper-csa-without-sla/README.md +47 -0
  200. package/templates/common-paper-csa-without-sla/metadata.yaml +155 -0
  201. package/templates/common-paper-csa-without-sla/template.docx +0 -0
  202. package/templates/common-paper-data-processing-agreement/README.md +46 -0
  203. package/templates/common-paper-data-processing-agreement/metadata.yaml +149 -0
  204. package/templates/common-paper-data-processing-agreement/template.docx +0 -0
  205. package/templates/common-paper-design-partner-agreement/README.md +29 -0
  206. package/templates/common-paper-design-partner-agreement/metadata.yaml +65 -0
  207. package/templates/common-paper-design-partner-agreement/template.docx +0 -0
  208. package/templates/common-paper-independent-contractor-agreement/README.md +27 -0
  209. package/templates/common-paper-independent-contractor-agreement/metadata.yaml +55 -0
  210. package/templates/common-paper-independent-contractor-agreement/template.docx +0 -0
  211. package/templates/common-paper-letter-of-intent/README.md +25 -0
  212. package/templates/common-paper-letter-of-intent/metadata.yaml +43 -0
  213. package/templates/common-paper-letter-of-intent/template.docx +0 -0
  214. package/templates/common-paper-mutual-nda/README.md +29 -0
  215. package/templates/common-paper-mutual-nda/metadata.yaml +59 -0
  216. package/templates/common-paper-mutual-nda/template.docx +0 -0
  217. package/templates/common-paper-one-way-nda/README.md +27 -0
  218. package/templates/common-paper-one-way-nda/metadata.yaml +60 -0
  219. package/templates/common-paper-one-way-nda/template.docx +0 -0
  220. package/templates/common-paper-order-form/README.md +36 -0
  221. package/templates/common-paper-order-form/metadata.yaml +98 -0
  222. package/templates/common-paper-order-form/template.docx +0 -0
  223. package/templates/common-paper-order-form-with-sla/README.md +42 -0
  224. package/templates/common-paper-order-form-with-sla/metadata.yaml +129 -0
  225. package/templates/common-paper-order-form-with-sla/template.docx +0 -0
  226. package/templates/common-paper-partnership-agreement/README.md +34 -0
  227. package/templates/common-paper-partnership-agreement/metadata.yaml +90 -0
  228. package/templates/common-paper-partnership-agreement/template.docx +0 -0
  229. package/templates/common-paper-pilot-agreement/README.md +34 -0
  230. package/templates/common-paper-pilot-agreement/metadata.yaml +90 -0
  231. package/templates/common-paper-pilot-agreement/template.docx +0 -0
  232. package/templates/common-paper-professional-services-agreement/README.md +44 -0
  233. package/templates/common-paper-professional-services-agreement/metadata.yaml +141 -0
  234. package/templates/common-paper-professional-services-agreement/template.docx +0 -0
  235. package/templates/common-paper-software-license-agreement/README.md +18 -0
  236. package/templates/common-paper-software-license-agreement/metadata.yaml +13 -0
  237. package/templates/common-paper-software-license-agreement/template.docx +0 -0
  238. package/templates/common-paper-statement-of-work/README.md +32 -0
  239. package/templates/common-paper-statement-of-work/metadata.yaml +78 -0
  240. package/templates/common-paper-statement-of-work/template.docx +0 -0
  241. package/templates/common-paper-term-sheet/README.md +22 -0
  242. package/templates/common-paper-term-sheet/metadata.yaml +28 -0
  243. package/templates/common-paper-term-sheet/template.docx +0 -0
@@ -0,0 +1,240 @@
1
+ import AdmZip from 'adm-zip';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
4
+ import { enumerateTextParts, getGeneralTextPartNames } from './ooxml-parts.js';
5
+ const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
6
+ /** Maximum replacement iterations per key per paragraph to prevent infinite loops. */
7
+ const MAX_REPLACEMENTS_PER_KEY = 200;
8
+ /**
9
+ * Element local names that indicate a run is NOT safe to remove even if its text is empty.
10
+ * These are non-text children that carry visual or structural meaning.
11
+ */
12
+ const UNSAFE_RUN_CHILDREN = new Set([
13
+ 'drawing', 'pict', 'object', 'fldChar', 'instrText',
14
+ 'br', 'cr', 'tab', 'footnoteReference', 'endnoteReference',
15
+ ]);
16
+ /**
17
+ * Patch a DOCX document by replacing bracketed placeholders with template tags.
18
+ * Uses a char_map algorithm to handle cross-run replacements where Word splits
19
+ * placeholder text across multiple XML run elements.
20
+ *
21
+ * Processes all general OOXML text parts (document, headers, footers, endnotes).
22
+ */
23
+ export async function patchDocument(inputPath, outputPath, replacements) {
24
+ const zip = new AdmZip(inputPath);
25
+ const parser = new DOMParser();
26
+ const serializer = new XMLSerializer();
27
+ const parts = enumerateTextParts(zip);
28
+ const partNames = getGeneralTextPartNames(parts);
29
+ if (partNames.length === 0) {
30
+ throw new Error('No OOXML text parts found in DOCX');
31
+ }
32
+ // Sort keys longest-first to prevent partial matches
33
+ const sortedKeys = Object.keys(replacements).sort((a, b) => b.length - a.length);
34
+ // Track which parts we modify so we can rebuild the zip cleanly
35
+ const modifiedParts = new Map();
36
+ for (const partName of partNames) {
37
+ const entry = zip.getEntry(partName);
38
+ if (!entry)
39
+ continue;
40
+ const xml = entry.getData().toString('utf-8');
41
+ const doc = parser.parseFromString(xml, 'text/xml');
42
+ // Process all paragraphs (body + tables)
43
+ const allParagraphs = doc.getElementsByTagNameNS(W_NS, 'p');
44
+ for (let i = 0; i < allParagraphs.length; i++) {
45
+ replaceInParagraph(allParagraphs[i], replacements, sortedKeys);
46
+ }
47
+ modifiedParts.set(partName, Buffer.from(serializer.serializeToString(doc), 'utf-8'));
48
+ }
49
+ // Rebuild the zip from scratch using addFile() to avoid adm-zip data
50
+ // descriptor issues. Some DOCX files use streaming (bit 3) flags which
51
+ // adm-zip's updateFile/writeZip/toBuffer handle incorrectly.
52
+ const outZip = new AdmZip();
53
+ for (const entry of zip.getEntries()) {
54
+ const data = modifiedParts.get(entry.entryName) ?? entry.getData();
55
+ outZip.addFile(entry.entryName, data);
56
+ }
57
+ writeFileSync(outputPath, outZip.toBuffer());
58
+ return outputPath;
59
+ }
60
+ function buildCharMap(runs) {
61
+ const charMap = [];
62
+ let fullText = '';
63
+ for (let runIndex = 0; runIndex < runs.length; runIndex++) {
64
+ const text = getRunText(runs[runIndex]);
65
+ for (let offset = 0; offset < text.length; offset++) {
66
+ charMap.push({ runIndex, charOffset: offset });
67
+ }
68
+ fullText += text;
69
+ }
70
+ return { fullText, charMap };
71
+ }
72
+ export function getRunText(run) {
73
+ const tElements = run.getElementsByTagNameNS(W_NS, 't');
74
+ let text = '';
75
+ for (let i = 0; i < tElements.length; i++) {
76
+ text += tElements[i].textContent ?? '';
77
+ }
78
+ return text;
79
+ }
80
+ function setRunText(run, text) {
81
+ const tElements = run.getElementsByTagNameNS(W_NS, 't');
82
+ if (tElements.length === 0) {
83
+ const doc = run.ownerDocument;
84
+ const t = doc.createElementNS(W_NS, 'w:t');
85
+ t.setAttribute('xml:space', 'preserve');
86
+ t.textContent = text;
87
+ run.appendChild(t);
88
+ return;
89
+ }
90
+ tElements[0].textContent = text;
91
+ if (text.startsWith(' ') || text.endsWith(' ')) {
92
+ tElements[0].setAttribute('xml:space', 'preserve');
93
+ }
94
+ for (let i = 1; i < tElements.length; i++) {
95
+ tElements[i].textContent = '';
96
+ }
97
+ }
98
+ /**
99
+ * Collect all <w:r> elements in document order within a paragraph, including
100
+ * runs nested inside wrapper elements like <w:hyperlink>, <w:ins>, <w:del>, etc.
101
+ */
102
+ function getRunElements(para) {
103
+ const runs = [];
104
+ collectRunsRecursive(para, runs);
105
+ return runs;
106
+ }
107
+ function collectRunsRecursive(node, runs) {
108
+ const children = node.childNodes;
109
+ if (!children)
110
+ return;
111
+ for (let i = 0; i < children.length; i++) {
112
+ const child = children[i];
113
+ if (child.nodeType !== 1)
114
+ continue;
115
+ const el = child;
116
+ if (el.localName === 'r' && el.namespaceURI === W_NS) {
117
+ runs.push(el);
118
+ }
119
+ else if (el.localName !== 'p') {
120
+ // Recurse into wrapper elements (hyperlink, ins, del, smartTag, etc.)
121
+ // but don't recurse into nested paragraphs
122
+ collectRunsRecursive(el, runs);
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Check if a run is safe to remove when its text is empty.
128
+ * Returns true only if all element children are <w:rPr> or <w:t>.
129
+ * Returns false if the run contains drawings, fields, breaks, or other non-text elements.
130
+ */
131
+ export function isRunSafeToRemove(run) {
132
+ const children = run.childNodes;
133
+ if (!children)
134
+ return true;
135
+ for (let i = 0; i < children.length; i++) {
136
+ const child = children[i];
137
+ if (child.nodeType !== 1)
138
+ continue; // skip text/comment nodes
139
+ const name = child.localName;
140
+ if (!name)
141
+ return false;
142
+ if (name === 'rPr' || name === 't')
143
+ continue;
144
+ if (UNSAFE_RUN_CHILDREN.has(name))
145
+ return false;
146
+ // Any other element child we don't recognize — not safe
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ /**
152
+ * Compute the length of the common prefix between two strings.
153
+ */
154
+ function commonPrefixLen(a, b) {
155
+ let i = 0;
156
+ while (i < a.length && i < b.length && a[i] === b[i])
157
+ i++;
158
+ return i;
159
+ }
160
+ /**
161
+ * Compute the length of the common suffix between two strings,
162
+ * not overlapping with a known common prefix.
163
+ */
164
+ function commonSuffixLen(a, b, prefixLen) {
165
+ let i = 0;
166
+ while (i < a.length - prefixLen &&
167
+ i < b.length - prefixLen &&
168
+ a[a.length - 1 - i] === b[b.length - 1 - i])
169
+ i++;
170
+ return i;
171
+ }
172
+ function replaceInParagraph(para, replacements, sortedKeys) {
173
+ const runs = getRunElements(para);
174
+ if (runs.length === 0)
175
+ return;
176
+ const { fullText } = buildCharMap(runs);
177
+ if (!sortedKeys.some((key) => fullText.includes(key)))
178
+ return;
179
+ for (const key of sortedKeys) {
180
+ let rebuilt = buildCharMap(runs);
181
+ let iterations = 0;
182
+ while (rebuilt.fullText.includes(key)) {
183
+ iterations++;
184
+ if (iterations > MAX_REPLACEMENTS_PER_KEY) {
185
+ throw new Error(`Patcher: exceeded ${MAX_REPLACEMENTS_PER_KEY} replacements for key "${key}" ` +
186
+ `in a single paragraph. This usually means the replacement value contains ` +
187
+ `the search key, creating an infinite loop.`);
188
+ }
189
+ const prevText = rebuilt.fullText;
190
+ const matchStart = rebuilt.fullText.indexOf(key);
191
+ const replacement = replacements[key];
192
+ // Surgical replacement: compute common prefix/suffix between key and value
193
+ // so we only modify the differing middle, preserving formatting on context text.
194
+ const cpLen = commonPrefixLen(key, replacement);
195
+ const csLen = commonSuffixLen(key, replacement, cpLen);
196
+ let start;
197
+ let end;
198
+ let replText;
199
+ if (cpLen + csLen >= key.length || cpLen + csLen >= replacement.length) {
200
+ // No useful common prefix/suffix — full replacement (current behavior)
201
+ start = matchStart;
202
+ end = matchStart + key.length;
203
+ replText = replacement;
204
+ }
205
+ else {
206
+ // Surgical: only replace the differing middle
207
+ start = matchStart + cpLen;
208
+ end = matchStart + key.length - csLen;
209
+ replText = replacement.slice(cpLen, replacement.length - csLen);
210
+ }
211
+ const firstEntry = rebuilt.charMap[start];
212
+ const lastEntry = rebuilt.charMap[end - 1];
213
+ if (firstEntry.runIndex === lastEntry.runIndex) {
214
+ const runText = getRunText(runs[firstEntry.runIndex]);
215
+ setRunText(runs[firstEntry.runIndex], runText.slice(0, firstEntry.charOffset) + replText + runText.slice(lastEntry.charOffset + 1));
216
+ }
217
+ else {
218
+ const firstRunText = getRunText(runs[firstEntry.runIndex]);
219
+ setRunText(runs[firstEntry.runIndex], firstRunText.slice(0, firstEntry.charOffset) + replText);
220
+ const lastRunText = getRunText(runs[lastEntry.runIndex]);
221
+ setRunText(runs[lastEntry.runIndex], lastRunText.slice(lastEntry.charOffset + 1));
222
+ for (let mid = firstEntry.runIndex + 1; mid < lastEntry.runIndex; mid++) {
223
+ setRunText(runs[mid], '');
224
+ }
225
+ }
226
+ rebuilt = buildCharMap(runs);
227
+ // Progress guard: if text didn't change, we're stuck
228
+ if (rebuilt.fullText === prevText) {
229
+ throw new Error(`Patcher: no progress replacing key "${key}" — replacement value may contain the search key.`);
230
+ }
231
+ }
232
+ }
233
+ // Sweep: remove empty runs that are safe to remove (no drawings, fields, etc.)
234
+ for (let i = runs.length - 1; i >= 0; i--) {
235
+ if (getRunText(runs[i]) === '' && isRunSafeToRemove(runs[i])) {
236
+ runs[i].parentNode?.removeChild(runs[i]);
237
+ }
238
+ }
239
+ }
240
+ //# sourceMappingURL=patcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patcher.js","sourceRoot":"","sources":["../../../src/core/recipe/patcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { RecipeMetadata } from '../metadata.js';
2
+ export interface RecipeRunOptions {
3
+ recipeId: string;
4
+ inputPath?: string;
5
+ outputPath: string;
6
+ values: Record<string, string>;
7
+ keepIntermediate?: boolean;
8
+ }
9
+ export interface RecipeRunResult {
10
+ outputPath: string;
11
+ metadata: RecipeMetadata;
12
+ fieldsUsed: string[];
13
+ stages: {
14
+ clean: string;
15
+ patch: string;
16
+ fill: string;
17
+ };
18
+ }
19
+ export interface VerifyResult {
20
+ passed: boolean;
21
+ checks: VerifyCheck[];
22
+ }
23
+ export interface VerifyCheck {
24
+ name: string;
25
+ passed: boolean;
26
+ details?: string;
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/recipe/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/recipe/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import type { VerifyResult } from './types.js';
2
+ import type { CleanConfig } from '../metadata.js';
3
+ /**
4
+ * Normalize text for value comparison:
5
+ * - Convert non-breaking spaces to regular spaces
6
+ * - Normalize smart quotes to straight quotes
7
+ * - Collapse runs of spaces/tabs to single space (preserve newlines)
8
+ * - Trim
9
+ */
10
+ export declare function normalizeText(text: string): string;
11
+ /**
12
+ * Verify a filled recipe output DOCX:
13
+ * - All context values appear in the document text
14
+ * - No unrendered {template_tags} remain
15
+ * - No leftover [bracketed placeholders] from the replacement map remain
16
+ * - Footnotes removed (if clean config specified)
17
+ * - Drafting note paragraphs removed (if clean config specified)
18
+ */
19
+ export declare function verifyOutput(outputPath: string, values: Record<string, string>, replacements: Record<string, string>, cleanConfig?: CleanConfig): Promise<VerifyResult>;
20
+ /**
21
+ * Extract all text from general OOXML text parts (document, headers, footers, endnotes).
22
+ */
23
+ export declare function extractAllText(docxPath: string): string;
24
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../../src/core/recipe/verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAKlD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWlD;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,YAAY,CAAC,CA6EvB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8BvD"}
@@ -0,0 +1,143 @@
1
+ import AdmZip from 'adm-zip';
2
+ import { DOMParser } from '@xmldom/xmldom';
3
+ import { enumerateTextParts, getGeneralTextPartNames } from './ooxml-parts.js';
4
+ const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
5
+ /**
6
+ * Normalize text for value comparison:
7
+ * - Convert non-breaking spaces to regular spaces
8
+ * - Normalize smart quotes to straight quotes
9
+ * - Collapse runs of spaces/tabs to single space (preserve newlines)
10
+ * - Trim
11
+ */
12
+ export function normalizeText(text) {
13
+ return text
14
+ // Non-breaking spaces
15
+ .replace(/[\u00A0\u2007\u202F]/g, ' ')
16
+ // Smart single quotes → straight
17
+ .replace(/[\u2018\u2019\u2039\u203A]/g, "'")
18
+ // Smart double quotes → straight
19
+ .replace(/[\u201C\u201D\u201A\u201E\u00AB\u00BB]/g, '"')
20
+ // Collapse horizontal whitespace (spaces/tabs) to single space, preserve newlines
21
+ .replace(/[^\S\n]+/g, ' ')
22
+ .trim();
23
+ }
24
+ /**
25
+ * Verify a filled recipe output DOCX:
26
+ * - All context values appear in the document text
27
+ * - No unrendered {template_tags} remain
28
+ * - No leftover [bracketed placeholders] from the replacement map remain
29
+ * - Footnotes removed (if clean config specified)
30
+ * - Drafting note paragraphs removed (if clean config specified)
31
+ */
32
+ export async function verifyOutput(outputPath, values, replacements, cleanConfig) {
33
+ const checks = [];
34
+ const rawFullText = extractAllText(outputPath);
35
+ const normalizedFullText = normalizeText(rawFullText);
36
+ const xml = extractDocumentXml(outputPath);
37
+ // Check 1: All context values present (with normalization)
38
+ const missingValues = [];
39
+ for (const [key, value] of Object.entries(values)) {
40
+ if (!value || !value.trim())
41
+ continue; // skip empty/whitespace-only values
42
+ const normalizedValue = normalizeText(value);
43
+ if (!normalizedFullText.includes(normalizedValue)) {
44
+ missingValues.push(`${key}="${value}"`);
45
+ }
46
+ }
47
+ checks.push({
48
+ name: 'Context values present',
49
+ passed: missingValues.length === 0,
50
+ details: missingValues.length > 0 ? `Missing: ${missingValues.join(', ')}` : undefined,
51
+ });
52
+ // Check 2: No unrendered {template_tags}
53
+ const unrenderedTags = rawFullText.match(/\{[a-z_][a-z0-9_]*\}/gi) ?? [];
54
+ checks.push({
55
+ name: 'No unrendered template tags',
56
+ passed: unrenderedTags.length === 0,
57
+ details: unrenderedTags.length > 0 ? `Found: ${unrenderedTags.join(', ')}` : undefined,
58
+ });
59
+ // Check 3: No leftover [bracketed placeholders] from replacement map
60
+ const replacementKeys = Object.keys(replacements);
61
+ const leftoverBrackets = replacementKeys.filter((key) => rawFullText.includes(key));
62
+ checks.push({
63
+ name: 'No leftover source placeholders',
64
+ passed: leftoverBrackets.length === 0,
65
+ details: leftoverBrackets.length > 0 ? `Found: ${leftoverBrackets.join(', ')}` : undefined,
66
+ });
67
+ // Check 4: No footnote references (if removeFootnotes was set)
68
+ if (cleanConfig?.removeFootnotes) {
69
+ const footnoteRefs = (xml.match(/footnoteReference/g) ?? []).length;
70
+ checks.push({
71
+ name: 'Footnotes removed',
72
+ passed: footnoteRefs === 0,
73
+ details: footnoteRefs > 0 ? `${footnoteRefs} footnote reference(s) remain` : undefined,
74
+ });
75
+ }
76
+ // Check 5: No double dollar signs ($$ or $ $)
77
+ // This catches cases where the template already has a $ before a placeholder
78
+ // and the user also included a $ in their value (e.g. "$1,000,000")
79
+ const doubleDollarPattern = /\$[\s\u00A0\t]*\$/;
80
+ const doubleDollarLines = rawFullText.split('\n').filter((line) => doubleDollarPattern.test(line));
81
+ checks.push({
82
+ name: 'No double dollar signs',
83
+ passed: doubleDollarLines.length === 0,
84
+ details: doubleDollarLines.length > 0
85
+ ? `Found ${doubleDollarLines.length} occurrence(s): "${doubleDollarLines[0].trim().slice(0, 80)}"`
86
+ : undefined,
87
+ });
88
+ // Check 6: No drafting note paragraphs (if patterns were set)
89
+ if (cleanConfig?.removeParagraphPatterns && cleanConfig.removeParagraphPatterns.length > 0) {
90
+ const regexes = cleanConfig.removeParagraphPatterns.map((p) => new RegExp(p, 'i'));
91
+ const lines = rawFullText.split('\n');
92
+ const matchingLines = lines.filter((line) => regexes.some((r) => r.test(line.trim())));
93
+ checks.push({
94
+ name: 'Drafting notes removed',
95
+ passed: matchingLines.length === 0,
96
+ details: matchingLines.length > 0 ? `Found: ${matchingLines[0].trim().slice(0, 80)}...` : undefined,
97
+ });
98
+ }
99
+ return {
100
+ passed: checks.every((c) => c.passed),
101
+ checks,
102
+ };
103
+ }
104
+ /**
105
+ * Extract all text from general OOXML text parts (document, headers, footers, endnotes).
106
+ */
107
+ export function extractAllText(docxPath) {
108
+ const zip = new AdmZip(docxPath);
109
+ const parser = new DOMParser();
110
+ const parts = enumerateTextParts(zip);
111
+ const partNames = getGeneralTextPartNames(parts);
112
+ const allParagraphs = [];
113
+ for (const partName of partNames) {
114
+ const entry = zip.getEntry(partName);
115
+ if (!entry)
116
+ continue;
117
+ const xml = entry.getData().toString('utf-8');
118
+ const doc = parser.parseFromString(xml, 'text/xml');
119
+ const paras = doc.getElementsByTagNameNS(W_NS, 'p');
120
+ for (let i = 0; i < paras.length; i++) {
121
+ const tElements = paras[i].getElementsByTagNameNS(W_NS, 't');
122
+ const textParts = [];
123
+ for (let j = 0; j < tElements.length; j++) {
124
+ textParts.push(tElements[j].textContent ?? '');
125
+ }
126
+ if (textParts.length > 0) {
127
+ allParagraphs.push(textParts.join(''));
128
+ }
129
+ }
130
+ }
131
+ return allParagraphs.join('\n');
132
+ }
133
+ /**
134
+ * Extract raw XML from word/document.xml only (for footnote ref counting).
135
+ */
136
+ function extractDocumentXml(docxPath) {
137
+ const zip = new AdmZip(docxPath);
138
+ const entry = zip.getEntry('word/document.xml');
139
+ if (!entry)
140
+ return '';
141
+ return entry.getData().toString('utf-8');
142
+ }
143
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../src/core/recipe/verifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface ExternalValidationResult {
2
+ externalId: string;
3
+ valid: boolean;
4
+ errors: string[];
5
+ warnings: string[];
6
+ }
7
+ /**
8
+ * Validate an external template directory:
9
+ * - metadata.yaml validates against ExternalMetadataSchema
10
+ * - template.docx exists and SHA-256 matches source_sha256
11
+ * - replacements.json is present and valid
12
+ * - clean.json is valid if present
13
+ * - Replacement targets reference metadata fields
14
+ */
15
+ export declare function validateExternal(externalDir: string, externalId: string): ExternalValidationResult;
16
+ //# sourceMappingURL=external.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../../src/core/validation/external.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AASD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,wBAAwB,CAsG1B"}
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { validateExternalMetadata, loadExternalMetadata, CleanConfigSchema } from '../metadata.js';
5
+ /** Pattern for valid template tags within replacement values */
6
+ const TAG_RE = /\{[a-z_][a-z0-9_]*\}/g;
7
+ /** Pattern for any curly-brace token */
8
+ const ANY_BRACE_RE = /\{[^}]+\}/g;
9
+ /** Only simple identifiers inside braces are allowed */
10
+ const SAFE_TAG_RE = /^\{[a-z_][a-z0-9_]*\}$/;
11
+ /**
12
+ * Validate an external template directory:
13
+ * - metadata.yaml validates against ExternalMetadataSchema
14
+ * - template.docx exists and SHA-256 matches source_sha256
15
+ * - replacements.json is present and valid
16
+ * - clean.json is valid if present
17
+ * - Replacement targets reference metadata fields
18
+ */
19
+ export function validateExternal(externalDir, externalId) {
20
+ const errors = [];
21
+ const warnings = [];
22
+ // Validate metadata
23
+ const metaResult = validateExternalMetadata(externalDir);
24
+ if (!metaResult.valid) {
25
+ errors.push(...metaResult.errors.map((e) => `metadata: ${e}`));
26
+ return { externalId, valid: false, errors, warnings };
27
+ }
28
+ const metadata = loadExternalMetadata(externalDir);
29
+ // Validate template.docx exists
30
+ const docxPath = join(externalDir, 'template.docx');
31
+ if (!existsSync(docxPath)) {
32
+ errors.push('template.docx not found');
33
+ return { externalId, valid: false, errors, warnings };
34
+ }
35
+ // SHA-256 integrity check
36
+ const docxBuf = readFileSync(docxPath);
37
+ const actualHash = createHash('sha256').update(docxBuf).digest('hex');
38
+ if (actualHash !== metadata.source_sha256) {
39
+ errors.push(`DOCX integrity check failed:\n` +
40
+ ` Expected SHA-256: ${metadata.source_sha256}\n` +
41
+ ` Actual SHA-256: ${actualHash}`);
42
+ }
43
+ // Validate replacements.json
44
+ const replacementsPath = join(externalDir, 'replacements.json');
45
+ if (!existsSync(replacementsPath)) {
46
+ errors.push('replacements.json not found (required for external templates)');
47
+ }
48
+ else {
49
+ try {
50
+ const raw = readFileSync(replacementsPath, 'utf-8');
51
+ const replacements = JSON.parse(raw);
52
+ if (typeof replacements !== 'object' || replacements === null) {
53
+ errors.push('replacements.json must be a JSON object');
54
+ }
55
+ else {
56
+ const metadataFieldNames = new Set(metadata.fields.map((f) => f.name));
57
+ for (const [key, value] of Object.entries(replacements)) {
58
+ if (typeof value !== 'string') {
59
+ errors.push(`replacements.json: value for "${key}" must be a string`);
60
+ continue;
61
+ }
62
+ const tags = value.match(TAG_RE);
63
+ if (!tags || tags.length === 0) {
64
+ errors.push(`replacements.json: value for "${key}" must contain at least one {identifier} tag, got "${value}"`);
65
+ }
66
+ const allBraces = value.match(ANY_BRACE_RE);
67
+ if (allBraces) {
68
+ for (const token of allBraces) {
69
+ if (!SAFE_TAG_RE.test(token)) {
70
+ errors.push(`replacements.json: unsafe tag "${token}" in value for "${key}". Only {identifier} tags allowed.`);
71
+ }
72
+ }
73
+ }
74
+ if (value.includes(key)) {
75
+ errors.push(`replacements.json: value for "${key}" contains the key itself (would cause infinite loop)`);
76
+ }
77
+ // Check field coverage
78
+ if (tags) {
79
+ for (const tag of tags) {
80
+ const fieldName = tag.slice(1, -1);
81
+ if (!metadataFieldNames.has(fieldName)) {
82
+ warnings.push(`Replacement target {${fieldName}} not found in metadata fields`);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ catch (err) {
90
+ errors.push(`replacements.json: ${err.message}`);
91
+ }
92
+ }
93
+ // Validate clean.json if present
94
+ const cleanPath = join(externalDir, 'clean.json');
95
+ if (existsSync(cleanPath)) {
96
+ try {
97
+ const raw = readFileSync(cleanPath, 'utf-8');
98
+ CleanConfigSchema.parse(JSON.parse(raw));
99
+ }
100
+ catch {
101
+ errors.push('clean.json: invalid format');
102
+ }
103
+ }
104
+ return { externalId, valid: errors.length === 0, errors, warnings };
105
+ }
106
+ //# sourceMappingURL=external.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.js","sourceRoot":"","sources":["../../../src/core/validation/external.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AASnG,gEAAgE;AAChE,MAAM,MAAM,GAAG,uBAAuB,CAAC;AACvC,wCAAwC;AACxC,MAAM,YAAY,GAAG,YAAY,CAAC;AAClC,wDAAwD;AACxD,MAAM,WAAW,GAAG,wBAAwB,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAmB,EACnB,UAAkB;IAElB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,oBAAoB;IACpB,MAAM,UAAU,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC;IACzD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAEnD,gCAAgC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACpD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACvC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACxD,CAAC;IAED,0BAA0B;IAC1B,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,IAAI,UAAU,KAAK,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CACT,gCAAgC;YAChC,yBAAyB,QAAQ,CAAC,aAAa,IAAI;YACnD,yBAAyB,UAAU,EAAE,CACtC,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAChE,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;YACpD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;gBAC9D,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBAEvE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;oBACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;wBAC9B,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,oBAAoB,CAAC,CAAC;wBACtE,SAAS;oBACX,CAAC;oBAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACjC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC/B,MAAM,CAAC,IAAI,CACT,iCAAiC,GAAG,sDAAsD,KAAK,GAAG,CACnG,CAAC;oBACJ,CAAC;oBAED,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;oBAC5C,IAAI,SAAS,EAAE,CAAC;wBACd,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;4BAC9B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gCAC7B,MAAM,CAAC,IAAI,CACT,kCAAkC,KAAK,mBAAmB,GAAG,oCAAoC,CAClG,CAAC;4BACJ,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,CACT,iCAAiC,GAAG,uDAAuD,CAC5F,CAAC;oBACJ,CAAC;oBAED,uBAAuB;oBACvB,IAAI,IAAI,EAAE,CAAC;wBACT,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;4BACvB,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;4BACnC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gCACvC,QAAQ,CAAC,IAAI,CAAC,uBAAuB,SAAS,gCAAgC,CAAC,CAAC;4BAClF,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,sBAAuB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAClD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC7C,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACtE,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface LicenseValidationResult {
2
+ templateId: string;
3
+ valid: boolean;
4
+ errors: string[];
5
+ }
6
+ /**
7
+ * Validate a template's license metadata.
8
+ *
9
+ * The allow_derivatives field means "the committed source DOCX must not be
10
+ * modified" when false. It does NOT prevent the tool from rendering filled
11
+ * output — that decision is made by the fill command based on directory
12
+ * context (templates/ vs external/).
13
+ */
14
+ export declare function validateLicense(templateDir: string, templateId: string): LicenseValidationResult;
15
+ //# sourceMappingURL=license.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../../../src/core/validation/license.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,uBAAuB,CAyBhG"}