@su-record/vibe 4.2.34 → 4.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/cli/common.js +4 -1
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/heldout.js +10 -4
- package/dist/cli/heldout.js.map +1 -1
- package/dist/cli/human.js +8 -4
- package/dist/cli/human.js.map +1 -1
- package/dist/cli/internal.js +37 -24
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/ledger.js +67 -0
- package/dist/cli/ledger.js.map +1 -0
- package/dist/cli/map.js +5 -3
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/memory.js +104 -74
- package/dist/cli/memory.js.map +1 -1
- package/dist/cli/risks.js +20 -3
- package/dist/cli/risks.js.map +1 -1
- package/dist/cli/session.js +4 -2
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/setup.js +44 -13
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/verification.js +1 -1
- package/dist/cli/verification.js.map +1 -1
- package/dist/cli/work.js +75 -22
- package/dist/cli/work.js.map +1 -1
- package/dist/cli.js +18 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/analyze.js +21 -7
- package/dist/core/analyze.js.map +1 -1
- package/dist/core/artifact-paths.js +14 -3
- package/dist/core/artifact-paths.js.map +1 -1
- package/dist/core/capabilities.js +69 -15
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/check-authorization.js +37 -0
- package/dist/core/check-authorization.js.map +1 -0
- package/dist/core/check-process.js +12 -4
- package/dist/core/check-process.js.map +1 -1
- package/dist/core/check-report.js +66 -0
- package/dist/core/check-report.js.map +1 -0
- package/dist/core/check-selection.js +4 -2
- package/dist/core/check-selection.js.map +1 -1
- package/dist/core/check.js +66 -130
- package/dist/core/check.js.map +1 -1
- package/dist/core/checks/changed.js +20 -6
- package/dist/core/checks/changed.js.map +1 -1
- package/dist/core/checks/eval.js +35 -10
- package/dist/core/checks/eval.js.map +1 -1
- package/dist/core/checks/file.js +26 -23
- package/dist/core/checks/file.js.map +1 -1
- package/dist/core/checks/fileRules.js +42 -16
- package/dist/core/checks/fileRules.js.map +1 -1
- package/dist/core/checks/http.js +35 -34
- package/dist/core/checks/http.js.map +1 -1
- package/dist/core/checks/packs.js +1 -1
- package/dist/core/checks/packs.js.map +1 -1
- package/dist/core/checks/review.js +75 -40
- package/dist/core/checks/review.js.map +1 -1
- package/dist/core/checks/run.js +5 -2
- package/dist/core/checks/run.js.map +1 -1
- package/dist/core/checks/schema.js +25 -0
- package/dist/core/checks/schema.js.map +1 -0
- package/dist/core/checks/source.js +11 -6
- package/dist/core/checks/source.js.map +1 -1
- package/dist/core/config.js +9 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/consent.js +24 -16
- package/dist/core/consent.js.map +1 -1
- package/dist/core/context-notes.js +15 -7
- package/dist/core/context-notes.js.map +1 -1
- package/dist/core/context.js +24 -12
- package/dist/core/context.js.map +1 -1
- package/dist/core/conventions.js +22 -9
- package/dist/core/conventions.js.map +1 -1
- package/dist/core/docs/cfb.js +81 -45
- package/dist/core/docs/cfb.js.map +1 -1
- package/dist/core/docs/html.js +14 -3
- package/dist/core/docs/html.js.map +1 -1
- package/dist/core/docs/hwp.js +44 -22
- package/dist/core/docs/hwp.js.map +1 -1
- package/dist/core/docs/office.js +20 -10
- package/dist/core/docs/office.js.map +1 -1
- package/dist/core/docs/pdf.js +10 -4
- package/dist/core/docs/pdf.js.map +1 -1
- package/dist/core/docs/read.js +32 -24
- package/dist/core/docs/read.js.map +1 -1
- package/dist/core/docs/zip.js +32 -15
- package/dist/core/docs/zip.js.map +1 -1
- package/dist/core/evidence.js +17 -7
- package/dist/core/evidence.js.map +1 -1
- package/dist/core/failure-message.js +35 -13
- package/dist/core/failure-message.js.map +1 -1
- package/dist/core/failure.js +49 -14
- package/dist/core/failure.js.map +1 -1
- package/dist/core/github.js +3 -1
- package/dist/core/github.js.map +1 -1
- package/dist/core/handoff.js +20 -7
- package/dist/core/handoff.js.map +1 -1
- package/dist/core/heldout-run.js +6 -3
- package/dist/core/heldout-run.js.map +1 -1
- package/dist/core/heldout.js +24 -13
- package/dist/core/heldout.js.map +1 -1
- package/dist/core/inbox.js +3 -1
- package/dist/core/inbox.js.map +1 -1
- package/dist/core/inputs.js +76 -0
- package/dist/core/inputs.js.map +1 -0
- package/dist/core/inspect.js +61 -20
- package/dist/core/inspect.js.map +1 -1
- package/dist/core/intent.js +50 -12
- package/dist/core/intent.js.map +1 -1
- package/dist/core/knowledge-find.js +31 -12
- package/dist/core/knowledge-find.js.map +1 -1
- package/dist/core/knowledge.js +22 -12
- package/dist/core/knowledge.js.map +1 -1
- package/dist/core/lang.js +5 -2
- package/dist/core/lang.js.map +1 -1
- package/dist/core/ledger.js +44 -20
- package/dist/core/ledger.js.map +1 -1
- package/dist/core/locations.js +12 -0
- package/dist/core/locations.js.map +1 -0
- package/dist/core/map/imports.js +20 -9
- package/dist/core/map/imports.js.map +1 -1
- package/dist/core/map/index.js +28 -13
- package/dist/core/map/index.js.map +1 -1
- package/dist/core/map/symbols.js +27 -12
- package/dist/core/map/symbols.js.map +1 -1
- package/dist/core/output-capture.js +7 -4
- package/dist/core/output-capture.js.map +1 -1
- package/dist/core/paths.js +2 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/performance.js +43 -17
- package/dist/core/performance.js.map +1 -1
- package/dist/core/profile.js +6 -3
- package/dist/core/profile.js.map +1 -1
- package/dist/core/reader.js +32 -16
- package/dist/core/reader.js.map +1 -1
- package/dist/core/readerSession.js +34 -14
- package/dist/core/readerSession.js.map +1 -1
- package/dist/core/recent.js +20 -6
- package/dist/core/recent.js.map +1 -1
- package/dist/core/regress.js +21 -12
- package/dist/core/regress.js.map +1 -1
- package/dist/core/repair.js +22 -4
- package/dist/core/repair.js.map +1 -1
- package/dist/core/research-queries.js +85 -0
- package/dist/core/research-queries.js.map +1 -0
- package/dist/core/research.js +89 -88
- package/dist/core/research.js.map +1 -1
- package/dist/core/results.js +89 -0
- package/dist/core/results.js.map +1 -0
- package/dist/core/risk-signals.js +55 -16
- package/dist/core/risk-signals.js.map +1 -1
- package/dist/core/run-id.js +10 -6
- package/dist/core/run-id.js.map +1 -1
- package/dist/core/scenarios.js +97 -57
- package/dist/core/scenarios.js.map +1 -1
- package/dist/core/session.js +3 -2
- package/dist/core/session.js.map +1 -1
- package/dist/core/size-gate.js +57 -0
- package/dist/core/size-gate.js.map +1 -0
- package/dist/core/size.js +32 -10
- package/dist/core/size.js.map +1 -1
- package/dist/core/skills.js +66 -28
- package/dist/core/skills.js.map +1 -1
- package/dist/core/skillsmp.js +26 -12
- package/dist/core/skillsmp.js.map +1 -1
- package/dist/core/source-basis.js +21 -9
- package/dist/core/source-basis.js.map +1 -1
- package/dist/core/status.js +26 -13
- package/dist/core/status.js.map +1 -1
- package/dist/core/table.js +8 -1
- package/dist/core/table.js.map +1 -1
- package/dist/core/tokens.js +20 -10
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/tree.js +26 -16
- package/dist/core/tree.js.map +1 -1
- package/dist/core/verification-plan.js +26 -10
- package/dist/core/verification-plan.js.map +1 -1
- package/dist/core/verification.js +14 -7
- package/dist/core/verification.js.map +1 -1
- package/dist/core/view.js +93 -44
- package/dist/core/view.js.map +1 -1
- package/dist/install/global.js +37 -18
- package/dist/install/global.js.map +1 -1
- package/dist/install/hooks.js +17 -8
- package/dist/install/hooks.js.map +1 -1
- package/dist/install/manifest.js +11 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/mcpb.js +52 -23
- package/dist/install/mcpb.js.map +1 -1
- package/dist/install/plugin.js +73 -33
- package/dist/install/plugin.js.map +1 -1
- package/dist/install/project.js +8 -7
- package/dist/install/project.js.map +1 -1
- package/dist/install/register.js +83 -48
- package/dist/install/register.js.map +1 -1
- package/dist/install/tree.js +27 -11
- package/dist/install/tree.js.map +1 -1
- package/dist/install/update.js +46 -20
- package/dist/install/update.js.map +1 -1
- package/hooks/code-map.cjs +22 -9
- package/hooks/file-size.cjs +83 -0
- package/hooks/journal.cjs +28 -18
- package/hooks/knowledge-auto.cjs +55 -21
- package/hooks/knowledge-journal.cjs +47 -20
- package/hooks/knowledge-projects.cjs +50 -21
- package/hooks/knowledge-recall.cjs +16 -6
- package/hooks/knowledge-upkeep.cjs +22 -5
- package/hooks/locations.cjs +35 -0
- package/hooks/notify.js +52 -24
- package/hooks/obsidian.cjs +17 -7
- package/hooks/private-store.cjs +25 -10
- package/hooks/retest.cjs +39 -16
- package/hooks/session-context.cjs +30 -13
- package/hooks/session-files.cjs +44 -21
- package/hooks/session-repair.cjs +26 -11
- package/hooks/session-state.cjs +78 -38
- package/hooks/session.js +19 -8
- package/hooks/slice-command.cjs +38 -17
- package/hooks/slice-guard.cjs +20 -6
- package/hooks/slice-store.cjs +25 -12
- package/internal/guides/code.md +17 -0
- package/mcpb/server/index.js +40 -12
- package/package.json +1 -1
package/dist/core/checks/file.js
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { Ajv } from 'ajv';
|
|
4
3
|
import { formatOf, parseTable } from '../table.js';
|
|
5
|
-
import { a11yRule, traceableRule } from './fileRules.js';
|
|
4
|
+
import { a11yRule, traceableRule, describeFindings, MAX_FINDINGS } from './fileRules.js';
|
|
5
|
+
import { checkSchema } from './schema.js';
|
|
6
|
+
/** A failure reason's prefix to the stable failure code it reports. */
|
|
7
|
+
const FAILURE_CODES = {
|
|
8
|
+
'file not found': 'file-not-found',
|
|
9
|
+
'read failed': 'file-read-failed',
|
|
10
|
+
'schema/json parse failed': 'schema-invalid',
|
|
11
|
+
'schema mismatch': 'schema-mismatch',
|
|
12
|
+
'sum mismatch': 'sum-mismatch',
|
|
13
|
+
'bad pattern': 'pattern-invalid',
|
|
14
|
+
'bad absent': 'pattern-invalid',
|
|
15
|
+
'forbidden text present': 'forbidden-text',
|
|
16
|
+
'untraceable number': 'untraceable-number',
|
|
17
|
+
'accessibility defect': 'accessibility-defect',
|
|
18
|
+
};
|
|
6
19
|
function done(pass, started, tail, reason) {
|
|
7
20
|
const result = { pass, exit: pass ? 0 : 1, ms: Date.now() - started, tail };
|
|
8
21
|
if (!pass) {
|
|
9
|
-
|
|
10
|
-
result.failureCode = Object.entries(kinds).find(([prefix]) => reason?.startsWith(prefix))?.[1] ?? 'file-rule-failed';
|
|
22
|
+
result.failureCode = Object.entries(FAILURE_CODES).find(([prefix]) => reason?.startsWith(prefix))?.[1] ?? 'file-rule-failed';
|
|
11
23
|
}
|
|
12
24
|
if (reason)
|
|
13
25
|
result.reason = reason;
|
|
14
26
|
return result;
|
|
15
27
|
}
|
|
16
28
|
function schemaRule(schemaPath, content, root, started) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
catch (error) {
|
|
24
|
-
return done(false, started, '', `schema/json parse failed: ${error.message}`);
|
|
25
|
-
}
|
|
26
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
27
|
-
const validate = ajv.compile(schema);
|
|
28
|
-
if (validate(data))
|
|
29
|
-
return null;
|
|
30
|
-
const errors = (validate.errors ?? []).slice(0, 5).map((e) => `${e.instancePath || '/'} ${e.message ?? ''}`).join('\n');
|
|
31
|
-
return done(false, started, errors, 'schema mismatch');
|
|
29
|
+
const verdict = checkSchema(root, schemaPath, content);
|
|
30
|
+
if (verdict.kind === 'unreadable')
|
|
31
|
+
return done(false, started, '', `schema/json parse failed: ${verdict.message}`);
|
|
32
|
+
if (verdict.kind === 'mismatch')
|
|
33
|
+
return done(false, started, verdict.errors, 'schema mismatch');
|
|
34
|
+
return null;
|
|
32
35
|
}
|
|
33
36
|
function sumRule(check, content, started) {
|
|
34
37
|
const sum = check.sum;
|
|
@@ -82,8 +85,8 @@ function absentRule(expression, content, started) {
|
|
|
82
85
|
for (const m of content.matchAll(re)) {
|
|
83
86
|
if (m[0] === '')
|
|
84
87
|
break;
|
|
85
|
-
hits.push(
|
|
86
|
-
if (hits.length ===
|
|
88
|
+
hits.push(describeFindings([{ line: content.slice(0, m.index).split('\n').length, text: m[0] }]));
|
|
89
|
+
if (hits.length === MAX_FINDINGS)
|
|
87
90
|
break;
|
|
88
91
|
}
|
|
89
92
|
return hits.length === 0 ? null : done(false, started, hits.join('\n'), 'forbidden text present');
|
|
@@ -128,12 +131,12 @@ export function fileCheck(check, root) {
|
|
|
128
131
|
if (typeof r === 'string')
|
|
129
132
|
return done(false, started, '', r);
|
|
130
133
|
if (r.length > 0)
|
|
131
|
-
return done(false, started, r
|
|
134
|
+
return done(false, started, describeFindings(r), 'untraceable number');
|
|
132
135
|
}
|
|
133
136
|
if (check.a11y === true) {
|
|
134
137
|
const r = a11yRule(content, check.path);
|
|
135
138
|
if (r.length > 0)
|
|
136
|
-
return done(false, started, r
|
|
139
|
+
return done(false, started, describeFindings(r), 'accessibility defect');
|
|
137
140
|
}
|
|
138
141
|
if (check.schema !== undefined) {
|
|
139
142
|
const failed = schemaRule(check.schema, content, root, started);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/core/checks/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/core/checks/file.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
/** How many findings a rule reports, so the writer sees where to start without a wall of lines. */
|
|
4
|
+
export const MAX_FINDINGS = 3;
|
|
5
|
+
/** WCAG AA contrast for normal text. */
|
|
6
|
+
const MIN_CONTRAST_RATIO = 4.5;
|
|
7
|
+
/** WCAG relative luminance: the linear-light threshold and the red, green, blue weights. */
|
|
8
|
+
const SRGB_LINEAR_CUTOFF = 0.03928;
|
|
9
|
+
/** The sRGB transfer function (WCAG 2 relative luminance). */
|
|
10
|
+
const CHANNEL_MAX = 255;
|
|
11
|
+
const SRGB_LINEAR_SLOPE = 12.92;
|
|
12
|
+
const SRGB_OFFSET = 0.055;
|
|
13
|
+
const SRGB_GAMMA = 2.4;
|
|
14
|
+
/** WCAG adds this to each luminance so a pure black still divides. */
|
|
15
|
+
const CONTRAST_FLARE = 0.05;
|
|
16
|
+
const LUMA_WEIGHTS = [0.2126, 0.7152, 0.0722];
|
|
3
17
|
const NUMBER = /(?<![\w.])(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?%?(?![\w.])/g;
|
|
4
18
|
function lineOf(content, index) {
|
|
5
19
|
return content.slice(0, index).split('\n').length;
|
|
@@ -15,7 +29,8 @@ function numbersIn(content, skipFences) {
|
|
|
15
29
|
const digits = raw.replace(/[^\d]/g, '');
|
|
16
30
|
if (digits.replace(/^0+/, '').length < 2)
|
|
17
31
|
continue; // one significant digit is a word, not a figure
|
|
18
|
-
|
|
32
|
+
const key = raw.replace(/,/g, '').replace(/%$/, '');
|
|
33
|
+
out.push({ line: lineOf(text, m.index ?? 0), text: raw, key });
|
|
19
34
|
}
|
|
20
35
|
return out;
|
|
21
36
|
}
|
|
@@ -28,7 +43,8 @@ export function traceableRule(content, evidenceFile, root) {
|
|
|
28
43
|
for (const token of fs.readFileSync(full, 'utf-8').split(/[^\d.]+/))
|
|
29
44
|
if (token)
|
|
30
45
|
present.add(token.replace(/\.$/, ''));
|
|
31
|
-
|
|
46
|
+
const missing = numbersIn(content, true).filter((n) => !present.has(n.key));
|
|
47
|
+
return missing.slice(0, MAX_FINDINGS).map(({ line, text }) => ({ line, text }));
|
|
32
48
|
}
|
|
33
49
|
function luminance(hex) {
|
|
34
50
|
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
@@ -44,10 +60,10 @@ function luminance(hex) {
|
|
|
44
60
|
rgb = [Number(r[1]), Number(r[2]), Number(r[3])];
|
|
45
61
|
}
|
|
46
62
|
const [r, g, b] = rgb.map((v) => {
|
|
47
|
-
const c = v /
|
|
48
|
-
return c <=
|
|
63
|
+
const c = v / CHANNEL_MAX;
|
|
64
|
+
return c <= SRGB_LINEAR_CUTOFF ? c / SRGB_LINEAR_SLOPE : ((c + SRGB_OFFSET) / (1 + SRGB_OFFSET)) ** SRGB_GAMMA;
|
|
49
65
|
});
|
|
50
|
-
return 0
|
|
66
|
+
return LUMA_WEIGHTS[0] * r + LUMA_WEIGHTS[1] * g + LUMA_WEIGHTS[2] * b;
|
|
51
67
|
}
|
|
52
68
|
export function contrastRatio(fg, bg) {
|
|
53
69
|
const a = luminance(fg);
|
|
@@ -55,9 +71,9 @@ export function contrastRatio(fg, bg) {
|
|
|
55
71
|
if (a === null || b === null)
|
|
56
72
|
return null;
|
|
57
73
|
const [hi, lo] = a > b ? [a, b] : [b, a];
|
|
58
|
-
return (hi +
|
|
74
|
+
return (hi + CONTRAST_FLARE) / (lo + CONTRAST_FLARE);
|
|
59
75
|
}
|
|
60
|
-
/** CSS rules that set both a text colour and a background: the pair must reach
|
|
76
|
+
/** CSS rules that set both a text colour and a background: the pair must reach MIN_CONTRAST_RATIO. */
|
|
61
77
|
function contrastFindings(css, offset, whole) {
|
|
62
78
|
const out = [];
|
|
63
79
|
const rule = /\{([^{}]*)\}/g;
|
|
@@ -68,11 +84,13 @@ function contrastFindings(css, offset, whole) {
|
|
|
68
84
|
if (!fg || !bg)
|
|
69
85
|
continue;
|
|
70
86
|
const ratio = contrastRatio(fg, bg.split(/\s+/)[0] ?? bg);
|
|
71
|
-
if (ratio
|
|
72
|
-
|
|
87
|
+
if (ratio === null || ratio >= MIN_CONTRAST_RATIO)
|
|
88
|
+
continue;
|
|
89
|
+
out.push({ line: lineOf(whole, offset + (m.index ?? 0)), text: `contrast ${ratio.toFixed(2)}:1 for color ${fg.trim()} on ${bg.trim()}` });
|
|
73
90
|
}
|
|
74
91
|
return out;
|
|
75
92
|
}
|
|
93
|
+
const ARIA_LABEL = /aria-label(?:ledby)?\s*=/i;
|
|
76
94
|
const TAG = (name) => new RegExp(`<${name}\\b([^>]*)>`, 'gi');
|
|
77
95
|
function unnamed(html, tag) {
|
|
78
96
|
const out = [];
|
|
@@ -80,18 +98,24 @@ function unnamed(html, tag) {
|
|
|
80
98
|
for (const m of html.matchAll(re)) {
|
|
81
99
|
const attrs = m[1] ?? '';
|
|
82
100
|
const text = (m[2] ?? '').replace(/<[^>]+>/g, '').trim();
|
|
83
|
-
if (
|
|
84
|
-
|
|
101
|
+
if (text || ARIA_LABEL.test(attrs))
|
|
102
|
+
continue;
|
|
103
|
+
out.push({ line: lineOf(html, m.index ?? 0), text: `<${tag}> without text or aria-label` });
|
|
85
104
|
}
|
|
86
105
|
return out;
|
|
87
106
|
}
|
|
107
|
+
/** Findings as the `line N: text` lines a check result shows. */
|
|
108
|
+
export function describeFindings(findings) {
|
|
109
|
+
return findings.map((f) => `line ${f.line}: ${f.text}`).join('\n');
|
|
110
|
+
}
|
|
88
111
|
export function a11yRule(content, file) {
|
|
89
112
|
const out = [];
|
|
90
113
|
if (/\.css$/i.test(file))
|
|
91
114
|
return contrastFindings(content, 0, content);
|
|
92
|
-
for (const m of content.matchAll(TAG('img')))
|
|
115
|
+
for (const m of content.matchAll(TAG('img'))) {
|
|
93
116
|
if (!/\balt\s*=/i.test(m[1] ?? ''))
|
|
94
117
|
out.push({ line: lineOf(content, m.index ?? 0), text: '<img> without alt' });
|
|
118
|
+
}
|
|
95
119
|
let last = 0;
|
|
96
120
|
for (const m of content.matchAll(/<h([1-6])\b/gi)) {
|
|
97
121
|
const level = Number(m[1]);
|
|
@@ -106,11 +130,13 @@ export function a11yRule(content, file) {
|
|
|
106
130
|
if (/type\s*=\s*["']?(hidden|submit|button)/i.test(attrs))
|
|
107
131
|
continue;
|
|
108
132
|
const id = /\bid\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1];
|
|
109
|
-
if (!(id && labelled.has(id)) &&
|
|
133
|
+
if (!(id && labelled.has(id)) && !ARIA_LABEL.test(attrs))
|
|
110
134
|
out.push({ line: lineOf(content, m.index ?? 0), text: `<${m[1]}> without a label` });
|
|
111
135
|
}
|
|
112
|
-
for (const m of content.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi))
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
for (const m of content.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)) {
|
|
137
|
+
const css = m[1] ?? '';
|
|
138
|
+
out.push(...contrastFindings(css, (m.index ?? 0) + m[0].indexOf(css), content));
|
|
139
|
+
}
|
|
140
|
+
return out.sort((a, b) => a.line - b.line).slice(0, MAX_FINDINGS);
|
|
115
141
|
}
|
|
116
142
|
//# sourceMappingURL=fileRules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileRules.js","sourceRoot":"","sources":["../../../src/core/checks/fileRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAY7B,MAAM,MAAM,GAAG,4DAA4D,CAAC;AAE5E,SAAS,MAAM,CAAC,OAAe,EAAE,KAAa;IAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AACpD,CAAC;AAED,0GAA0G;AAC1G,SAAS,SAAS,CAAC,OAAe,EAAE,UAAmB;IACrD,MAAM,GAAG,GAAqC,EAAE,CAAC;IACjD,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,UAAU;QAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3F,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS,CAAC,gDAAgD;QACpG,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"fileRules.js","sourceRoot":"","sources":["../../../src/core/checks/fileRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAY7B,mGAAmG;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,wCAAwC;AACxC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,4FAA4F;AAC5F,MAAM,kBAAkB,GAAG,OAAO,CAAC;AACnC,8DAA8D;AAC9D,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAChC,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,sEAAsE;AACtE,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAEvD,MAAM,MAAM,GAAG,4DAA4D,CAAC;AAE5E,SAAS,MAAM,CAAC,OAAe,EAAE,KAAa;IAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AACpD,CAAC;AAED,0GAA0G;AAC1G,SAAS,SAAS,CAAC,OAAe,EAAE,UAAmB;IACrD,MAAM,GAAG,GAAqC,EAAE,CAAC;IACjD,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,UAAU;QAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3F,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS,CAAC,gDAAgD;QACpG,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,YAAoB,EAAE,IAAY;IAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,0BAA0B,YAAY,EAAE,CAAC;IAC1E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5F,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IACtH,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5E,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,CAAC,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC;QACN,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;QAClF,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;QAC1B,OAAO,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,UAAU,CAAC;IACjH,CAAC,CAA6B,CAAC;IAC/B,OAAO,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAU,EAAE,EAAU;IAClD,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACxB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,GAAG,cAAc,CAAC,CAAC;AACvD,CAAC;AAED,sGAAsG;AACtG,SAAS,gBAAgB,CAAC,GAAW,EAAE,MAAc,EAAE,KAAa;IAClE,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,eAAe,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG,sCAAsC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE;YAAE,SAAS;QACzB,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,kBAAkB;YAAE,SAAS;QAC5D,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5I,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAC/C,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,aAAa,EAAE,IAAI,CAAC,CAAC;AAE9E,SAAS,OAAO,CAAC,IAAY,EAAE,GAAW;IACxC,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,4BAA4B,GAAG,GAAG,EAAE,IAAI,CAAC,CAAC;IACvE,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,IAAI,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QAC7C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,8BAA8B,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,gBAAgB,CAAC,QAAmB;IAClD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,IAAY;IACpD,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACvE,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;IACnH,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,uBAAuB,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC,CAAC;QAClI,IAAI,GAAG,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,6CAA6C,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChH,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAAE,CAAC;QACzE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,yCAAyC,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACpE,MAAM,EAAE,GAAG,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,CAAC,EAAE,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACjJ,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,qCAAqC,CAAC,EAAE,CAAC;QACxE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;AACpE,CAAC"}
|
package/dist/core/checks/http.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Ajv } from 'ajv';
|
|
4
1
|
import { tail } from './run.js';
|
|
5
2
|
import { outputCapture } from '../output-capture.js';
|
|
3
|
+
import { checkSchema } from './schema.js';
|
|
6
4
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
export const HTTP_OK = 200;
|
|
6
|
+
/** Streams the body into `streams`; true when the capture limit was hit and the request was cut off. */
|
|
7
|
+
async function readBody(response, streams, controller) {
|
|
8
|
+
const reader = response.body?.getReader();
|
|
9
|
+
if (!reader)
|
|
10
|
+
return false;
|
|
11
|
+
for (;;) {
|
|
12
|
+
const item = await reader.read();
|
|
13
|
+
if (item.done)
|
|
14
|
+
return false;
|
|
15
|
+
if (streams.add('stdout', Buffer.from(item.value))) {
|
|
16
|
+
controller.abort();
|
|
17
|
+
await reader.cancel();
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
7
22
|
/** `http` check — status code, body against a JSON Schema, latency ceiling. The harness makes the request. */
|
|
8
23
|
export async function httpCheck(check, root) {
|
|
9
24
|
const started = Date.now();
|
|
@@ -11,56 +26,42 @@ export async function httpCheck(check, root) {
|
|
|
11
26
|
const timer = setTimeout(() => controller.abort(), check.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
12
27
|
const streams = outputCapture();
|
|
13
28
|
let response;
|
|
14
|
-
let body;
|
|
15
29
|
let overflow = false;
|
|
16
30
|
try {
|
|
17
31
|
response = await fetch(check.url, { method: check.method ?? 'GET', signal: controller.signal, redirect: 'manual' });
|
|
18
|
-
|
|
19
|
-
if (reader)
|
|
20
|
-
for (;;) {
|
|
21
|
-
const item = await reader.read();
|
|
22
|
-
if (item.done)
|
|
23
|
-
break;
|
|
24
|
-
if (streams.add('stdout', Buffer.from(item.value))) {
|
|
25
|
-
overflow = true;
|
|
26
|
-
controller.abort();
|
|
27
|
-
await reader.cancel();
|
|
28
|
-
break;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
32
|
+
overflow = await readBody(response, streams, controller);
|
|
31
33
|
}
|
|
32
34
|
catch (error) {
|
|
33
35
|
clearTimeout(timer);
|
|
34
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
...streams.finish(false),
|
|
38
|
+
pass: false,
|
|
39
|
+
exit: null,
|
|
40
|
+
ms: Date.now() - started,
|
|
41
|
+
tail: '',
|
|
42
|
+
reason: `request failed: ${error.message}`,
|
|
43
|
+
failureCode: controller.signal.aborted ? 'timeout' : 'request-failed',
|
|
44
|
+
};
|
|
35
45
|
}
|
|
36
46
|
clearTimeout(timer);
|
|
37
47
|
const output = streams.finish(!overflow);
|
|
38
|
-
body = output.raw.stdout.toString('utf8');
|
|
48
|
+
const body = output.raw.stdout.toString('utf8');
|
|
39
49
|
if (overflow)
|
|
40
50
|
return { ...output, pass: false, exit: response.status, ms: Date.now() - started, tail: '', failureCode: 'capture-overflow' };
|
|
41
51
|
const ms = Date.now() - started;
|
|
42
52
|
const expect = check.expect ?? {};
|
|
43
|
-
const status = expect.status ??
|
|
53
|
+
const status = expect.status ?? HTTP_OK;
|
|
44
54
|
const base = { ...output, exit: response.status, ms, tail: tail(body) };
|
|
45
55
|
if (response.status !== status)
|
|
46
56
|
return { ...base, pass: false, reason: `status ${response.status}, expected ${status}` };
|
|
47
57
|
if (expect.maxMs !== undefined && ms > expect.maxMs)
|
|
48
58
|
return { ...base, pass: false, reason: `${ms}ms, limit ${expect.maxMs}ms` };
|
|
49
59
|
if (expect.schema !== undefined) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
return { ...base, pass: false, reason: `schema/json parse failed: ${error.message}` };
|
|
58
|
-
}
|
|
59
|
-
const validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
|
|
60
|
-
if (!validate(data)) {
|
|
61
|
-
const errors = (validate.errors ?? []).slice(0, 5).map((e) => `${e.instancePath || '/'} ${e.message ?? ''}`).join('\n');
|
|
62
|
-
return { ...base, pass: false, tail: errors, reason: 'schema mismatch' };
|
|
63
|
-
}
|
|
60
|
+
const verdict = checkSchema(root, expect.schema, body);
|
|
61
|
+
if (verdict.kind === 'unreadable')
|
|
62
|
+
return { ...base, pass: false, reason: `schema/json parse failed: ${verdict.message}` };
|
|
63
|
+
if (verdict.kind === 'mismatch')
|
|
64
|
+
return { ...base, pass: false, tail: verdict.errors, reason: 'schema mismatch' };
|
|
64
65
|
}
|
|
65
66
|
return { ...base, pass: true };
|
|
66
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/core/checks/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/core/checks/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { packageRoot } from '../paths.js';
|
|
4
|
-
const STAGE_FILE = /^(\d+)-([a-z0-9]+(?:-[a-z0-9]+)*)\.md$/;
|
|
4
|
+
export const STAGE_FILE = /^(\d+)-([a-z0-9]+(?:-[a-z0-9]+)*)\.md$/;
|
|
5
5
|
export function reviewersDir(pack, root = packageRoot()) {
|
|
6
6
|
return path.join(root, 'reviewers', pack);
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packs.js","sourceRoot":"","sources":["../../../src/core/checks/packs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAc1C,MAAM,UAAU,GAAG,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"packs.js","sourceRoot":"","sources":["../../../src/core/checks/packs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAc1C,MAAM,CAAC,MAAM,UAAU,GAAG,wCAAwC,CAAC;AAEnE,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAe,WAAW,EAAE;IACrE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAe,WAAW,EAAE;IACnE,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,WAAW,EAAE;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1H,CAAC;AAED,uGAAuG;AACvG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU,CAAC"}
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { detectLang } from '../lang.js';
|
|
4
4
|
import { recordUsage } from '../ledger.js';
|
|
5
5
|
import { roleChoice } from '../config.js';
|
|
6
|
-
import { claudeArgs, codexArgs, driverLabel, hasCli, parseClaude, parseCodex, readerHome, REVIEWER_DRIVER, spawnReader, withChoice } from '../readerSession.js';
|
|
6
|
+
import { claudeArgs, codexArgs, driverLabel, hasCli, parseClaude, parseCodex, readerHome, REVIEWER_DRIVER, spawnReader, withChoice, } from '../readerSession.js';
|
|
7
7
|
import { ensureDir } from '../store.js';
|
|
8
8
|
import { packStages, TEXT_PACKS } from './packs.js';
|
|
9
9
|
import { collectChanged } from './changed.js';
|
|
@@ -11,19 +11,27 @@ import { collectSource } from './source.js';
|
|
|
11
11
|
import { tail } from './run.js';
|
|
12
12
|
import { outputCapture } from '../output-capture.js';
|
|
13
13
|
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
14
|
+
/** Source handed to the reviewers is capped at what fits their context. */
|
|
14
15
|
const MAX_SOURCE_CHARS = 400_000;
|
|
16
|
+
const REVIEW_CMD_ENV = 'VIBE_REVIEW_CMD';
|
|
17
|
+
const REVIEW_CLIENT_ENV = 'VIBE_REVIEW_CLIENT';
|
|
18
|
+
const PASS_VERDICT = 'PASS';
|
|
19
|
+
const MOCKUP_INSTRUCTION = 'the person confirmed this mockup before the work was built; open it with your file reader and judge the result against it.';
|
|
20
|
+
const NUMBER_LOCALE = 'en-US';
|
|
15
21
|
/**
|
|
16
22
|
* The reviewer: `VIBE_REVIEW_CMD` (a stateless text command — tests, custom clients), else the
|
|
17
23
|
* client CLI on PATH through the slim driver, `VIBE_REVIEW_CLIENT` forcing one. The model is the
|
|
18
24
|
* client's default: judgment keeps the strong model; only the project context is dropped.
|
|
19
25
|
*/
|
|
20
26
|
export function chooseReviewer(root = process.cwd()) {
|
|
21
|
-
const custom = process.env[
|
|
27
|
+
const custom = process.env[REVIEW_CMD_ENV];
|
|
22
28
|
if (custom)
|
|
23
29
|
return { client: 'custom', label: custom, cmd: custom };
|
|
24
30
|
const driver = withChoice(REVIEWER_DRIVER, roleChoice(root, 'reviewer'));
|
|
25
|
-
const pick = (client) =>
|
|
26
|
-
|
|
31
|
+
const pick = (client) => {
|
|
32
|
+
return { client, label: `${driverLabel(client, driver)} (slim)`, cmd: client, driver };
|
|
33
|
+
};
|
|
34
|
+
const forced = process.env[REVIEW_CLIENT_ENV];
|
|
27
35
|
if (forced === 'claude' || forced === 'codex')
|
|
28
36
|
return pick(forced);
|
|
29
37
|
if (hasCli('claude'))
|
|
@@ -53,8 +61,9 @@ function artifact(check, root, pack) {
|
|
|
53
61
|
const kind = pack === 'code' ? 'code' : 'design';
|
|
54
62
|
if (check.changed) {
|
|
55
63
|
const c = collectChanged(root, check.path, MAX_SOURCE_CHARS, kind, check.changed === true ? true : check.changed);
|
|
56
|
-
if (c.selection.changed.length === 0)
|
|
64
|
+
if (c.selection.changed.length === 0) {
|
|
57
65
|
return { section: 'Source', body: '', nothing: `nothing changed under ${check.path} since ${c.selection.ref} — nothing reviewed` };
|
|
66
|
+
}
|
|
58
67
|
return { section: 'Source', body: c.text };
|
|
59
68
|
}
|
|
60
69
|
const collected = collectSource(root, check.path, MAX_SOURCE_CHARS, kind);
|
|
@@ -69,10 +78,17 @@ function choosePack(check, root) {
|
|
|
69
78
|
const file = path.resolve(root, check.path);
|
|
70
79
|
return fs.statSync(file).isDirectory() ? null : detectLang(fs.readFileSync(file, 'utf-8'));
|
|
71
80
|
}
|
|
72
|
-
|
|
81
|
+
function withInstructions(instructions, message) {
|
|
82
|
+
return `${instructions.trim()}\n\n---\n\n${message}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One stage: the custom command gets instructions + message on stdin in the project; a client
|
|
86
|
+
* driver gets the instructions as its system prompt (Claude) or leading the message (Codex), in
|
|
87
|
+
* the neutral directory.
|
|
88
|
+
*/
|
|
73
89
|
async function askStage(choice, instructions, message, root, timeoutMs, onOutput) {
|
|
74
90
|
if (choice.client === 'custom') {
|
|
75
|
-
const r = await spawnReader(choice.cmd, null,
|
|
91
|
+
const r = await spawnReader(choice.cmd, null, withInstructions(instructions, message), root, timeoutMs, onOutput);
|
|
76
92
|
return { raw: r.raw, failureCode: r.failureCode, reply: r.out, usage: null, costUsd: null, model: null, exit: r.exit, killed: r.killed };
|
|
77
93
|
}
|
|
78
94
|
const cwd = readerHome();
|
|
@@ -80,69 +96,88 @@ async function askStage(choice, instructions, message, root, timeoutMs, onOutput
|
|
|
80
96
|
const claude = choice.client === 'claude';
|
|
81
97
|
const driver = choice.driver ?? REVIEWER_DRIVER;
|
|
82
98
|
const args = claude ? claudeArgs(instructions.trim(), null, driver) : codexArgs(null, driver);
|
|
83
|
-
const r = await spawnReader(choice.cmd, args, claude ? message :
|
|
99
|
+
const r = await spawnReader(choice.cmd, args, claude ? message : withInstructions(instructions, message), cwd, timeoutMs, onOutput);
|
|
84
100
|
const parsed = claude ? parseClaude(r.out) : parseCodex(r.out);
|
|
85
101
|
return { ...parsed, raw: r.raw, failureCode: r.failureCode, exit: r.exit, killed: r.killed };
|
|
86
102
|
}
|
|
87
103
|
function usageLine(u) {
|
|
88
|
-
|
|
104
|
+
if (!u)
|
|
105
|
+
return '';
|
|
106
|
+
const n = (value) => value.toLocaleString(NUMBER_LOCALE);
|
|
107
|
+
return ` · in ${n(u.input)} · cache read ${n(u.cacheRead)} · out ${n(u.output)}`;
|
|
89
108
|
}
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
* verdict. A stage passes only when the whole trimmed reply is exactly `PASS`; a REJECT list, a
|
|
93
|
-
* remark after PASS, an empty or killed reply all fail, with the reply kept in `tail`. Nothing is
|
|
94
|
-
* edited here: the reviewers judge, the writer fixes.
|
|
95
|
-
*/
|
|
96
|
-
export async function reviewCheck(check, root) {
|
|
97
|
-
const started = Date.now();
|
|
98
|
-
const streams = outputCapture();
|
|
99
|
-
const fail = (reason, text = '', complete = true) => ({ ...streams.finish(complete), pass: false, exit: 1, ms: Date.now() - started, tail: tail(text), reason, failureCode: 'review-failed' });
|
|
109
|
+
/** The prepared review, or the reason it cannot run. */
|
|
110
|
+
function prepare(check, root) {
|
|
100
111
|
if (!fs.existsSync(path.resolve(root, check.path)))
|
|
101
|
-
return
|
|
112
|
+
return `artifact missing: ${check.path}`;
|
|
102
113
|
if (check.mockup && !fs.existsSync(path.resolve(root, check.mockup)))
|
|
103
|
-
return
|
|
114
|
+
return `confirmed mockup missing: ${check.mockup}`;
|
|
104
115
|
const pack = choosePack(check, root);
|
|
105
116
|
if (!pack)
|
|
106
|
-
return
|
|
117
|
+
return 'language unknown — set lang: ko|en or pack: <name> on the check';
|
|
107
118
|
const stages = packStages(pack);
|
|
108
119
|
if (stages.length === 0)
|
|
109
|
-
return
|
|
120
|
+
return `no reviewers/${pack} in this package`;
|
|
110
121
|
const choice = chooseReviewer(root);
|
|
111
122
|
if (!choice)
|
|
112
|
-
return
|
|
113
|
-
let piece;
|
|
123
|
+
return `no reviewer available — needs \`claude\` or \`codex\` on PATH, or ${REVIEW_CMD_ENV}`;
|
|
114
124
|
try {
|
|
115
|
-
piece
|
|
125
|
+
return { pack, stages, choice, piece: artifact(check, root, pack) };
|
|
116
126
|
}
|
|
117
127
|
catch (error) {
|
|
118
|
-
return
|
|
128
|
+
return error.message;
|
|
119
129
|
}
|
|
130
|
+
}
|
|
131
|
+
/** The screenshot and confirmed-mockup sections: files the reviewer opens with its own reader. */
|
|
132
|
+
function imageSections(check, root) {
|
|
133
|
+
const shot = check.screenshot ? `\n\n## Screenshot\n\n${path.resolve(root, check.screenshot)} — open this image with your file reader before judging.` : '';
|
|
134
|
+
const mockup = check.mockup
|
|
135
|
+
? `\n\n## Confirmed mockup\n\n${path.resolve(root, check.mockup)} — ${MOCKUP_INSTRUCTION}`
|
|
136
|
+
: '';
|
|
137
|
+
return shot + mockup;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* `review` check — the harness itself runs the pack's reviewer stages in order and reads their
|
|
141
|
+
* verdict. A stage passes only when the whole trimmed reply is exactly `PASS`; a REJECT list, a
|
|
142
|
+
* remark after PASS, an empty or killed reply all fail, with the reply kept in `tail`. Nothing is
|
|
143
|
+
* edited here: the reviewers judge, the writer fixes.
|
|
144
|
+
*/
|
|
145
|
+
export async function reviewCheck(check, root) {
|
|
146
|
+
const started = Date.now();
|
|
147
|
+
const streams = outputCapture();
|
|
148
|
+
const fail = (reason, text = '', complete = true) => {
|
|
149
|
+
return { ...streams.finish(complete), pass: false, exit: 1, ms: Date.now() - started, tail: tail(text), reason, failureCode: 'review-failed' };
|
|
150
|
+
};
|
|
151
|
+
const prepared = prepare(check, root);
|
|
152
|
+
if (typeof prepared === 'string')
|
|
153
|
+
return fail(prepared);
|
|
154
|
+
const { pack, stages, choice, piece } = prepared;
|
|
120
155
|
if (piece.nothing)
|
|
121
156
|
return { pass: true, exit: 0, ms: Date.now() - started, tail: piece.nothing };
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const shot = check.screenshot ? `\n\n## Screenshot\n\n${path.resolve(root, check.screenshot)} — open this image with your file reader before judging.` : '';
|
|
125
|
-
const mockup = check.mockup ? `\n\n## Confirmed mockup\n\n${path.resolve(root, check.mockup)} — the person confirmed this mockup before the work was built; open it with your file reader and judge the result against it.` : '';
|
|
157
|
+
const body = `## ${piece.section}\n\n${piece.body}`;
|
|
158
|
+
const text = message(readOptional(root, check.contract), readOptional(root, check.evidence), body, imageSections(check, root));
|
|
126
159
|
const timeoutMs = check.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
127
160
|
const lines = [];
|
|
128
161
|
const usage = [];
|
|
129
162
|
for (const stage of stages) {
|
|
130
|
-
const
|
|
131
|
-
const r = await askStage(choice, instructions, message(contract, evidence, `## ${piece.section}\n\n${piece.body}`, shot + mockup), root, timeoutMs, streams.add);
|
|
163
|
+
const r = await askStage(choice, fs.readFileSync(stage.file, 'utf-8'), text, root, timeoutMs, streams.add);
|
|
132
164
|
if (r.usage)
|
|
133
165
|
usage.push({ stage: stage.name, ...r.usage });
|
|
134
|
-
const chosen = choice.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
166
|
+
const chosen = choice.client === 'claude' ? choice.driver?.model : choice.driver?.codexModel;
|
|
167
|
+
const detail = `review ${pack}/${stage.name}`;
|
|
168
|
+
recordUsage(root, { detail, client: choice.client, model: r.model ?? chosen ?? null, tokens: r.usage, costUsd: r.costUsd, ms: Date.now() - started });
|
|
169
|
+
if (r.failureCode || r.exit !== 0) {
|
|
170
|
+
const failureCode = r.failureCode ?? 'reviewer-exit';
|
|
171
|
+
return { ...fail(failureCode, '', r.failureCode === null), failureCode, usage };
|
|
172
|
+
}
|
|
138
173
|
if (r.killed)
|
|
139
174
|
return fail(`${stage.name}: killed after ${timeoutMs}ms`, lines.join('\n'), false);
|
|
140
175
|
const verdict = r.reply.trim();
|
|
141
|
-
if (verdict ===
|
|
142
|
-
lines.push(`${pack} ${stage.name}:
|
|
176
|
+
if (verdict === PASS_VERDICT) {
|
|
177
|
+
lines.push(`${pack} ${stage.name}: ${PASS_VERDICT}${usageLine(r.usage)}`);
|
|
143
178
|
continue;
|
|
144
179
|
}
|
|
145
|
-
lines.push(`${pack} ${stage.name}: ${verdict === '' ? `no reply (exit ${r.exit})` :
|
|
180
|
+
lines.push(`${pack} ${stage.name}: ${verdict === '' ? `no reply (exit ${r.exit})` : `not ${PASS_VERDICT}`}${usageLine(r.usage)}`, verdict);
|
|
146
181
|
return { ...fail(`${stage.name} did not pass`, lines.join('\n')), usage };
|
|
147
182
|
}
|
|
148
183
|
return { ...streams.finish(true), pass: true, exit: 0, ms: Date.now() - started, tail: lines.join('\n'), usage };
|