@open-agent-toolkit/cli 0.2.72 → 0.2.74

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 (165) hide show
  1. package/assets/agents/oat-reviewer.md +4 -3
  2. package/assets/agents/recon-worker.md +42 -8
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/cli-utilities/configuration.md +3 -56
  5. package/assets/docs/cli-utilities/tool-packs.md +4 -4
  6. package/assets/docs/contributing/code.md +2 -2
  7. package/assets/docs/contributing/index.md +0 -1
  8. package/assets/docs/reference/cli-reference.md +1 -1
  9. package/assets/docs/reference/troubleshooting.md +17 -40
  10. package/assets/docs/workflows/projects/artifacts.md +16 -34
  11. package/assets/docs/workflows/projects/lifecycle.md +20 -19
  12. package/assets/docs/workflows/skills/explainer-kit.md +180 -440
  13. package/assets/docs/workflows/skills/index.md +1 -2
  14. package/assets/docs/workflows/skills/recon.md +114 -23
  15. package/assets/public-package-versions.json +4 -4
  16. package/assets/skills/explainer-kit/SKILL.md +96 -171
  17. package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
  18. package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
  19. package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
  20. package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
  21. package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
  22. package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
  23. package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
  24. package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
  25. package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
  26. package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
  27. package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
  28. package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
  29. package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
  30. package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
  31. package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
  32. package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
  33. package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
  34. package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
  35. package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
  36. package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
  37. package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
  38. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
  39. package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
  40. package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
  41. package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
  42. package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
  43. package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
  44. package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
  45. package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
  46. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
  47. package/assets/skills/oat-project-complete/SKILL.md +168 -349
  48. package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
  49. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
  50. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
  51. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
  52. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
  53. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
  54. package/assets/skills/oat-project-document/SKILL.md +1 -1
  55. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
  56. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  57. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
  58. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
  59. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  60. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
  61. package/assets/skills/oat-project-plan/SKILL.md +5 -6
  62. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  63. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
  64. package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
  65. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
  66. package/assets/skills/oat-project-review-receive/SKILL.md +14 -1
  67. package/assets/skills/oat-project-summary/SKILL.md +35 -25
  68. package/assets/skills/oat-wave-execute/SKILL.md +11 -36
  69. package/assets/skills/oat-wave-program/SKILL.md +13 -37
  70. package/assets/skills/recon/SKILL.md +103 -40
  71. package/assets/skills/recon/references/packet-contract.md +150 -67
  72. package/assets/skills/recon/references/profiles.md +38 -16
  73. package/assets/skills/recon/references/worker-contract.md +39 -8
  74. package/assets/skills/recon/scripts/lib/contracts.mjs +703 -55
  75. package/assets/skills/recon/scripts/lib/routing.mjs +588 -0
  76. package/assets/skills/recon/scripts/lib/validated-run.mjs +2 -0
  77. package/assets/skills/recon/scripts/prepare-routing.mjs +88 -0
  78. package/assets/skills/recon/scripts/render-packet.mjs +69 -1
  79. package/assets/skills/recon/scripts/validate-packet.mjs +473 -56
  80. package/assets/skills/subagent-orchestration/SKILL.md +18 -9
  81. package/assets/skills/subagent-orchestration/references/model-selection-principles.md +17 -8
  82. package/dist/commands/config/index.d.ts.map +1 -1
  83. package/dist/commands/config/index.js +9 -113
  84. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  85. package/dist/commands/project/archive/archive-utils.js +32 -184
  86. package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
  87. package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
  88. package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
  89. package/dist/config/oat-config.d.ts +0 -11
  90. package/dist/config/oat-config.d.ts.map +1 -1
  91. package/dist/config/oat-config.js +0 -34
  92. package/dist/config/resolve.d.ts.map +1 -1
  93. package/dist/config/resolve.js +0 -8
  94. package/dist/release/public-package-contract.d.ts.map +1 -1
  95. package/dist/release/public-package-contract.js +2 -10
  96. package/dist/validation/project-state.d.ts.map +1 -1
  97. package/dist/validation/project-state.js +42 -3
  98. package/package.json +2 -2
  99. package/assets/docs/contributing/explainer-kit-verification.md +0 -133
  100. package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
  101. package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
  102. package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
  103. package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
  104. package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
  105. package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
  106. package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
  107. package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
  108. package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
  109. package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
  110. package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
  111. package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
  112. package/assets/skills/explainer-kit/references/contracts.md +0 -324
  113. package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
  114. package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
  115. package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
  116. package/assets/skills/explainer-kit/references/visual-review.md +0 -71
  117. package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
  118. package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
  119. package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
  120. package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
  121. package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
  122. package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
  123. package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
  124. package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
  125. package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
  126. package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
  127. package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
  128. package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
  129. package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
  130. package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
  131. package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
  132. package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
  133. package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
  134. package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
  135. package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
  136. package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
  137. package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
  138. package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
  139. package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
  140. package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
  141. package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
  142. package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
  143. package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
  144. package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
  145. package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
  146. package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
  147. package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
  148. package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
  149. package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
  150. package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
  151. package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
  152. package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
  153. package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
  154. package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
  155. package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
  156. package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
  157. package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
  158. package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
  159. package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
  160. package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
  161. package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
  162. package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
  163. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
  164. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
  165. package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
