@vltpkg/query 1.5.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 (210) hide show
  1. package/package.json +10 -12
  2. package/skills/dss-query/REFERENCE.md +0 -125
  3. package/skills/dss-query/SKILL.md +0 -190
  4. package/skills/dss-query/evals/README.md +0 -81
  5. package/skills/dss-query/evals/evals.json +0 -89
  6. package/skills/dss-query/evals/grade.mjs +0 -481
  7. package/src/attribute.ts +0 -190
  8. package/src/combinator.ts +0 -135
  9. package/src/error.ts +0 -78
  10. package/src/id.ts +0 -46
  11. package/src/index.ts +0 -670
  12. package/src/parser.ts +0 -122
  13. package/src/pseudo/abandoned.ts +0 -9
  14. package/src/pseudo/attr.ts +0 -92
  15. package/src/pseudo/built.ts +0 -19
  16. package/src/pseudo/confused.ts +0 -29
  17. package/src/pseudo/cve.ts +0 -93
  18. package/src/pseudo/cwe.ts +0 -97
  19. package/src/pseudo/debug.ts +0 -9
  20. package/src/pseudo/deprecated.ts +0 -9
  21. package/src/pseudo/dev.ts +0 -18
  22. package/src/pseudo/diff.ts +0 -90
  23. package/src/pseudo/dist.ts +0 -148
  24. package/src/pseudo/dynamic.ts +0 -9
  25. package/src/pseudo/empty.ts +0 -15
  26. package/src/pseudo/entropic.ts +0 -9
  27. package/src/pseudo/env.ts +0 -6
  28. package/src/pseudo/eval.ts +0 -9
  29. package/src/pseudo/fs.ts +0 -9
  30. package/src/pseudo/helpers.ts +0 -106
  31. package/src/pseudo/host.ts +0 -111
  32. package/src/pseudo/hostname.ts +0 -156
  33. package/src/pseudo/license.ts +0 -134
  34. package/src/pseudo/link.ts +0 -30
  35. package/src/pseudo/malware.ts +0 -41
  36. package/src/pseudo/minified.ts +0 -9
  37. package/src/pseudo/missing.ts +0 -16
  38. package/src/pseudo/native.ts +0 -9
  39. package/src/pseudo/network.ts +0 -9
  40. package/src/pseudo/obfuscated.ts +0 -9
  41. package/src/pseudo/optional.ts +0 -18
  42. package/src/pseudo/outdated.ts +0 -345
  43. package/src/pseudo/overridden.ts +0 -20
  44. package/src/pseudo/path.ts +0 -146
  45. package/src/pseudo/peer.ts +0 -18
  46. package/src/pseudo/prerelease.ts +0 -47
  47. package/src/pseudo/private.ts +0 -19
  48. package/src/pseudo/prod.ts +0 -18
  49. package/src/pseudo/published.ts +0 -250
  50. package/src/pseudo/registry.ts +0 -29
  51. package/src/pseudo/root.ts +0 -19
  52. package/src/pseudo/scanned.ts +0 -20
  53. package/src/pseudo/score.ts +0 -186
  54. package/src/pseudo/scripts.ts +0 -61
  55. package/src/pseudo/semver.ts +0 -320
  56. package/src/pseudo/severity.ts +0 -231
  57. package/src/pseudo/shell.ts +0 -9
  58. package/src/pseudo/shrinkwrap.ts +0 -9
  59. package/src/pseudo/skill.ts +0 -59
  60. package/src/pseudo/skills.ts +0 -29
  61. package/src/pseudo/spec.ts +0 -131
  62. package/src/pseudo/squat.ts +0 -225
  63. package/src/pseudo/suspicious.ts +0 -9
  64. package/src/pseudo/tracker.ts +0 -9
  65. package/src/pseudo/trivial.ts +0 -9
  66. package/src/pseudo/type.ts +0 -26
  67. package/src/pseudo/undesirable.ts +0 -9
  68. package/src/pseudo/unknown.ts +0 -9
  69. package/src/pseudo/unmaintained.ts +0 -9
  70. package/src/pseudo/unpopular.ts +0 -9
  71. package/src/pseudo/unstable.ts +0 -9
  72. package/src/pseudo/vuln.ts +0 -273
  73. package/src/pseudo/workspace.ts +0 -22
  74. package/src/pseudo.ts +0 -440
  75. package/src/types.ts +0 -206
  76. package/tap-snapshots/test/attribute.ts.test.cjs +0 -304
  77. package/tap-snapshots/test/combinator.ts.test.cjs +0 -333
  78. package/tap-snapshots/test/id.ts.test.cjs +0 -118
  79. package/tap-snapshots/test/parser.ts.test.cjs +0 -344
  80. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +0 -18
  81. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +0 -161
  82. package/tap-snapshots/test/pseudo/built.ts.test.cjs +0 -70
  83. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +0 -32
  84. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +0 -49
  85. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +0 -49
  86. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +0 -18
  87. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +0 -18
  88. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +0 -30
  89. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +0 -18
  90. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +0 -50
  91. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +0 -18
  92. package/tap-snapshots/test/pseudo/env.ts.test.cjs +0 -18
  93. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +0 -18
  94. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +0 -18
  95. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +0 -18
  96. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +0 -148
  97. package/tap-snapshots/test/pseudo/license.ts.test.cjs +0 -41
  98. package/tap-snapshots/test/pseudo/link.ts.test.cjs +0 -36
  99. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +0 -22
  100. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +0 -18
  101. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +0 -30
  102. package/tap-snapshots/test/pseudo/native.ts.test.cjs +0 -18
  103. package/tap-snapshots/test/pseudo/network.ts.test.cjs +0 -18
  104. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +0 -18
  105. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +0 -132
  106. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +0 -75
  107. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +0 -101
  108. package/tap-snapshots/test/pseudo/private.ts.test.cjs +0 -38
  109. package/tap-snapshots/test/pseudo/published.ts.test.cjs +0 -171
  110. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +0 -68
  111. package/tap-snapshots/test/pseudo/root.ts.test.cjs +0 -24
  112. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +0 -18
  113. package/tap-snapshots/test/pseudo/score.ts.test.cjs +0 -239
  114. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +0 -62
  115. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +0 -575
  116. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +0 -122
  117. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +0 -18
  118. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +0 -18
  119. package/tap-snapshots/test/pseudo/skills.ts.test.cjs +0 -19
  120. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +0 -148
  121. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +0 -129
  122. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +0 -18
  123. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +0 -18
  124. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +0 -18
  125. package/tap-snapshots/test/pseudo/type.ts.test.cjs +0 -51
  126. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +0 -18
  127. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +0 -18
  128. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +0 -18
  129. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +0 -18
  130. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +0 -18
  131. package/tap-snapshots/test/pseudo/vuln.ts.test.cjs +0 -208
  132. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +0 -20
  133. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +0 -19
  134. package/tap-snapshots/test/pseudo.ts.test.cjs +0 -1247
  135. package/test/attribute.ts +0 -273
  136. package/test/combinator.ts +0 -175
  137. package/test/error.ts +0 -60
  138. package/test/fixtures/graph.ts +0 -999
  139. package/test/fixtures/selector.ts +0 -103
  140. package/test/fixtures/types.ts +0 -7
  141. package/test/id.ts +0 -102
  142. package/test/index.ts +0 -1148
  143. package/test/parser.ts +0 -117
  144. package/test/pseudo/abandoned.ts +0 -98
  145. package/test/pseudo/attr.ts +0 -298
  146. package/test/pseudo/built.ts +0 -214
  147. package/test/pseudo/confused.ts +0 -160
  148. package/test/pseudo/cve.ts +0 -249
  149. package/test/pseudo/cwe.ts +0 -255
  150. package/test/pseudo/debug.ts +0 -98
  151. package/test/pseudo/deprecated.ts +0 -98
  152. package/test/pseudo/dev.ts +0 -61
  153. package/test/pseudo/diff.ts +0 -417
  154. package/test/pseudo/dist.ts +0 -215
  155. package/test/pseudo/dynamic.ts +0 -98
  156. package/test/pseudo/empty.ts +0 -108
  157. package/test/pseudo/entropic.ts +0 -101
  158. package/test/pseudo/env.ts +0 -98
  159. package/test/pseudo/eval.ts +0 -98
  160. package/test/pseudo/fs.ts +0 -98
  161. package/test/pseudo/helpers.ts +0 -370
  162. package/test/pseudo/host-context.ts +0 -276
  163. package/test/pseudo/hostname.ts +0 -314
  164. package/test/pseudo/license.ts +0 -265
  165. package/test/pseudo/link.ts +0 -94
  166. package/test/pseudo/malware.ts +0 -184
  167. package/test/pseudo/minified.ts +0 -98
  168. package/test/pseudo/missing.ts +0 -111
  169. package/test/pseudo/native.ts +0 -98
  170. package/test/pseudo/network.ts +0 -98
  171. package/test/pseudo/obfuscated.ts +0 -98
  172. package/test/pseudo/optional.ts +0 -73
  173. package/test/pseudo/outdated.ts +0 -461
  174. package/test/pseudo/overridden.ts +0 -317
  175. package/test/pseudo/path.ts +0 -680
  176. package/test/pseudo/peer.ts +0 -101
  177. package/test/pseudo/prerelease.ts +0 -279
  178. package/test/pseudo/private.ts +0 -108
  179. package/test/pseudo/prod.ts +0 -61
  180. package/test/pseudo/published.ts +0 -557
  181. package/test/pseudo/registry.ts +0 -122
  182. package/test/pseudo/root.ts +0 -66
  183. package/test/pseudo/scanned.ts +0 -144
  184. package/test/pseudo/score.ts +0 -591
  185. package/test/pseudo/scripts.ts +0 -324
  186. package/test/pseudo/semver.ts +0 -822
  187. package/test/pseudo/severity.ts +0 -342
  188. package/test/pseudo/shell.ts +0 -98
  189. package/test/pseudo/shrinkwrap.ts +0 -98
  190. package/test/pseudo/skill.ts +0 -189
  191. package/test/pseudo/skills.ts +0 -90
  192. package/test/pseudo/spec.ts +0 -525
  193. package/test/pseudo/squat.ts +0 -565
  194. package/test/pseudo/suspicious.ts +0 -98
  195. package/test/pseudo/tracker.ts +0 -98
  196. package/test/pseudo/trivial.ts +0 -98
  197. package/test/pseudo/type.ts +0 -105
  198. package/test/pseudo/undesirable.ts +0 -98
  199. package/test/pseudo/unknown.ts +0 -98
  200. package/test/pseudo/unmaintained.ts +0 -98
  201. package/test/pseudo/unpopular.ts +0 -98
  202. package/test/pseudo/unstable.ts +0 -98
  203. package/test/pseudo/vuln.ts +0 -625
  204. package/test/pseudo/vulnerable.ts +0 -137
  205. package/test/pseudo/workspace.ts +0 -111
  206. package/test/pseudo.ts +0 -584
  207. package/tsconfig.json +0 -3
  208. package/tsconfig.publish.json +0 -19
  209. package/tsconfig.publish.tsbuildinfo +0 -1
  210. 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
- }