@vltpkg/query 1.4.0 → 1.6.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 (213) hide show
  1. package/dist/index.d.ts +7 -2
  2. package/dist/index.js +23 -4
  3. package/dist/pseudo/skill.d.ts +7 -0
  4. package/dist/pseudo/skill.js +52 -0
  5. package/dist/pseudo/skills.d.ts +6 -0
  6. package/dist/pseudo/skills.js +26 -0
  7. package/dist/pseudo.js +7 -0
  8. package/dist/types.d.ts +29 -1
  9. package/package.json +10 -12
  10. package/skills/dss-query/REFERENCE.md +0 -123
  11. package/skills/dss-query/SKILL.md +0 -190
  12. package/skills/dss-query/evals/README.md +0 -81
  13. package/skills/dss-query/evals/evals.json +0 -89
  14. package/skills/dss-query/evals/grade.mjs +0 -481
  15. package/src/attribute.ts +0 -190
  16. package/src/combinator.ts +0 -135
  17. package/src/error.ts +0 -78
  18. package/src/id.ts +0 -46
  19. package/src/index.ts +0 -640
  20. package/src/parser.ts +0 -122
  21. package/src/pseudo/abandoned.ts +0 -9
  22. package/src/pseudo/attr.ts +0 -92
  23. package/src/pseudo/built.ts +0 -19
  24. package/src/pseudo/confused.ts +0 -29
  25. package/src/pseudo/cve.ts +0 -93
  26. package/src/pseudo/cwe.ts +0 -97
  27. package/src/pseudo/debug.ts +0 -9
  28. package/src/pseudo/deprecated.ts +0 -9
  29. package/src/pseudo/dev.ts +0 -18
  30. package/src/pseudo/diff.ts +0 -90
  31. package/src/pseudo/dist.ts +0 -148
  32. package/src/pseudo/dynamic.ts +0 -9
  33. package/src/pseudo/empty.ts +0 -15
  34. package/src/pseudo/entropic.ts +0 -9
  35. package/src/pseudo/env.ts +0 -6
  36. package/src/pseudo/eval.ts +0 -9
  37. package/src/pseudo/fs.ts +0 -9
  38. package/src/pseudo/helpers.ts +0 -106
  39. package/src/pseudo/host.ts +0 -111
  40. package/src/pseudo/hostname.ts +0 -156
  41. package/src/pseudo/license.ts +0 -134
  42. package/src/pseudo/link.ts +0 -30
  43. package/src/pseudo/malware.ts +0 -41
  44. package/src/pseudo/minified.ts +0 -9
  45. package/src/pseudo/missing.ts +0 -16
  46. package/src/pseudo/native.ts +0 -9
  47. package/src/pseudo/network.ts +0 -9
  48. package/src/pseudo/obfuscated.ts +0 -9
  49. package/src/pseudo/optional.ts +0 -18
  50. package/src/pseudo/outdated.ts +0 -345
  51. package/src/pseudo/overridden.ts +0 -20
  52. package/src/pseudo/path.ts +0 -146
  53. package/src/pseudo/peer.ts +0 -18
  54. package/src/pseudo/prerelease.ts +0 -47
  55. package/src/pseudo/private.ts +0 -19
  56. package/src/pseudo/prod.ts +0 -18
  57. package/src/pseudo/published.ts +0 -250
  58. package/src/pseudo/registry.ts +0 -29
  59. package/src/pseudo/root.ts +0 -19
  60. package/src/pseudo/scanned.ts +0 -20
  61. package/src/pseudo/score.ts +0 -186
  62. package/src/pseudo/scripts.ts +0 -61
  63. package/src/pseudo/semver.ts +0 -320
  64. package/src/pseudo/severity.ts +0 -231
  65. package/src/pseudo/shell.ts +0 -9
  66. package/src/pseudo/shrinkwrap.ts +0 -9
  67. package/src/pseudo/spec.ts +0 -131
  68. package/src/pseudo/squat.ts +0 -225
  69. package/src/pseudo/suspicious.ts +0 -9
  70. package/src/pseudo/tracker.ts +0 -9
  71. package/src/pseudo/trivial.ts +0 -9
  72. package/src/pseudo/type.ts +0 -26
  73. package/src/pseudo/undesirable.ts +0 -9
  74. package/src/pseudo/unknown.ts +0 -9
  75. package/src/pseudo/unmaintained.ts +0 -9
  76. package/src/pseudo/unpopular.ts +0 -9
  77. package/src/pseudo/unstable.ts +0 -9
  78. package/src/pseudo/vuln.ts +0 -273
  79. package/src/pseudo/workspace.ts +0 -22
  80. package/src/pseudo.ts +0 -433
  81. package/src/types.ts +0 -176
  82. package/tap-snapshots/test/attribute.ts.test.cjs +0 -304
  83. package/tap-snapshots/test/combinator.ts.test.cjs +0 -333
  84. package/tap-snapshots/test/id.ts.test.cjs +0 -118
  85. package/tap-snapshots/test/parser.ts.test.cjs +0 -344
  86. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +0 -18
  87. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +0 -161
  88. package/tap-snapshots/test/pseudo/built.ts.test.cjs +0 -70
  89. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +0 -32
  90. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +0 -49
  91. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +0 -49
  92. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +0 -18
  93. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +0 -18
  94. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +0 -30
  95. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +0 -18
  96. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +0 -50
  97. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +0 -18
  98. package/tap-snapshots/test/pseudo/env.ts.test.cjs +0 -18
  99. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +0 -18
  100. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +0 -18
  101. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +0 -18
  102. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +0 -148
  103. package/tap-snapshots/test/pseudo/license.ts.test.cjs +0 -41
  104. package/tap-snapshots/test/pseudo/link.ts.test.cjs +0 -36
  105. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +0 -22
  106. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +0 -18
  107. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +0 -30
  108. package/tap-snapshots/test/pseudo/native.ts.test.cjs +0 -18
  109. package/tap-snapshots/test/pseudo/network.ts.test.cjs +0 -18
  110. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +0 -18
  111. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +0 -132
  112. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +0 -75
  113. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +0 -101
  114. package/tap-snapshots/test/pseudo/private.ts.test.cjs +0 -38
  115. package/tap-snapshots/test/pseudo/published.ts.test.cjs +0 -171
  116. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +0 -68
  117. package/tap-snapshots/test/pseudo/root.ts.test.cjs +0 -24
  118. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +0 -18
  119. package/tap-snapshots/test/pseudo/score.ts.test.cjs +0 -239
  120. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +0 -62
  121. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +0 -575
  122. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +0 -122
  123. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +0 -18
  124. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +0 -18
  125. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +0 -148
  126. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +0 -129
  127. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +0 -18
  128. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +0 -18
  129. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +0 -18
  130. package/tap-snapshots/test/pseudo/type.ts.test.cjs +0 -51
  131. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +0 -18
  132. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +0 -18
  133. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +0 -18
  134. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +0 -18
  135. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +0 -18
  136. package/tap-snapshots/test/pseudo/vuln.ts.test.cjs +0 -208
  137. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +0 -20
  138. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +0 -19
  139. package/tap-snapshots/test/pseudo.ts.test.cjs +0 -1247
  140. package/test/attribute.ts +0 -273
  141. package/test/combinator.ts +0 -175
  142. package/test/error.ts +0 -60
  143. package/test/fixtures/graph.ts +0 -999
  144. package/test/fixtures/selector.ts +0 -103
  145. package/test/fixtures/types.ts +0 -7
  146. package/test/id.ts +0 -102
  147. package/test/index.ts +0 -1049
  148. package/test/parser.ts +0 -117
  149. package/test/pseudo/abandoned.ts +0 -98
  150. package/test/pseudo/attr.ts +0 -298
  151. package/test/pseudo/built.ts +0 -214
  152. package/test/pseudo/confused.ts +0 -160
  153. package/test/pseudo/cve.ts +0 -249
  154. package/test/pseudo/cwe.ts +0 -255
  155. package/test/pseudo/debug.ts +0 -98
  156. package/test/pseudo/deprecated.ts +0 -98
  157. package/test/pseudo/dev.ts +0 -61
  158. package/test/pseudo/diff.ts +0 -417
  159. package/test/pseudo/dist.ts +0 -215
  160. package/test/pseudo/dynamic.ts +0 -98
  161. package/test/pseudo/empty.ts +0 -108
  162. package/test/pseudo/entropic.ts +0 -101
  163. package/test/pseudo/env.ts +0 -98
  164. package/test/pseudo/eval.ts +0 -98
  165. package/test/pseudo/fs.ts +0 -98
  166. package/test/pseudo/helpers.ts +0 -370
  167. package/test/pseudo/host-context.ts +0 -276
  168. package/test/pseudo/hostname.ts +0 -314
  169. package/test/pseudo/license.ts +0 -265
  170. package/test/pseudo/link.ts +0 -94
  171. package/test/pseudo/malware.ts +0 -184
  172. package/test/pseudo/minified.ts +0 -98
  173. package/test/pseudo/missing.ts +0 -111
  174. package/test/pseudo/native.ts +0 -98
  175. package/test/pseudo/network.ts +0 -98
  176. package/test/pseudo/obfuscated.ts +0 -98
  177. package/test/pseudo/optional.ts +0 -73
  178. package/test/pseudo/outdated.ts +0 -461
  179. package/test/pseudo/overridden.ts +0 -317
  180. package/test/pseudo/path.ts +0 -680
  181. package/test/pseudo/peer.ts +0 -101
  182. package/test/pseudo/prerelease.ts +0 -279
  183. package/test/pseudo/private.ts +0 -108
  184. package/test/pseudo/prod.ts +0 -61
  185. package/test/pseudo/published.ts +0 -557
  186. package/test/pseudo/registry.ts +0 -122
  187. package/test/pseudo/root.ts +0 -66
  188. package/test/pseudo/scanned.ts +0 -144
  189. package/test/pseudo/score.ts +0 -591
  190. package/test/pseudo/scripts.ts +0 -324
  191. package/test/pseudo/semver.ts +0 -822
  192. package/test/pseudo/severity.ts +0 -342
  193. package/test/pseudo/shell.ts +0 -98
  194. package/test/pseudo/shrinkwrap.ts +0 -98
  195. package/test/pseudo/spec.ts +0 -525
  196. package/test/pseudo/squat.ts +0 -565
  197. package/test/pseudo/suspicious.ts +0 -98
  198. package/test/pseudo/tracker.ts +0 -98
  199. package/test/pseudo/trivial.ts +0 -98
  200. package/test/pseudo/type.ts +0 -105
  201. package/test/pseudo/undesirable.ts +0 -98
  202. package/test/pseudo/unknown.ts +0 -98
  203. package/test/pseudo/unmaintained.ts +0 -98
  204. package/test/pseudo/unpopular.ts +0 -98
  205. package/test/pseudo/unstable.ts +0 -98
  206. package/test/pseudo/vuln.ts +0 -625
  207. package/test/pseudo/vulnerable.ts +0 -137
  208. package/test/pseudo/workspace.ts +0 -111
  209. package/test/pseudo.ts +0 -584
  210. package/tsconfig.json +0 -3
  211. package/tsconfig.publish.json +0 -19
  212. package/tsconfig.publish.tsbuildinfo +0 -1
  213. package/typedoc.mjs +0 -2