@@ -1,538 +0,0 @@
1
- const MAX_HTML_BYTES = 1_000_000;
2
- const MAX_ELEMENTS = 10_000;
3
- const MAX_REFERENCES = 20_000;
4
- const MAX_ERRORS = 100;
5
- const RAW_TEXT_ELEMENTS = new Set(['script', 'style']);
6
- const REFERENCE_ATTRIBUTES = new Set([
7
- 'href',
8
- 'poster',
9
- 'src',
10
- 'srcset',
11
- 'xlink:href',
12
- ]);
13
- const SAFE_DATA_REFERENCE =
14
- /^data:image\/(?:gif|jpeg|png|webp);base64,[a-z0-9+/]+={0,2}$/i;
15
-
16
- export function validateInternalReferences({ artifacts, manifestPaths } = {}) {
17
- const errors = [];
18
- const add = (artifactId, code, message, detail = {}) => {
19
- if (errors.length >= MAX_ERRORS) return;
20
- errors.push({ artifactId, code, message, ...detail });
21
- };
22
- if (!Array.isArray(artifacts) || !Array.isArray(manifestPaths)) {
23
- add(
24
- null,
25
- 'invalid-site-tree',
26
- 'Internal-reference validation requires artifacts and manifest paths.',
27
- );
28
- return { valid: false, errors };
29
- }
30
- const declaredPaths = new Set(manifestPaths);
31
- if (
32
- declaredPaths.size !== manifestPaths.length ||
33
- manifestPaths.some((path) => !isCanonicalSitePath(path))
34
- ) {
35
- add(
36
- null,
37
- 'invalid-site-tree',
38
- 'Manifest paths must be unique canonical site-relative files.',
39
- );
40
- }
41
-
42
- const idsByPath = new Map();
43
- const tokenized = [];
44
- const boundPaths = new Set();
45
- let referenceCount = 0;
46
- for (const artifact of artifacts) {
47
- if (
48
- !isObject(artifact) ||
49
- typeof artifact.artifactId !== 'string' ||
50
- typeof artifact.renderedPath !== 'string' ||
51
- typeof artifact.html !== 'string' ||
52
- !declaredPaths.has(artifact.renderedPath)
53
- ) {
54
- add(
55
- artifact?.artifactId ?? null,
56
- 'invalid-site-tree',
57
- 'Every rendered artifact must bind to one manifest-declared site path.',
58
- );
59
- continue;
60
- }
61
- if (boundPaths.has(artifact.renderedPath)) {
62
- add(
63
- artifact.artifactId,
64
- 'invalid-site-tree',
65
- 'Rendered artifact paths must be unique.',
66
- { renderedPath: artifact.renderedPath },
67
- );
68
- continue;
69
- }
70
- boundPaths.add(artifact.renderedPath);
71
- if (Buffer.byteLength(artifact.html, 'utf8') > MAX_HTML_BYTES) {
72
- add(
73
- artifact.artifactId,
74
- 'validation-bound',
75
- `Rendered artifact exceeds the ${MAX_HTML_BYTES}-byte validation bound.`,
76
- );
77
- continue;
78
- }
79
- const parsed = tokenizeBoundedHtml(artifact.html);
80
- if (!parsed.valid) {
81
- add(artifact.artifactId, parsed.code, parsed.message, {
82
- renderedPath: artifact.renderedPath,
83
- });
84
- continue;
85
- }
86
- const idCounts = new Map();
87
- for (const element of parsed.elements) {
88
- for (const attribute of element.attributes) {
89
- if (attribute.name === 'id') {
90
- idCounts.set(
91
- attribute.value,
92
- (idCounts.get(attribute.value) ?? 0) + 1,
93
- );
94
- }
95
- if (REFERENCE_ATTRIBUTES.has(attribute.name)) {
96
- referenceCount += 1;
97
- }
98
- }
99
- }
100
- idsByPath.set(artifact.renderedPath, idCounts);
101
- tokenized.push({ artifact, elements: parsed.elements });
102
- }
103
- if (referenceCount > MAX_REFERENCES) {
104
- add(
105
- null,
106
- 'validation-bound',
107
- `Rendered site exceeds the ${MAX_REFERENCES}-reference validation bound.`,
108
- );
109
- return { valid: false, errors };
110
- }
111
-
112
- for (const { artifact, elements } of tokenized) {
113
- for (const element of elements) {
114
- for (const attribute of element.attributes) {
115
- if (!REFERENCE_ATTRIBUTES.has(attribute.name)) continue;
116
- const references =
117
- attribute.name === 'srcset'
118
- ? parseSrcset(attribute.value)
119
- : { valid: true, values: [attribute.value] };
120
- if (!references.valid) {
121
- add(
122
- artifact.artifactId,
123
- 'malformed-srcset',
124
- 'srcset must contain bounded URL candidates with valid descriptors.',
125
- referenceDetail(artifact, element, attribute, attribute.value),
126
- );
127
- continue;
128
- }
129
- for (const reference of references.values) {
130
- validateReference({
131
- artifact,
132
- element,
133
- attribute,
134
- reference,
135
- declaredPaths,
136
- idsByPath,
137
- add,
138
- });
139
- }
140
- }
141
- }
142
- }
143
- return { valid: errors.length === 0, errors };
144
- }
145
-
146
- function validateReference({
147
- artifact,
148
- element,
149
- attribute,
150
- reference,
151
- declaredPaths,
152
- idsByPath,
153
- add,
154
- }) {
155
- const detail = referenceDetail(artifact, element, attribute, reference);
156
- if (
157
- typeof reference !== 'string' ||
158
- reference.length === 0 ||
159
- hasUnsafeRawReferenceBytes(reference)
160
- ) {
161
- add(
162
- artifact.artifactId,
163
- 'unsafe-reference',
164
- 'References must use a non-empty canonical raw form.',
165
- detail,
166
- );
167
- return;
168
- }
169
- const scheme = reference.match(/^([a-z][a-z0-9+.-]*):/i)?.[1]?.toLowerCase();
170
- if (scheme === 'data') {
171
- if (
172
- !isResourceAttribute(element.name, attribute.name) ||
173
- !SAFE_DATA_REFERENCE.test(reference)
174
- ) {
175
- add(
176
- artifact.artifactId,
177
- 'unsafe-scheme',
178
- 'Only bounded base64 image data references are allowed inline.',
179
- detail,
180
- );
181
- }
182
- return;
183
- }
184
- if (scheme) {
185
- let external;
186
- try {
187
- external = new URL(reference);
188
- } catch {
189
- add(
190
- artifact.artifactId,
191
- 'malformed-reference',
192
- 'Absolute reference is malformed.',
193
- detail,
194
- );
195
- return;
196
- }
197
- if (
198
- scheme !== 'https' ||
199
- external.username ||
200
- external.password ||
201
- (element.name !== 'a' && element.name !== 'area') ||
202
- attribute.name !== 'href'
203
- ) {
204
- add(
205
- artifact.artifactId,
206
- scheme === 'https' && isResourceAttribute(element.name, attribute.name)
207
- ? 'external-resource'
208
- : 'unsafe-scheme',
209
- 'External resources and unsafe navigation schemes are not allowed.',
210
- detail,
211
- );
212
- }
213
- return;
214
- }
215
- if (reference.startsWith('//')) {
216
- add(
217
- artifact.artifactId,
218
- 'unsafe-reference',
219
- 'Protocol-relative references are ambiguous.',
220
- detail,
221
- );
222
- return;
223
- }
224
- if (reference.includes('?')) {
225
- add(
226
- artifact.artifactId,
227
- 'query-reference',
228
- 'Internal references cannot contain queries.',
229
- detail,
230
- );
231
- return;
232
- }
233
- const [pathPart, rawFragment, ...extraFragments] = reference.split('#');
234
- if (extraFragments.length > 0) {
235
- add(
236
- artifact.artifactId,
237
- 'malformed-reference',
238
- 'Internal references can contain at most one fragment delimiter.',
239
- detail,
240
- );
241
- return;
242
- }
243
- if (pathPart.endsWith('/')) {
244
- add(
245
- artifact.artifactId,
246
- 'directory-reference',
247
- 'Internal links must name an explicit file such as index.html.',
248
- detail,
249
- );
250
- return;
251
- }
252
- if (pathPart.startsWith('/')) {
253
- add(
254
- artifact.artifactId,
255
- 'site-root-escape',
256
- 'Internal references must be relative to the current artifact.',
257
- detail,
258
- );
259
- return;
260
- }
261
- const base = new URL(artifact.renderedPath, 'https://explainer.invalid/');
262
- let resolved;
263
- try {
264
- resolved = new URL(reference, base);
265
- } catch {
266
- add(
267
- artifact.artifactId,
268
- 'malformed-reference',
269
- 'Relative reference is malformed.',
270
- detail,
271
- );
272
- return;
273
- }
274
- if (
275
- resolved.origin !== base.origin ||
276
- resolved.protocol !== 'https:' ||
277
- !resolved.pathname.startsWith('/site/')
278
- ) {
279
- add(
280
- artifact.artifactId,
281
- 'site-root-escape',
282
- 'Internal reference resolves outside the generated site root.',
283
- detail,
284
- );
285
- return;
286
- }
287
- const targetPath = resolved.pathname.slice(1);
288
- if (!declaredPaths.has(targetPath)) {
289
- add(
290
- artifact.artifactId,
291
- 'missing-target',
292
- 'Internal reference does not resolve to a manifest-declared file.',
293
- { ...detail, targetPath },
294
- );
295
- return;
296
- }
297
- if (rawFragment !== undefined) {
298
- let fragment;
299
- try {
300
- fragment = decodeURIComponent(rawFragment);
301
- } catch {
302
- add(
303
- artifact.artifactId,
304
- 'malformed-reference',
305
- 'Reference fragment is not valid percent-encoded text.',
306
- detail,
307
- );
308
- return;
309
- }
310
- const targetCount = idsByPath.get(targetPath)?.get(fragment) ?? 0;
311
- if (!fragment || targetCount === 0) {
312
- add(
313
- artifact.artifactId,
314
- 'missing-fragment',
315
- 'Internal reference fragment does not exist in the target document.',
316
- { ...detail, targetPath, fragment },
317
- );
318
- } else if (targetCount > 1) {
319
- add(
320
- artifact.artifactId,
321
- 'ambiguous-fragment',
322
- 'Internal reference fragment resolves to multiple targets in the target document.',
323
- { ...detail, targetPath, fragment },
324
- );
325
- }
326
- }
327
- }
328
-
329
- function tokenizeBoundedHtml(html) {
330
- const elements = [];
331
- let cursor = 0;
332
- while (cursor < html.length) {
333
- const open = html.indexOf('<', cursor);
334
- if (open < 0) break;
335
- if (html.startsWith('<!--', open)) {
336
- const close = html.indexOf('-->', open + 4);
337
- if (close < 0) return malformedHtml();
338
- cursor = close + 3;
339
- continue;
340
- }
341
- const close = findTagEnd(html, open + 1);
342
- if (close < 0) return malformedHtml();
343
- const source = html.slice(open + 1, close);
344
- if (/^\s*[!/]/.test(source)) {
345
- cursor = close + 1;
346
- continue;
347
- }
348
- const parsed = parseStartTag(source);
349
- if (!parsed.valid) return malformedHtml();
350
- elements.push(parsed.element);
351
- if (elements.length > MAX_ELEMENTS) {
352
- return {
353
- valid: false,
354
- code: 'validation-bound',
355
- message: `Rendered artifact exceeds the ${MAX_ELEMENTS}-element validation bound.`,
356
- };
357
- }
358
- cursor = close + 1;
359
- if (RAW_TEXT_ELEMENTS.has(parsed.element.name)) {
360
- const closing = indexOfCaseInsensitive(
361
- html,
362
- `</${parsed.element.name}`,
363
- cursor,
364
- );
365
- if (closing < 0) return malformedHtml();
366
- const closingEnd = findTagEnd(html, closing + 2);
367
- if (closingEnd < 0) return malformedHtml();
368
- cursor = closingEnd + 1;
369
- }
370
- }
371
- return { valid: true, elements };
372
- }
373
-
374
- function findTagEnd(html, start) {
375
- let quote = null;
376
- for (let index = start; index < html.length; index += 1) {
377
- const char = html[index];
378
- if (quote) {
379
- if (char === quote) quote = null;
380
- } else if (char === '"' || char === "'") {
381
- quote = char;
382
- } else if (char === '>') {
383
- return index;
384
- }
385
- }
386
- return -1;
387
- }
388
-
389
- function parseStartTag(source) {
390
- const nameMatch = source.match(/^\s*([a-z][a-z0-9:-]*)/i);
391
- if (!nameMatch) return { valid: false };
392
- const name = nameMatch[1].toLowerCase();
393
- const attributes = [];
394
- let cursor = nameMatch[0].length;
395
- while (cursor < source.length) {
396
- while (/\s/.test(source[cursor] ?? '')) cursor += 1;
397
- if (cursor >= source.length || source[cursor] === '/') break;
398
- const attributeMatch = source
399
- .slice(cursor)
400
- .match(/^([^\s"'<>/=]+)(?:\s*=\s*)?/);
401
- if (!attributeMatch) return { valid: false };
402
- const attributeName = attributeMatch[1].toLowerCase();
403
- cursor += attributeMatch[0].length;
404
- const hasValue = attributeMatch[0].includes('=');
405
- let value = '';
406
- if (hasValue) {
407
- const quote = source[cursor];
408
- if (quote !== '"' && quote !== "'") return { valid: false };
409
- const end = source.indexOf(quote, cursor + 1);
410
- if (end < 0) return { valid: false };
411
- value = decodeHtmlReferences(source.slice(cursor + 1, end));
412
- cursor = end + 1;
413
- }
414
- attributes.push({ name: attributeName, value });
415
- }
416
- return { valid: true, element: { name, attributes } };
417
- }
418
-
419
- function parseSrcset(value) {
420
- const values = [];
421
- let cursor = 0;
422
- while (cursor < value.length) {
423
- while (/\s/.test(value[cursor] ?? '')) cursor += 1;
424
- if (cursor >= value.length || value[cursor] === ',') {
425
- return { valid: false, values: [] };
426
- }
427
- const dataReference =
428
- value.slice(cursor, cursor + 'data:'.length).toLowerCase() === 'data:';
429
- const urlStart = cursor;
430
- if (dataReference) {
431
- while (cursor < value.length && !/\s/.test(value[cursor])) {
432
- cursor += 1;
433
- }
434
- } else {
435
- while (cursor < value.length && !/[\s,]/.test(value[cursor])) {
436
- cursor += 1;
437
- }
438
- }
439
- const url = value.slice(urlStart, cursor);
440
- if (!url) return { valid: false, values: [] };
441
- values.push(url);
442
- if (value[cursor] === ',') {
443
- cursor += 1;
444
- continue;
445
- }
446
- while (/\s/.test(value[cursor] ?? '')) cursor += 1;
447
- const descriptorStart = cursor;
448
- while (cursor < value.length && !/[\s,]/.test(value[cursor])) {
449
- cursor += 1;
450
- }
451
- const descriptor = value.slice(descriptorStart, cursor);
452
- if (descriptor && !/^(?:\d+w|(?:\d+(?:\.\d+)?|\.\d+)x)$/.test(descriptor)) {
453
- return { valid: false, values: [] };
454
- }
455
- while (/\s/.test(value[cursor] ?? '')) cursor += 1;
456
- if (cursor < value.length) {
457
- if (value[cursor] !== ',') return { valid: false, values: [] };
458
- cursor += 1;
459
- }
460
- }
461
- return {
462
- valid: values.length > 0,
463
- values,
464
- };
465
- }
466
-
467
- function hasUnsafeRawReferenceBytes(value) {
468
- if (
469
- value !== value.trim() ||
470
- value.includes('\\') ||
471
- value.includes('\0') ||
472
- /%(?:2e|2f|5c)/i.test(value)
473
- ) {
474
- return true;
475
- }
476
- for (const char of value) {
477
- const code = char.codePointAt(0);
478
- if (code < 0x20 || code === 0x7f) return true;
479
- }
480
- return false;
481
- }
482
-
483
- function isResourceAttribute(elementName, attributeName) {
484
- return (
485
- ['poster', 'src', 'srcset'].includes(attributeName) ||
486
- (['href', 'xlink:href'].includes(attributeName) &&
487
- !['a', 'area'].includes(elementName))
488
- );
489
- }
490
-
491
- function isCanonicalSitePath(value) {
492
- if (
493
- typeof value !== 'string' ||
494
- !/^site\/[a-z0-9._-]+(?:\/[a-z0-9._-]+)*$/i.test(value)
495
- ) {
496
- return false;
497
- }
498
- return value.split('/').every((segment) => !['.', '..'].includes(segment));
499
- }
500
-
501
- function referenceDetail(artifact, element, attribute, reference) {
502
- return {
503
- renderedPath: artifact.renderedPath,
504
- element: element.name,
505
- attribute: attribute.name,
506
- reference,
507
- };
508
- }
509
-
510
- function malformedHtml() {
511
- return {
512
- valid: false,
513
- code: 'malformed-html',
514
- message: 'Rendered HTML cannot be tokenized safely.',
515
- };
516
- }
517
-
518
- function indexOfCaseInsensitive(source, search, fromIndex) {
519
- return source.toLowerCase().indexOf(search.toLowerCase(), fromIndex);
520
- }
521
-
522
- function decodeHtmlReferences(value) {
523
- return value.replace(
524
- /&(?:amp|quot|apos|#39|#x27);/gi,
525
- (entity) =>
526
- ({
527
- '&amp;': '&',
528
- '&quot;': '"',
529
- '&apos;': "'",
530
- '&#39;': "'",
531
- '&#x27;': "'",
532
- })[entity.toLowerCase()] ?? entity,
533
- );
534
- }
535
-
536
- function isObject(value) {
537
- return typeof value === 'object' && value !== null && !Array.isArray(value);
538
- }