diff --git a/.github/workflows/repo-analysis.yml b/.github/workflows/repo-analysis.yml
index ed3fc135..8520c9e5 100644
--- a/.github/workflows/repo-analysis.yml
+++ b/.github/workflows/repo-analysis.yml
@@ -29,13 +29,15 @@ jobs:
ref: ${{ inputs.ref }}
fetch-depth: 0
- # install.sh resolves the latest published release, for the same reason
- # PR Review does — a pinned version names an unpublished release for the
- # whole of a release-bump PR. See .github/workflows/review.yml.
- - name: Install xmd release binary
- run: |
- curl -fsSL https://executable.md/install.sh | sh
- echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ - uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
+ with:
+ deno-version: v2.9.5
+
+ - name: Install dependencies
+ run: deno task deps
+
+ - name: Build the checked-out xmd binary
+ run: deno task build
- name: Run repo analysis
env:
@@ -43,19 +45,12 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
- xmd run .reviews/AnalyzeRepoCI.md \
+ ./dist/xmd run .reviews/AnalyzeRepoCI.md \
--component-dir .reviews/components \
--component-dir .reviews/policies \
--component-dir packages/core/components \
-j .reviews/journal.analyze.ci.jsonl > .reviews/analyze-report.md
- # xmd embeds a failed EnsureOxlint as a comment and still exits 0, so
- # assert the lint binaries were provisioned — fail the job if not.
- - name: Verify oxlint provisioning
- run: |
- test -x .reviews/.oxlint/oxlint && test -x .reviews/.oxlint/tsgolint \
- || { echo "::error::oxlint/tsgolint could not be provisioned by EnsureOxlint"; exit 1; }
-
- name: Write run metadata
if: always()
run: |
diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml
index 57ab97cb..3abbda17 100644
--- a/.github/workflows/review.yml
+++ b/.github/workflows/review.yml
@@ -15,14 +15,15 @@ jobs:
with:
fetch-depth: 0
- # install.sh resolves the latest published release. Pinning a version here
- # broke every release-bump PR, which pinned the release it was preparing
- # and downloaded a binary that did not exist yet; the tradeoff is that a
- # new release changes PR-review behavior as soon as it is the latest.
- - name: Install xmd release binary
- run: |
- curl -fsSL https://executable.md/install.sh | sh
- echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ - uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
+ with:
+ deno-version: v2.9.5
+
+ - name: Install dependencies
+ run: deno task deps
+
+ - name: Build the checked-out xmd binary
+ run: deno task build
- name: Run review
env:
@@ -34,20 +35,13 @@ jobs:
GITHUB_REPOSITORY: ${{ github.repository }}
DEEPINFRA_TOKEN: ${{ secrets.DEEPINFRA_TOKEN }}
run: |
- xmd run .reviews/ReviewPR.md \
+ ./dist/xmd run .reviews/ReviewPR.md \
--component-dir .reviews/components \
--component-dir .reviews/policies \
--component-dir packages/core/components \
-j .reviews/journal.jsonl \
--verbose
- # xmd embeds a failed EnsureOxlint as a comment and still exits 0, so
- # assert the lint binaries were provisioned — fail the job if not.
- - name: Verify oxlint provisioning
- run: |
- test -x .reviews/.oxlint/oxlint && test -x .reviews/.oxlint/tsgolint \
- || { echo "::error::oxlint/tsgolint could not be provisioned by EnsureOxlint"; exit 1; }
-
- name: Upload journal
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
diff --git a/.reviews/AnalyzeRepo.md b/.reviews/AnalyzeRepo.md
index 0de780be..ed8e7ced 100644
--- a/.reviews/AnalyzeRepo.md
+++ b/.reviews/AnalyzeRepo.md
@@ -83,10 +83,33 @@ const doctor = parseDoctorResult(doctorJson);
```bash exec
OUT=$(OXLINT_TSGOLINT_PATH=.reviews/.oxlint/tsgolint .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --type-aware --tsconfig .reviews/tsconfig.oxlint.json --format json 2>/dev/null || true)
-if [ -n "$OUT" ]; then
- printf '%s' "$OUT"
-else
- echo "[]"
+if [ -z "$OUT" ] || ! printf '%s' "$OUT" | jq -c '
+ def entries:
+ if type == "array" then .
+ elif (.diagnostics? | type) == "array" then .diagnostics
+ else []
+ end;
+ def span_line:
+ if (.line? | type) == "number" then .line
+ elif (.labels?[0].span.line? | type) == "number" then .labels[0].span.line
+ else 0
+ end;
+ def span_column:
+ if (.column? | type) == "number" then .column
+ elif (.labels?[0].span.column? | type) == "number" then .labels[0].span.column
+ else 0
+ end;
+ entries
+ | map({
+ message: (if (.message? | type) == "string" then .message else "" end),
+ ruleId: (if (.ruleId? | type) == "string" then .ruleId elif (.code? | type) == "string" then .code else "unknown" end),
+ severity: (if .severity == "error" then "error" else "warning" end),
+ file: (if (.file? | type) == "string" then .file elif (.filename? | type) == "string" then .filename else "" end),
+ line: span_line,
+ column: span_column
+ })
+'; then
+ printf '[]'
fi
```
@@ -97,10 +120,33 @@ fi
```bash exec
OUT=$(.reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --format json 2>/dev/null || true)
-if [ -n "$OUT" ]; then
- printf '%s' "$OUT"
-else
- echo "[]"
+if [ -z "$OUT" ] || ! printf '%s' "$OUT" | jq -c '
+ def entries:
+ if type == "array" then .
+ elif (.diagnostics? | type) == "array" then .diagnostics
+ else []
+ end;
+ def span_line:
+ if (.line? | type) == "number" then .line
+ elif (.labels?[0].span.line? | type) == "number" then .labels[0].span.line
+ else 0
+ end;
+ def span_column:
+ if (.column? | type) == "number" then .column
+ elif (.labels?[0].span.column? | type) == "number" then .labels[0].span.column
+ else 0
+ end;
+ entries
+ | map({
+ message: (if (.message? | type) == "string" then .message else "" end),
+ ruleId: (if (.ruleId? | type) == "string" then .ruleId elif (.code? | type) == "string" then .code else "unknown" end),
+ severity: (if .severity == "error" then "error" else "warning" end),
+ file: (if (.file? | type) == "string" then .file elif (.filename? | type) == "string" then .filename else "" end),
+ line: span_line,
+ column: span_column
+ })
+'; then
+ printf '[]'
fi
```
diff --git a/.reviews/AnalyzeRepoCI.md b/.reviews/AnalyzeRepoCI.md
index be8f9730..90539595 100644
--- a/.reviews/AnalyzeRepoCI.md
+++ b/.reviews/AnalyzeRepoCI.md
@@ -2,6 +2,8 @@
title: Repository Analysis (CI)
---
+
diff --git a/.reviews/ReviewPR.local.md b/.reviews/ReviewPR.local.md
index 843c0fcc..c852eab9 100644
--- a/.reviews/ReviewPR.local.md
+++ b/.reviews/ReviewPR.local.md
@@ -94,10 +94,43 @@ git diff --name-only {BASE_SHA}...{HEAD_SHA} -- '*.ts' '*.tsx' | grep -v '\.test
|| doctor.recommendation === "type-aware-filtered"}>
```bash exec
-if [ -n "{changedTsFiles}" ]; then
- echo "{changedTsFiles}" | tr '\n' ' ' | OXLINT_TSGOLINT_PATH=.reviews/.oxlint/tsgolint xargs .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --type-aware --tsconfig .reviews/tsconfig.oxlint.json --format json 2>/dev/null || true
+changed_files=$(cat <<'FILES'
+{changedTsFiles}
+FILES
+)
+if [ -z "$changed_files" ]; then
+ printf '[]'
else
- echo "[]"
+ raw=$(printf '%s\n' "$changed_files" | OXLINT_TSGOLINT_PATH=.reviews/.oxlint/tsgolint xargs .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --type-aware --tsconfig .reviews/tsconfig.oxlint.json --format json 2>/dev/null || true)
+ if [ -z "$raw" ] || ! printf '%s' "$raw" | jq -c --arg changed "$changed_files" '
+ def entries:
+ if type == "array" then .
+ elif (.diagnostics? | type) == "array" then .diagnostics
+ else []
+ end;
+ def span_line:
+ if (.line? | type) == "number" then .line
+ elif (.labels?[0].span.line? | type) == "number" then .labels[0].span.line
+ else 0
+ end;
+ def span_column:
+ if (.column? | type) == "number" then .column
+ elif (.labels?[0].span.column? | type) == "number" then .labels[0].span.column
+ else 0
+ end;
+ entries
+ | map({
+ message: (if (.message? | type) == "string" then .message else "" end),
+ ruleId: (if (.ruleId? | type) == "string" then .ruleId elif (.code? | type) == "string" then .code else "unknown" end),
+ severity: (if .severity == "error" then "error" else "warning" end),
+ file: (if (.file? | type) == "string" then .file elif (.filename? | type) == "string" then .filename else "" end),
+ line: span_line,
+ column: span_column
+ })
+ | map(select(.file as $file | ($changed | split("\n") | index($file)) != null))
+ '; then
+ printf '[]'
+ fi
fi
```
@@ -107,10 +140,43 @@ fi
&& doctor.oxlintInstalled}>
```bash exec
-if [ -n "{changedTsFiles}" ]; then
- echo "{changedTsFiles}" | tr '\n' ' ' | xargs .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --format json 2>/dev/null || true
+changed_files=$(cat <<'FILES'
+{changedTsFiles}
+FILES
+)
+if [ -z "$changed_files" ]; then
+ printf '[]'
else
- echo "[]"
+ raw=$(printf '%s\n' "$changed_files" | xargs .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --format json 2>/dev/null || true)
+ if [ -z "$raw" ] || ! printf '%s' "$raw" | jq -c --arg changed "$changed_files" '
+ def entries:
+ if type == "array" then .
+ elif (.diagnostics? | type) == "array" then .diagnostics
+ else []
+ end;
+ def span_line:
+ if (.line? | type) == "number" then .line
+ elif (.labels?[0].span.line? | type) == "number" then .labels[0].span.line
+ else 0
+ end;
+ def span_column:
+ if (.column? | type) == "number" then .column
+ elif (.labels?[0].span.column? | type) == "number" then .labels[0].span.column
+ else 0
+ end;
+ entries
+ | map({
+ message: (if (.message? | type) == "string" then .message else "" end),
+ ruleId: (if (.ruleId? | type) == "string" then .ruleId elif (.code? | type) == "string" then .code else "unknown" end),
+ severity: (if .severity == "error" then "error" else "warning" end),
+ file: (if (.file? | type) == "string" then .file elif (.filename? | type) == "string" then .filename else "" end),
+ line: span_line,
+ column: span_column
+ })
+ | map(select(.file as $file | ($changed | split("\n") | index($file)) != null))
+ '; then
+ printf '[]'
+ fi
fi
```
diff --git a/.reviews/ReviewPR.md b/.reviews/ReviewPR.md
index 09741a0b..ecea60f1 100644
--- a/.reviews/ReviewPR.md
+++ b/.reviews/ReviewPR.md
@@ -2,6 +2,8 @@
title: PR Review
---
+
diff --git a/.reviews/components/AbstractionNames.md b/.reviews/components/AbstractionNames.md
index 338ef8e2..3572a83c 100644
--- a/.reviews/components/AbstractionNames.md
+++ b/.reviews/components/AbstractionNames.md
@@ -18,14 +18,14 @@ props:
---
```ts eval
-const re = new RegExp(pattern, "i");
-const suspicious = pr.created
+const re = new RegExp(props.pattern, "i");
+const suspicious = props.pr.created
.filter(f => f.path.endsWith(".ts") && !f.isTest && !f.isTypeDeclaration)
.filter(f => re.test(f.path));
const triggered = suspicious.length > 0;
-const resolvedMessage = message.replace(
+const resolvedMessage = props.message.replace(
"{names}", suspicious.map(f => f.path).join(", ")
);
```
-
+
diff --git a/.reviews/components/CleanupIssues.md b/.reviews/components/CleanupIssues.md
index 7f711724..110458b9 100644
--- a/.reviews/components/CleanupIssues.md
+++ b/.reviews/components/CleanupIssues.md
@@ -11,30 +11,31 @@ props:
---
```ts persist eval
-const token = process.env.GITHUB_TOKEN;
const repo = process.env.GITHUB_REPOSITORY;
-if (!token || !repo) {
+function githubHeaders() {
+ return {
+ "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
+ "Accept": "application/vnd.github+json",
+ "Content-Type": "application/json",
+ };
+}
+
+if (!process.env.GITHUB_TOKEN || !repo) {
return "";
}
const [owner, repoName] = repo.split("/");
const api = `https://api.github.com/repos/${owner}/${repoName}`;
-const headers = {
- "Authorization": `Bearer ${token}`,
- "Accept": "application/vnd.github+json",
- "Content-Type": "application/json",
-};
-
const LABEL = "cleanup";
const TOP_N = 5;
// 1. Ensure label exists
-const labelResponse = yield* fetch(`${api}/labels/${LABEL}`, { headers });
+const labelResponse = yield* fetch(`${api}/labels/${LABEL}`, { headers: githubHeaders() });
if (labelResponse.status === 404) {
yield* fetch(`${api}/labels`, {
method: "POST",
- headers,
+ headers: githubHeaders(),
body: JSON.stringify({
name: LABEL,
description: "Auto-generated cleanup finding from repo analysis",
@@ -49,7 +50,7 @@ let page = 1;
while (true) {
const batch = yield* fetch(
`${api}/issues?labels=${LABEL}&state=open&per_page=100&page=${page}`,
- { headers },
+ { headers: githubHeaders() },
).expect().json();
if (!Array.isArray(batch) || batch.length === 0) break;
@@ -69,7 +70,7 @@ for (const issue of existingIssues) {
}
// 4. Process top 5 clusters
-const topClusters = cleanupAnalysis.fileClusters.slice(0, TOP_N);
+const topClusters = props.cleanupAnalysis.fileClusters.slice(0, TOP_N);
const topFiles = new Set(topClusters.map(c => c.file));
let created = 0;
@@ -108,14 +109,14 @@ for (const cluster of topClusters) {
if (existing) {
yield* fetch(api + "/issues/" + existing.number, {
method: "PATCH",
- headers,
+ headers: githubHeaders(),
body: JSON.stringify({ title, body }),
}).expect();
updated++;
} else {
yield* fetch(api + "/issues", {
method: "POST",
- headers,
+ headers: githubHeaders(),
body: JSON.stringify({
title,
body,
@@ -131,7 +132,7 @@ for (const [file, issue] of issuesByFile.entries()) {
if (!topFiles.has(file)) {
yield* fetch(api + "/issues/" + issue.number + "/comments", {
method: "POST",
- headers,
+ headers: githubHeaders(),
body: JSON.stringify({
body: "Resolved — file no longer in top-5 cleanup targets. Closing automatically.",
}),
@@ -139,7 +140,7 @@ for (const [file, issue] of issuesByFile.entries()) {
yield* fetch(api + "/issues/" + issue.number, {
method: "PATCH",
- headers,
+ headers: githubHeaders(),
body: JSON.stringify({ state: "closed" }),
}).expect();
closed++;
diff --git a/.reviews/components/CommentReview.md b/.reviews/components/CommentReview.md
index e3b95ef2..944b2eaf 100644
--- a/.reviews/components/CommentReview.md
+++ b/.reviews/components/CommentReview.md
@@ -12,7 +12,7 @@ props:
// ---------------------------------------------------------------------------
// 1. Build comment/code pairs with file/line metadata
const pairs = [];
-const lines = pr.added.filter(l => !l.isTest);
+const lines = props.pr.added.filter(l => !l.isTest);
for (let i = 0; i < lines.length - 1; i++) {
const current = lines[i].content.trim();
@@ -39,24 +39,26 @@ let checklistMd = "";
// ---------------------------------------------------------------------------
// 2. Fetch previous bot review comments and human replies
-const token = process.env.GITHUB_TOKEN;
const repo = process.env.GITHUB_REPOSITORY;
const prNumber = process.env.PR_NUMBER;
+function githubHeaders() {
+ return {
+ "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
+ "Accept": "application/vnd.github+json",
+ };
+}
+
let previousFindings = [];
let dismissedReplies = [];
let repliesForClassification = [];
-if (token && repo && prNumber) {
+if (process.env.GITHUB_TOKEN && repo && prNumber) {
const [owner, name] = repo.split("/");
const api = `https://api.github.com/repos/${owner}/${name}`;
- const headers = {
- "Authorization": `Bearer ${token}`,
- "Accept": "application/vnd.github+json",
- };
const allComments = yield* fetch(
- `${api}/pulls/${prNumber}/comments?per_page=100`, { headers }
+ `${api}/pulls/${prNumber}/comments?per_page=100`, { headers: githubHeaders() }
).expect().json();
const botComments = allComments.filter(c =>
@@ -94,7 +96,7 @@ if (token && repo && prNumber) {
};
try {
const reactions = yield* fetch(
- `${api}/pulls/comments/${reply.id}/reactions`, { headers }
+ `${api}/pulls/comments/${reply.id}/reactions`, { headers: githubHeaders() }
).expect().json();
const alreadyAcked = reactions.some(r =>
r.user.login === "github-actions[bot]" && r.content === "+1"
@@ -168,7 +170,7 @@ const dismissedSet = new Set(
);
const addedLineSet = new Set(
- pr.added.map(l => `${l.file}:${l.lineNumber}`)
+ props.pr.added.map(l => `${l.file}:${l.lineNumber}`)
);
const appliedFindings = previousFindings.filter(pf =>
pf.lineNumber && !addedLineSet.has(`${pf.file}:${pf.lineNumber}`) &&
diff --git a/.reviews/components/ConfigSourceMix.md b/.reviews/components/ConfigSourceMix.md
index 42973ec0..2dd9f6cb 100644
--- a/.reviews/components/ConfigSourceMix.md
+++ b/.reviews/components/ConfigSourceMix.md
@@ -18,11 +18,11 @@ props:
---
```ts eval
-const hasConfig = pr.files.some(f => f.isConfig);
-const hasSource = pr.files.some(f =>
+const hasConfig = props.pr.files.some(f => f.isConfig);
+const hasSource = props.pr.files.some(f =>
!f.isConfig && !f.isTest && !f.isTypeDeclaration
);
-const triggered = hasConfig && hasSource && pr.stats.totalFiles > minFiles;
+const triggered = hasConfig && hasSource && props.pr.stats.totalFiles > props.minFiles;
```
-
+
diff --git a/.reviews/components/DeepInfraProvider.md b/.reviews/components/DeepInfraProvider.md
index 241bbeb2..b0b474a7 100644
--- a/.reviews/components/DeepInfraProvider.md
+++ b/.reviews/components/DeepInfraProvider.md
@@ -11,7 +11,7 @@ props:
```ts persist eval
yield* Sample.around({
*sample([context], next) {
- if (context.model !== undefined && context.model !== model) {
+ if (context.model !== undefined && context.model !== props.model) {
return yield* next(context);
}
@@ -27,7 +27,7 @@ yield* Sample.around({
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.DEEPINFRA_TOKEN}`,
},
- body: JSON.stringify({ model, messages, temperature: 0, max_tokens: 4096 }),
+ body: JSON.stringify({ model: props.model, messages, temperature: 0, max_tokens: 4096 }),
})
.expect()
.json();
diff --git a/.reviews/components/DescriptionCheck.md b/.reviews/components/DescriptionCheck.md
index b650de45..4b86a54f 100644
--- a/.reviews/components/DescriptionCheck.md
+++ b/.reviews/components/DescriptionCheck.md
@@ -17,5 +17,5 @@ props:
additionalProperties: false
---
-
+
diff --git a/.reviews/components/Doctor.md b/.reviews/components/Doctor.md
index d5701238..b9fb0605 100644
--- a/.reviews/components/Doctor.md
+++ b/.reviews/components/Doctor.md
@@ -42,7 +42,7 @@ test -x .reviews/.oxlint/oxlint && echo "EXISTS" || echo "MISSING"
```bash exec
-test -f {tsconfigPath} && echo "EXISTS" || echo "MISSING"
+test -f {props.tsconfigPath} && echo "EXISTS" || echo "MISSING"
```
@@ -87,13 +87,71 @@ Running type-aware probe to test Oxlint compatibility...
+ fallback='{"diagnosticCount":0,"importNoiseCount":0,"filesAnalyzed":0,"filesSkipped":0,"importErrors":0,"availableRuleIds":[],"tsgolintCrashed":false}'>
```bash exec
-RESULT=$(OXLINT_TSGOLINT_PATH=.reviews/.oxlint/tsgolint .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --type-aware --tsconfig {tsconfigPath} --format json 2>.reviews/probe-stderr.tmp || true)
+RESULT=$(OXLINT_TSGOLINT_PATH=.reviews/.oxlint/tsgolint .reviews/.oxlint/oxlint --config .reviews/.oxlintrc.json --type-aware --tsconfig {props.tsconfigPath} --format json 2>.reviews/probe-stderr.tmp || true)
STDERR=$(cat .reviews/probe-stderr.tmp 2>/dev/null || echo "")
rm -f .reviews/probe-stderr.tmp
-echo "{\"diagnostics\":$RESULT,\"stderr\":\"$STDERR\"}"
+if [ -n "$RESULT" ] && printf '%s' "$RESULT" | jq -c --arg stderr "$STDERR" '
+ def entries:
+ if type == "array" then .
+ elif (.diagnostics? | type) == "array" then .diagnostics
+ else []
+ end;
+ def file:
+ if (.file? | type) == "string" then .file
+ elif (.filename? | type) == "string" then .filename
+ else ""
+ end;
+ def rule:
+ if (.ruleId? | type) == "string" then .ruleId
+ elif (.code? | type) == "string" then .code
+ else "unknown"
+ end;
+ def message:
+ if (.message? | type) == "string" then .message else "" end;
+ def import_noise:
+ ((.message | ascii_downcase | contains("cannot find module"))
+ or (.ruleId | ascii_downcase | contains("import")));
+ def crashed($value):
+ ($value | ascii_downcase) as $lower
+ | ($lower | contains("tsgolint"))
+ and (($lower | contains("panic"))
+ or ($lower | contains("oom"))
+ or ($lower | contains("fatal")));
+ entries
+ | map({file: file, ruleId: rule, message: message}) as $diagnostics
+ | {
+ diagnosticCount: ($diagnostics | length),
+ importNoiseCount: ([$diagnostics[] | select(import_noise)] | length),
+ filesAnalyzed: ([$diagnostics[].file | select(length > 0)] | unique | length),
+ filesSkipped: ([$diagnostics[] | select(import_noise) | .file | select(length > 0)] | unique | length),
+ importErrors: ([$diagnostics[] | select(import_noise)] | length),
+ availableRuleIds: ([$diagnostics[].ruleId | select(length > 0)] | unique),
+ tsgolintCrashed: crashed($stderr)
+ }
+'; then
+ :
+else
+ jq -cn --arg stderr "$STDERR" '
+ def crashed($value):
+ ($value | ascii_downcase) as $lower
+ | ($lower | contains("tsgolint"))
+ and (($lower | contains("panic"))
+ or ($lower | contains("oom"))
+ or ($lower | contains("fatal")));
+ {
+ diagnosticCount: 0,
+ importNoiseCount: 0,
+ filesAnalyzed: 0,
+ filesSkipped: 0,
+ importErrors: 0,
+ availableRuleIds: [],
+ tsgolintCrashed: crashed($stderr)
+ }
+ '
+fi
```
@@ -117,31 +175,25 @@ const TYPE_AWARE_RULES = [
"no-unnecessary-boolean-literal-compare",
];
-let probe = { diagnostics: [], stderr: "" };
-try { probe = JSON.parse(probeResult); } catch { /* malformed */ }
-
-const diagnostics = Array.isArray(probe.diagnostics)
- ? probe.diagnostics
- : (probe.diagnostics && typeof probe.diagnostics === "object"
- && Array.isArray(probe.diagnostics.diagnostics))
- ? probe.diagnostics.diagnostics
- : [];
-
-const importNoise = diagnostics.filter(d =>
- d.message?.includes("Cannot find module")
- || d.message?.includes("cannot find")
- || d.ruleId?.includes("import")
-);
+let probe = {
+ diagnosticCount: 0,
+ importNoiseCount: 0,
+ filesAnalyzed: 0,
+ filesSkipped: 0,
+ importErrors: 0,
+ availableRuleIds: [],
+ tsgolintCrashed: false,
+};
+try { probe = { ...probe, ...JSON.parse(probeResult) }; } catch { }
-const fileSet = new Set(diagnostics.map(d => d.file).filter(Boolean));
-const noiseRatio = diagnostics.length > 0
- ? importNoise.length / diagnostics.length : 0;
+const diagnosticCount = typeof probe.diagnosticCount === "number"
+ ? probe.diagnosticCount : 0;
+const importNoiseCount = typeof probe.importNoiseCount === "number"
+ ? probe.importNoiseCount : 0;
+const noiseRatio = diagnosticCount > 0
+ ? importNoiseCount / diagnosticCount : 0;
-const tsgolintCrashed = typeof probe.stderr === "string"
- && probe.stderr.includes("tsgolint")
- && (probe.stderr.includes("panic")
- || probe.stderr.includes("OOM")
- || probe.stderr.includes("fatal"));
+const tsgolintCrashed = probe.tsgolintCrashed === true;
const typeAwareAvailable = canProbeTypeAware && !tsgolintCrashed;
@@ -167,9 +219,11 @@ const doctor = {
tsconfigExists,
nodeModulesExists,
typeAwareAvailable,
- filesAnalyzed: fileSet.size,
- filesSkipped: new Set(importNoise.map(d => d.file).filter(Boolean)).size,
- importErrors: importNoise.length,
+ filesAnalyzed: typeof probe.filesAnalyzed === "number" ? probe.filesAnalyzed : 0,
+ filesSkipped: typeof probe.filesSkipped === "number" ? probe.filesSkipped : 0,
+ importErrors: typeof probe.importErrors === "number" ? probe.importErrors : 0,
+ availableRuleIds: Array.isArray(probe.availableRuleIds)
+ ? probe.availableRuleIds : [],
bloatRulesAvailable,
bloatRulesMissing,
recommendation,
diff --git a/.reviews/components/EnsureOxlint.md b/.reviews/components/EnsureOxlint.md
index 59d8f403..09b67771 100644
--- a/.reviews/components/EnsureOxlint.md
+++ b/.reviews/components/EnsureOxlint.md
@@ -15,7 +15,7 @@ props:
# neither can change without the other. The workflow guards on the resulting
# binaries (see review.yml).
set -euo pipefail
-DIR="{dir}"
+DIR="{props.dir}"
OXLINT_TAG="apps_v1.74.0"
TSGOLINT_VERSION="0.25.0"
mkdir -p "$DIR"
diff --git a/.reviews/components/Finding.md b/.reviews/components/Finding.md
index af480db9..4d8fde17 100644
--- a/.reviews/components/Finding.md
+++ b/.reviews/components/Finding.md
@@ -14,11 +14,11 @@ props:
---
```ts eval
-const icon = severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
+const icon = props.severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
```
-
+
-{icon} {message}
+{icon} {props.message}
diff --git a/.reviews/components/GitHubComment.md b/.reviews/components/GitHubComment.md
index 0618b4a0..3d7ee32f 100644
--- a/.reviews/components/GitHubComment.md
+++ b/.reviews/components/GitHubComment.md
@@ -9,35 +9,36 @@ props:
---
```ts eval
-// GITHUB_TOKEN is read inline at each call site, never assigned to a binding:
-// eval bindings are journaled, and the journal is uploaded as a CI artifact.
const content = yield* renderChildren();
-const body = marker + "\n" + content.trim();
+const body = props.marker + "\n" + content.trim();
const repo = process.env.GITHUB_REPOSITORY;
const prNumber = process.env.PR_NUMBER;
const [owner, name] = repo.split("/");
const api = `https://api.github.com/repos/${owner}/${name}`;
-const commentsResult = yield* fetch(`${api}/issues/${prNumber}/comments`, {
- headers: {
+function githubHeaders() {
+ return {
"Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
"Accept": "application/vnd.github+json",
- },
+ };
+}
+
+const commentsResult = yield* fetch(`${api}/issues/${prNumber}/comments`, {
+ headers: githubHeaders(),
})
.expect()
.json();
const existing = commentsResult.find(c =>
- c.user.type === "Bot" && c.body.includes(marker)
+ c.user.type === "Bot" && c.body.includes(props.marker)
);
if (existing) {
yield* fetch(`${api}/issues/comments/${existing.id}`, {
method: "PATCH",
headers: {
- "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
- "Accept": "application/vnd.github+json",
+ ...githubHeaders(),
"Content-Type": "application/json",
},
body: JSON.stringify({ body }),
@@ -46,8 +47,7 @@ if (existing) {
yield* fetch(`${api}/issues/${prNumber}/comments`, {
method: "POST",
headers: {
- "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
- "Accept": "application/vnd.github+json",
+ ...githubHeaders(),
"Content-Type": "application/json",
},
body: JSON.stringify({ body }),
diff --git a/.reviews/components/LinkedIssue.md b/.reviews/components/LinkedIssue.md
index ae902f99..58424af5 100644
--- a/.reviews/components/LinkedIssue.md
+++ b/.reviews/components/LinkedIssue.md
@@ -18,8 +18,8 @@ props:
---
```ts eval
-const hasIssue = /(?:#\d+|https:\/\/github\.com\/.*\/issues\/\d+)/.test(pr.meta.body);
+const hasIssue = /(?:#\d+|https:\/\/github\.com\/.*\/issues\/\d+)/.test(props.pr.meta.body);
```
- whenLinesExceed}
- severity={severity} message={message} />
+ props.whenLinesExceed}
+ severity={props.severity} message={props.message} />
diff --git a/.reviews/components/NewDependencies.md b/.reviews/components/NewDependencies.md
index fa949fd3..ef40827e 100644
--- a/.reviews/components/NewDependencies.md
+++ b/.reviews/components/NewDependencies.md
@@ -15,11 +15,11 @@ props:
---
```ts eval
-const touchesPkg = pr.files.some(f =>
+const touchesPkg = props.pr.files.some(f =>
f.path === "package.json" || f.path.endsWith("/package.json")
);
-const mentionsDeps = pr.meta.body.toLowerCase().includes("dependenc");
+const mentionsDeps = props.pr.meta.body.toLowerCase().includes("dependenc");
const triggered = touchesPkg && !mentionsDeps;
```
-
+
diff --git a/.reviews/components/OllamaProvider.md b/.reviews/components/OllamaProvider.md
index 3c07a10f..69e24eab 100644
--- a/.reviews/components/OllamaProvider.md
+++ b/.reviews/components/OllamaProvider.md
@@ -14,7 +14,7 @@ props:
```ts persist eval
yield* Sample.around({
*sample([context], next) {
- if (context.model !== undefined && context.model !== model) {
+ if (context.model !== undefined && context.model !== props.model) {
return yield* next(context);
}
@@ -24,10 +24,10 @@ yield* Sample.around({
}
messages.push({ role: "user", content: context.content });
- const result = yield* fetch(`${baseUrl}/v1/chat/completions`, {
+ const result = yield* fetch(`${props.baseUrl}/v1/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ model, messages, temperature: 0 }),
+ body: JSON.stringify({ model: props.model, messages, temperature: 0 }),
})
.expect()
.json();
diff --git a/.reviews/components/OxlintSignals.md b/.reviews/components/OxlintSignals.md
index 186222c9..175d23a2 100644
--- a/.reviews/components/OxlintSignals.md
+++ b/.reviews/components/OxlintSignals.md
@@ -11,11 +11,11 @@ props:
---
```ts eval
-if (groups.length === 0) return;
+if (props.groups.length === 0) return;
-const lines = groups.map(g =>
+const lines = props.groups.map(g =>
`- \`${g.ruleId}\` ×${g.count}: ${g.files.slice(0, 3).join(", ")}${g.files.length > 3 ? ` (+${g.files.length - 3})` : ""}`
);
-return `**Oxlint ${label}:**\n${lines.join("\n")}`;
+return `**Oxlint ${props.label}:**\n${lines.join("\n")}`;
```
diff --git a/.reviews/components/OxlintSummary.md b/.reviews/components/OxlintSummary.md
index 937c4009..53f15491 100644
--- a/.reviews/components/OxlintSummary.md
+++ b/.reviews/components/OxlintSummary.md
@@ -13,22 +13,22 @@ props:
-
+
🟡 Oxlint not installed. Static analysis skipped.
- 0}>
+ 0}>
-{diagnostics.summary}
+{props.diagnostics.summary}
- 0
- && doctor.oxlintInstalled}>
+ 0
+ && props.doctor.oxlintInstalled}>
-*{doctor.bloatRulesMissing.length} type-aware rules unavailable
+*{props.doctor.bloatRulesMissing.length} type-aware rules unavailable
— install `oxlint-tsgolint` for full coverage.*
diff --git a/.reviews/components/Pattern.md b/.reviews/components/Pattern.md
index 8cd51dfb..9a57645a 100644
--- a/.reviews/components/Pattern.md
+++ b/.reviews/components/Pattern.md
@@ -22,16 +22,16 @@ props:
---
```ts eval
-const re = new RegExp(pattern, "g");
-const lines = excludeTests
- ? pr.added.filter(l => !l.isTest)
- : pr.added;
+const re = new RegExp(props.pattern, "g");
+const lines = props.excludeTests
+ ? props.pr.added.filter(l => !l.isTest)
+ : props.pr.added;
const matches = lines.filter(l => re.test(l.content));
re.lastIndex = 0;
-if (matches.length >= min) {
- const icon = severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
- return icon + " " + message
+if (matches.length >= props.min) {
+ const icon = props.severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
+ return icon + " " + props.message
.replace("{count}", String(matches.length));
}
```
diff --git a/.reviews/components/PrPolicyReport.md b/.reviews/components/PrPolicyReport.md
index 7ca0e588..d5ab9808 100644
--- a/.reviews/components/PrPolicyReport.md
+++ b/.reviews/components/PrPolicyReport.md
@@ -12,16 +12,16 @@ props:
additionalProperties: false
---
-## PR #{pr.meta.number}: {pr.meta.title}
+## PR #{props.pr.meta.number}: {props.pr.meta.title}
-**{pr.stats.totalFiles}** files, **+{pr.stats.additions}** / **-{pr.stats.deletions}**
+**{props.pr.stats.totalFiles}** files, **+{props.pr.stats.additions}** / **-{props.pr.stats.deletions}**
-
+
-
+
-
+
-
+
-
+
diff --git a/.reviews/components/Ratio.md b/.reviews/components/Ratio.md
index 7d97b0af..b9a2b037 100644
--- a/.reviews/components/Ratio.md
+++ b/.reviews/components/Ratio.md
@@ -26,20 +26,20 @@ props:
---
```ts eval
-const numRe = new RegExp(numerator, "g");
-const denRe = new RegExp(denominator, "g");
-const lines = excludeTests
- ? pr.added.filter(l => !l.isTest)
- : pr.added;
+const numRe = new RegExp(props.numerator, "g");
+const denRe = new RegExp(props.denominator, "g");
+const lines = props.excludeTests
+ ? props.pr.added.filter(l => !l.isTest)
+ : props.pr.added;
const source = lines.map(l => l.content).join("\n");
const numCount = (source.match(numRe) ?? []).length;
const denCount = (source.match(denRe) ?? []).length;
-if (denCount >= minDenominator && numCount / denCount > threshold) {
+if (denCount >= props.minDenominator && numCount / denCount > props.threshold) {
const ratio = (numCount / denCount * 100).toFixed(1);
- const icon = severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
- return icon + " " + message
+ const icon = props.severity === "error" ? "\ud83d\udd34" : "\ud83d\udfe1";
+ return icon + " " + props.message
.replace("{ratio}", ratio)
.replace("{numeratorCount}", String(numCount))
.replace("{denominatorCount}", String(denCount));
diff --git a/.reviews/components/ReleaseSpecWarning.md b/.reviews/components/ReleaseSpecWarning.md
index 4773e8ba..3f2257c1 100644
--- a/.reviews/components/ReleaseSpecWarning.md
+++ b/.reviews/components/ReleaseSpecWarning.md
@@ -29,7 +29,7 @@ list when a file is added or removed.
```ts eval
-const releaseChanged = files.filter((path) => releaseConfigFiles.includes(`- ${path}`));
+const releaseChanged = props.files.filter((path) => releaseConfigFiles.includes(`- ${path}`));
const changedList = releaseChanged.join(", ");
// TODO: evaluate whether the diff's changes are actually reflected in the
@@ -38,7 +38,7 @@ const changedList = releaseChanged.join(", ");