@@ -1,481 +0,0 @@
1
- /**
2
- * Deterministic grader for dss-query skill evals.
3
- * Run from src/query (so @vltpkg/* workspace deps resolve):
4
- * node skills/dss-query/evals/grade.mjs skills/dss-query-workspace/iteration-1
5
- *
6
- * Writes grading.json ({expectations: [{text, passed, evidence}]}) into each
7
- * <eval>/<variant>/ directory.
8
- */
9
- import {
10
- readFileSync,
11
- writeFileSync,
12
- readdirSync,
13
- existsSync,
14
- } from 'node:fs'
15
- import { join } from 'node:path'
16
- import { parse } from '@vltpkg/dss-parser'
17
- import { Query } from '../../../src/index.ts'
18
- import { getSimpleGraph } from '../../../test/fixtures/graph.ts'
19
-
20
- const iterDir = process.argv[2]
21
- if (!iterDir) throw new Error('usage: node grade.mjs <iteration-dir>')
22
-
23
- // ---- selector extraction -------------------------------------------------
24
- const extractSelectors = text => {
25
- const out = new Set()
26
- // vlt query '<sel>' / "<sel>" anywhere in the text
27
- for (const m of text.matchAll(/vlt query '([^']+)'/g)) out.add(m[1])
28
- for (const m of text.matchAll(/vlt query "([^"]+)"/g)) out.add(m[1])
29
- // bare selectors on their own line inside fenced code blocks
30
- for (const block of text.matchAll(/```[a-z]*\n([\s\S]*?)```/g)) {
31
- for (const line of block[1].split('\n')) {
32
- const t = line.trim()
33
- // '# ...' is a comment, not an ID selector (real IDs: '#react', no space)
34
- if (/^# /.test(t)) continue
35
- if (/^[:#[*]/.test(t) && !t.includes('<')) out.add(t)
36
- }
37
- }
38
- return [...out].filter(s => !s.includes('<'))
39
- }
40
-
41
- // ---- execution (offline only) ---------------------------------------------
42
- // :outdated fetches registry packuments; security selectors need the archive.
43
- const NO_EXEC = /:outdated|:missing/
44
- const skipExec = s => Query.hasSecuritySelectors(s) || NO_EXEC.test(s)
45
- // split top-level commas so loose mode can't mask invalid segments
46
- const topLevelSplit = s => {
47
- const parts = []
48
- let depth = 0,
49
- cur = ''
50
- for (const ch of s) {
51
- if (ch === '(' || ch === '[') depth++
52
- else if (ch === ')' || ch === ']') depth--
53
- if (ch === ',' && depth === 0) {
54
- parts.push(cur)
55
- cur = ''
56
- } else cur += ch
57
- }
58
- parts.push(cur)
59
- return parts.map(p => p.trim()).filter(Boolean)
60
- }
61
- const mockSignal = { throwIfAborted: () => {} }
62
- const makeQuery = () => {
63
- const graph = getSimpleGraph()
64
- return new Query({
65
- nodes: new Set(graph.nodes.values()),
66
- edges: graph.edges,
67
- importers: graph.importers,
68
- securityArchive: undefined,
69
- })
70
- }
71
-
72
- // parse() rejects invalid DSS (grammar + structure), not just bad syntax
73
- const parseAll = selectors => {
74
- const failures = []
75
- for (const s of selectors) {
76
- try {
77
- parse(s)
78
- } catch (err) {
79
- failures.push(`${s} → ${err.message}`)
80
- }
81
- }
82
- return failures
83
- }
84
-
85
- const execAll = async selectors => {
86
- const failures = []
87
- const skipped = []
88
- for (const s of selectors) {
89
- if (skipExec(s)) {
90
- skipped.push(s)
91
- continue
92
- }
93
- for (const seg of topLevelSplit(s)) {
94
- try {
95
- await makeQuery().search(seg, { signal: mockSignal })
96
- } catch (err) {
97
- failures.push(`${seg} → ${err.message}`)
98
- }
99
- }
100
- }
101
- return { failures, skipped }
102
- }
103
-
104
- // ---- per-eval assertion logic ----------------------------------------------
105
- const first = (arr, re) => arr.find(s => re.test(s))
106
- const preCodeText = text =>
107
- text.slice(0, Math.max(text.indexOf('```'), 0)).trim()
108
-
109
- const graders = {
110
- 0: async (text, sels) => {
111
- const hit = first(sels, /:root\s*>\s*\*?:outdated/)
112
- const parseFails = parseAll(sels)
113
- const { failures: execFails, skipped } = await execAll(sels)
114
- const pre = preCodeText(text)
115
- return [
116
- {
117
- text: 'proposes-root-outdated-selector',
118
- passed: !!hit,
119
- evidence: hit ?? `selectors: ${sels.join(' | ') || '(none)'}`,
120
- },
121
- {
122
- text: 'all-selectors-parse',
123
- passed: parseFails.length === 0,
124
- evidence:
125
- parseFails.join('; ') || `${sels.length} selectors parsed`,
126
- },
127
- {
128
- text: 'selector-executes-on-fixture-graph',
129
- passed: execFails.length === 0,
130
- evidence:
131
- execFails.join('; ') ||
132
- `executed ${sels.length - skipped.length}, skipped (network-dependent): ${skipped.length}`,
133
- },
134
- {
135
- text: 'query-first-style',
136
- passed: pre.length < 150,
137
- evidence:
138
- pre.length < 150 ?
139
- `only ${pre.length} chars before first code block`
140
- : `${pre.length} chars of preamble before first code block: "${pre.slice(0, 80)}..."`,
141
- },
142
- {
143
- text: 'mentions-view-flag',
144
- passed: /--view/.test(text),
145
- evidence:
146
- /--view/.test(text) ?
147
- `matched "${text.match(/--view\S*/)[0]}"`
148
- : 'no --view mention',
149
- },
150
- {
151
- text: 'offers-related-queries',
152
- passed: sels.length >= 2,
153
- evidence: `${sels.length} distinct selectors offered`,
154
- },
155
- ]
156
- },
157
- 1: async (text, sels) => {
158
- const attr = first(sels, /\[license\^?=\s*['"]?MIT/i)
159
- const badPseudo = /:license\(\s*['"]?mit/i.test(text)
160
- const anchored = first(sels, /:workspace\s*>\s*:prod/)
161
- const parseFails = parseAll(sels)
162
- return [
163
- {
164
- text: 'uses-license-attribute-form',
165
- passed: !!attr,
166
- evidence:
167
- attr ?? `selectors: ${sels.join(' | ') || '(none)'}`,
168
- },
169
- {
170
- text: 'avoids-invalid-license-pseudo',
171
- passed: !badPseudo,
172
- evidence:
173
- badPseudo ?
174
- 'answer proposes :license(mit), which throws "Expected a valid license kind"'
175
- : 'no :license(mit) present',
176
- },
177
- {
178
- text: 'workspace-prod-anchored',
179
- passed: !!anchored,
180
- evidence:
181
- anchored ?? `selectors: ${sels.join(' | ') || '(none)'}`,
182
- },
183
- {
184
- text: 'all-selectors-parse',
185
- passed: parseFails.length === 0,
186
- evidence:
187
- parseFails.join('; ') || `${sels.length} selectors parsed`,
188
- },
189
- {
190
- text: 'offers-related-queries',
191
- passed: sels.length >= 2,
192
- evidence: `${sels.length} distinct selectors offered`,
193
- },
194
- ]
195
- },
196
- 2: async (text, sels) => {
197
- const inv = first(
198
- sels,
199
- /:has\(\s*>\s*(#react|\[name=react\])\s*\)/,
200
- )
201
- const correction =
202
- /(#react\s*>\s*\*|wrong way|opposite direction|points? the wrong|selects? react'?s (own )?dependenc)/i.test(
203
- text,
204
- ) && /dependenc/i.test(text)
205
- // the recommended (first extracted) selector must not be the wrong-direction one
206
- const firstSel = sels[0] ?? ''
207
- const wrongAsAnswer = /^#react\s*>\s*\*$/.test(firstSel)
208
- const parseFails = parseAll(sels)
209
- const evidenceLine =
210
- text
211
- .split('\n')
212
- .find(l => /wrong|opposite|dependenc/i.test(l)) ?? ''
213
- return [
214
- {
215
- text: 'uses-has-inversion',
216
- passed: !!inv,
217
- evidence: inv ?? `selectors: ${sels.join(' | ') || '(none)'}`,
218
- },
219
- {
220
- text: 'corrects-direction-mistake',
221
- passed: correction,
222
- evidence:
223
- evidenceLine.trim().slice(0, 160) ||
224
- 'no direction-correction language found',
225
- },
226
- {
227
- text: 'does-not-present-wrong-selector-as-answer',
228
- passed: !wrongAsAnswer,
229
- evidence: `first recommended selector: ${firstSel || '(none)'}`,
230
- },
231
- {
232
- text: 'all-selectors-parse',
233
- passed: parseFails.length === 0,
234
- evidence:
235
- parseFails.join('; ') || `${sels.length} selectors parsed`,
236
- },
237
- {
238
- text: 'explains-combinator-rationale',
239
- passed:
240
- /point.{0,40}dependenc|dependent\s*(→|->|to)\s*dependenc|parent.{0,12}child/i.test(
241
- text,
242
- ),
243
- evidence: (
244
- text.split('\n').find(l => /point|parent|→|->/.test(l)) ??
245
- 'no general direction rule stated'
246
- )
247
- .trim()
248
- .slice(0, 160),
249
- },
250
- ]
251
- },
252
- 3: async text => {
253
- const checks = [
254
- ['covers-workspace-anchor', /workspace/i],
255
- ['covers-direct-prod-segment', null], // special: needs both
256
- ['covers-cve-segment', /cve/i],
257
- ['mentions-security-data-source', /socket|network/i],
258
- ]
259
- return checks.map(([name, re]) => {
260
- if (name === 'covers-direct-prod-segment') {
261
- const ok = /direct/i.test(text) && /prod/i.test(text)
262
- return {
263
- text: name,
264
- passed: ok,
265
- evidence:
266
- ok ?
267
- 'mentions both "direct" and "prod"'
268
- : `direct:${/direct/i.test(text)} prod:${/prod/i.test(text)}`,
269
- }
270
- }
271
- const m = text.match(re)
272
- return {
273
- text: name,
274
- passed: !!m,
275
- evidence: m ? `matched "${m[0]}"` : `no match for ${re}`,
276
- }
277
- })
278
- },
279
- 4: async (text, sels) => {
280
- const threat =
281
- /:malware/.test(text) &&
282
- /:squat|:obfuscated|:suspicious/.test(text)
283
- const parseFails = parseAll(sels)
284
- return [
285
- {
286
- text: 'uses-threat-selectors',
287
- passed: threat,
288
- evidence:
289
- threat ?
290
- `selectors: ${sels.join(' | ')}`
291
- : `malware:${/:malware/.test(text)} squat/obf/susp:${/:squat|:obfuscated|:suspicious/.test(text)}`,
292
- },
293
- {
294
- text: 'all-selectors-parse',
295
- passed: parseFails.length === 0,
296
- evidence:
297
- parseFails.join('; ') || `${sels.length} selectors parsed`,
298
- },
299
- {
300
- text: 'mentions-security-data-source',
301
- passed: /socket|network/i.test(text),
302
- evidence:
303
- text.match(/socket|network/i)?.[0] ??
304
- 'no Socket/network mention',
305
- },
306
- {
307
- text: 'offers-related-queries',
308
- passed: sels.length >= 2,
309
- evidence: `${sels.length} distinct selectors offered`,
310
- },
311
- ]
312
- },
313
- 5: async (text, sels) => {
314
- const cap =
315
- /:dev[^\s,']*:(shell|network)|:dev:is\([^)]*:(shell|network)/.test(
316
- text,
317
- )
318
- const both = /:shell/.test(text) && /:network/.test(text)
319
- const parseFails = parseAll(sels)
320
- return [
321
- {
322
- text: 'uses-capability-selectors',
323
- passed: cap,
324
- evidence:
325
- cap ?
326
- `selectors: ${sels.join(' | ')}`
327
- : `selectors: ${sels.join(' | ') || '(none)'}`,
328
- },
329
- {
330
- text: 'covers-both-capabilities',
331
- passed: both,
332
- evidence: `shell:${/:shell/.test(text)} network:${/:network/.test(text)}`,
333
- },
334
- {
335
- text: 'all-selectors-parse',
336
- passed: parseFails.length === 0,
337
- evidence:
338
- parseFails.join('; ') || `${sels.length} selectors parsed`,
339
- },
340
- {
341
- text: 'explains-capability-meaning',
342
- passed: /socket|static|capab|can\s/i.test(text),
343
- evidence:
344
- text.match(/socket|static|capab|can\s\S*/i)?.[0] ??
345
- 'no capability explanation',
346
- },
347
- ]
348
- },
349
- 6: async (text, sels) => {
350
- const sev =
351
- first(sels, /:sev(erity)?\(/) ??
352
- (/:sev(erity)?\(/.test(text) ?
353
- text.match(/:sev(erity)?\([^)]*\)/)[0]
354
- : undefined)
355
- const anchored = /:workspace\s*>\s*:prod/.test(text)
356
- const parseFails = parseAll(sels)
357
- return [
358
- {
359
- text: 'uses-severity-filter',
360
- passed: !!sev,
361
- evidence: sev ?? `selectors: ${sels.join(' | ') || '(none)'}`,
362
- },
363
- {
364
- text: 'anchors-workspace-prod',
365
- passed: anchored,
366
- evidence:
367
- anchored ?
368
- text.match(/:workspace\s*>\s*:prod\S*/)[0]
369
- : `selectors: ${sels.join(' | ') || '(none)'}`,
370
- },
371
- {
372
- text: 'all-selectors-parse',
373
- passed: parseFails.length === 0,
374
- evidence:
375
- parseFails.join('; ') || `${sels.length} selectors parsed`,
376
- },
377
- {
378
- text: 'mentions-security-data-source',
379
- passed: /socket|network/i.test(text),
380
- evidence:
381
- text.match(/socket|network/i)?.[0] ??
382
- 'no Socket/network mention',
383
- },
384
- ]
385
- },
386
- }
387
-
388
- // ---- round-3 style assertions (Examples tail, docs deep links, concision) ---
389
- const examplesHeading = text => ({
390
- text: 'uses-examples-heading',
391
- passed: /Examples:/.test(text) && !/Related:/.test(text),
392
- evidence: `Examples: ${/Examples:/.test(text)}, Related: ${/Related:/.test(text)}`,
393
- })
394
- const docsLink = re => text => ({
395
- text: 'ends-with-docs-link',
396
- passed: re.test(text),
397
- evidence: text.match(re)?.[0] ?? `no link matching ${re}`,
398
- })
399
- const conciseAnswer = max => text => {
400
- const lines = text.split('\n').filter(l => l.trim()).length
401
- return {
402
- text: 'concise-answer',
403
- passed: lines <= max,
404
- evidence: `${lines} non-empty lines (max ${max})`,
405
- }
406
- }
407
- const SELECTORS_DOC = /docs\.vlt\.io\/cli\/selectors/
408
- const round3Extras = {
409
- 0: [examplesHeading, docsLink(SELECTORS_DOC)],
410
- 1: [
411
- examplesHeading,
412
- // license query: either the structural selectors page or the
413
- // license-compliance section is a correct deep link
414
- docsLink(
415
- /docs\.vlt\.io\/cli\/(selectors|security#license-compliance)/,
416
- ),
417
- ],
418
- 2: [docsLink(SELECTORS_DOC)],
419
- 3: [
420
- docsLink(/docs\.vlt\.io\/cli\/security#vulnerability-detection/),
421
- ],
422
- 4: [docsLink(/docs\.vlt\.io\/cli\/security#malware-detection/)],
423
- 5: [
424
- conciseAnswer(22),
425
- docsLink(
426
- /docs\.vlt\.io\/cli\/security#behavioral-security-risks/,
427
- ),
428
- ],
429
- 6: [
430
- examplesHeading,
431
- docsLink(/docs\.vlt\.io\/cli\/security#vulnerability-detection/),
432
- ],
433
- }
434
-
435
- // ---- run --------------------------------------------------------------------
436
- for (const evalDir of readdirSync(iterDir).filter(d =>
437
- d.startsWith('eval-'),
438
- )) {
439
- const id = Number(evalDir.match(/^eval-(\d+)/)[1])
440
- const variants = readdirSync(join(iterDir, evalDir), {
441
- withFileTypes: true,
442
- })
443
- .filter(d => d.isDirectory())
444
- .map(d => d.name)
445
- for (const variant of variants) {
446
- const answerPath = join(
447
- iterDir,
448
- evalDir,
449
- variant,
450
- 'run-1',
451
- 'outputs',
452
- 'answer.md',
453
- )
454
- if (!existsSync(answerPath)) continue
455
- const text = readFileSync(answerPath, 'utf8')
456
- const sels = extractSelectors(text)
457
- const expectations = [
458
- ...(await graders[id](text, sels)),
459
- ...(round3Extras[id] ?? []).map(fn => fn(text)),
460
- ]
461
- const passed = expectations.filter(e => e.passed).length
462
- writeFileSync(
463
- join(iterDir, evalDir, variant, 'run-1', 'grading.json'),
464
- JSON.stringify(
465
- {
466
- summary: {
467
- total: expectations.length,
468
- passed,
469
- pass_rate: passed / expectations.length,
470
- },
471
- expectations,
472
- },
473
- null,
474
- 2,
475
- ) + '\n',
476
- )
477
- console.log(
478
- `${evalDir}/${variant}: ${passed}/${expectations.length}`,
479
- )
480
- }
481
- }
package/src/attribute.ts DELETED
@@ -1,190 +0,0 @@
1
- import { asAttributeNode } from '@vltpkg/dss-parser'
2
- import { error } from '@vltpkg/error-cause'
3
- import { queryError } from './error.ts'
4
- import { removeDanglingEdges } from './pseudo/helpers.ts'
5
- import type { AttributeOperatorName } from '@vltpkg/dss-parser'
6
- import type { NodeLike, JSONField, Manifest } from '@vltpkg/types'
7
- import type { ParserState } from './types.ts'
8
-
9
- export type ComparatorFn = (attr: string, value?: string) => boolean
10
-
11
- // JSONField has a mapped type constituent that would coerce to [object Object]
12
- // when stringified, which is what we want in this case.
13
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
14
- const jsonFieldToString = (v: JSONField) => String(v)
15
-
16
- /**
17
- * Retrieve the {@link Manifest} values found at the given `properties`
18
- * location for a given {@link Node}.
19
- */
20
- export const getManifestPropertyValues = (
21
- node: NodeLike,
22
- properties: string[],
23
- attribute: string,
24
- ): string[] | undefined => {
25
- if (!node.manifest) return
26
-
27
- const traverse = new Set<JSONField>([node.manifest])
28
- const props = new Set<JSONField>()
29
- for (const key of properties) {
30
- for (const prop of traverse) {
31
- /* c8 ignore start - should be impossible */
32
- if (!prop) {
33
- throw error('failed to find nested property in :attr', {
34
- found: properties,
35
- })
36
- }
37
- /* c8 ignore stop */
38
-
39
- // expand the result list to include nested array values
40
- if (Array.isArray(prop)) {
41
- for (const p of prop) {
42
- traverse.add(p)
43
- }
44
- continue
45
- }
46
-
47
- // guard for inspecting keys of objects next
48
- if (
49
- typeof prop === 'string' ||
50
- typeof prop === 'number' ||
51
- typeof prop === 'boolean'
52
- ) {
53
- continue
54
- }
55
-
56
- // assign next value when found
57
- if (key in prop) {
58
- const nextValue = prop[key]
59
- if (nextValue) {
60
- if (key === attribute) {
61
- props.add(nextValue)
62
- } else {
63
- traverse.delete(prop)
64
- traverse.add(nextValue)
65
- }
66
- }
67
- }
68
- }
69
- }
70
- // if no value was found after trying a given key
71
- // then there's nothing to be collected
72
- if (!props.size) return
73
-
74
- // expand the result to include array values
75
- const collect = new Set<string>()
76
- for (const prop of props) {
77
- if (Array.isArray(prop)) {
78
- for (const p of prop) {
79
- collect.add(p ? jsonFieldToString(p) : '')
80
- }
81
- } else {
82
- collect.add(jsonFieldToString(prop))
83
- }
84
- }
85
-
86
- return [...collect]
87
- }
88
-
89
- // decorator style of function that will filter `ParserState` results
90
- // based on a provided `comparator` function
91
- export const filterAttributes = (
92
- state: ParserState,
93
- comparator: ComparatorFn | undefined,
94
- value: string,
95
- propertyName: string,
96
- insensitive: boolean,
97
- prefixProperties: string[] = [],
98
- ): ParserState => {
99
- const check = (attr: JSONField) =>
100
- comparator?.(
101
- insensitive ?
102
- jsonFieldToString(attr).toLowerCase()
103
- : jsonFieldToString(attr),
104
- insensitive ? value.toLowerCase() : value,
105
- )
106
- const deleteNode = (node: NodeLike) => {
107
- for (const edge of node.edgesIn) {
108
- state.partial.edges.delete(edge)
109
- }
110
- state.partial.nodes.delete(node)
111
- }
112
-
113
- for (const node of state.partial.nodes) {
114
- const prefixes =
115
- prefixProperties.length ? prefixProperties : [propertyName]
116
- const attrs = getManifestPropertyValues(
117
- node,
118
- prefixes,
119
- propertyName,
120
- )
121
-
122
- // if no attribute value was found, that means the attribute won't match
123
- if (!attrs?.length) {
124
- deleteNode(node)
125
- continue
126
- }
127
-
128
- // if the node attribute value won't match, then remove the node
129
- if (comparator && !attrs.some(check)) {
130
- deleteNode(node)
131
- }
132
- }
133
-
134
- removeDanglingEdges(state)
135
- return state
136
- }
137
-
138
- // ref: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
139
- const attributeSelectors = {
140
- '=': (attr: string, value = '') => attr === value,
141
- '^=': (attr: string, value = '') => attr.startsWith(value),
142
- '$=': (attr: string, value = '') => attr.endsWith(value),
143
- '~=': (attr: string, value = '') =>
144
- new Set<string>(attr.match(/\w+/g)).has(value),
145
- '*=': (attr: string, value = '') => attr.includes(value),
146
- '|=': (attr: string, value = '') =>
147
- attr === value || attr.startsWith(`${value}-`),
148
- undefined: (attr: string) => !!attr,
149
- } satisfies Record<AttributeOperatorName | 'undefined', ComparatorFn>
150
- export const attributeSelectorsMap = new Map<string, ComparatorFn>(
151
- Object.entries(attributeSelectors),
152
- )
153
-
154
- /**
155
- * Parse attributes selectors, e.g: `[name]`, `[name=value]`, etc
156
- */
157
- export const attribute = async (
158
- state: ParserState,
159
- ): Promise<ParserState> => {
160
- await state.cancellable()
161
-
162
- const curr = asAttributeNode(state.current)
163
- const operatorFn = attributeSelectorsMap.get(String(curr.operator))
164
- if (!operatorFn) {
165
- if (state.loose) {
166
- return state
167
- }
168
-
169
- throw queryError(
170
- `Unsupported attribute operator: ${curr.operator}`,
171
- state.current,
172
- { validOptions: [...attributeSelectorsMap.keys()] },
173
- )
174
- }
175
-
176
- const value = curr.value || ''
177
- const propertyName = curr.attribute
178
- const insensitive = !!curr.insensitive
179
-
180
- // Increment the commonCounter for specificity
181
- state.specificity.commonCounter += 1
182
-
183
- return filterAttributes(
184
- state,
185
- operatorFn,
186
- value,
187
- propertyName,
188
- insensitive,
189
- )
190
- }