From 8f6197008f5817cb6d1c4c18c51c13cec1a5982c Mon Sep 17 00:00:00 2001
From: Taras Mankovski <74687+taras@users.noreply.github.com>
Date: Fri, 7 Aug 2026 08:26:20 -0400
Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=92=A5=20Namespace=20Markdown=20props?=
=?UTF-8?q?=20under=20the=20props=20binding?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.reviews/components/AbstractionNames.md | 8 +-
.reviews/components/CleanupIssues.md | 2 +-
.reviews/components/CommentReview.md | 4 +-
.reviews/components/ConfigSourceMix.md | 8 +-
.reviews/components/DeepInfraProvider.md | 4 +-
.reviews/components/DescriptionCheck.md | 4 +-
.reviews/components/Doctor.md | 4 +-
.reviews/components/EnsureOxlint.md | 2 +-
.reviews/components/Finding.md | 6 +-
.reviews/components/GitHubComment.md | 4 +-
.reviews/components/LinkedIssue.md | 6 +-
.reviews/components/NewDependencies.md | 6 +-
.reviews/components/OllamaProvider.md | 6 +-
.reviews/components/OxlintSignals.md | 6 +-
.reviews/components/OxlintSummary.md | 12 +-
.reviews/components/Pattern.md | 14 +-
.reviews/components/PrPolicyReport.md | 14 +-
.reviews/components/Ratio.md | 16 +-
.reviews/components/ReleaseSpecWarning.md | 4 +-
.reviews/components/RepoPolicyReport.md | 8 +-
.reviews/components/ReviewSection.md | 4 +-
.reviews/components/Show.md | 6 +-
.reviews/components/SuggestRemoval.md | 10 +-
.reviews/components/Threshold.md | 20 +-
.reviews/components/UnusedInDiff.md | 8 +-
.reviews/policies/BloatPolicy.md | 12 +-
.reviews/policies/ExtraneousCodePolicy.md | 12 +-
.reviews/policies/RepoCleanupPolicy.md | 8 +-
.reviews/policies/ScopePolicy.md | 18 +-
.reviews/policies/SlopPolicy.md | 6 +-
packages/core/components/AnthropicProvider.md | 4 +-
packages/core/components/Instruction.md | 2 +-
packages/core/components/OllamaProvider.md | 6 +-
packages/core/components/Sample.md | 6 +-
packages/core/src/eval-env.ts | 44 +++
packages/core/src/eval-interpolate.ts | 11 +-
packages/core/src/execute.ts | 3 +-
packages/core/src/expand.ts | 158 ++++++-----
packages/core/src/projection.ts | 5 +-
packages/core/tests/eval-interpolate.test.ts | 17 ++
.../core/tests/function-components.test.ts | 38 +++
packages/core/tests/loop.test.ts | 6 +-
packages/core/tests/named-slots.test.ts | 4 +-
packages/core/tests/props-binding.test.ts | 251 ++++++++++++++++++
packages/core/tests/root-props.test.ts | 6 +-
packages/core/tests/sample-component.test.ts | 8 +-
smoke-test/Guide/Summary.md | 2 +-
smoke-test/InnerStubProvider.md | 4 +-
smoke-test/StubProvider.md | 4 +-
smoke-test/TypedList.md | 2 +-
smoke-test/TypedRows.md | 2 +-
smoke-test/Verdict.md | 4 +-
specs/code-review-agent-spec.md | 166 ++++++------
specs/decisions.md | 11 +-
specs/executable-mdx-spec.md | 156 +++++++----
specs/oxlint-sensor-spec.md | 62 ++---
specs/root-document-props-spec.md | 45 +++-
57 files changed, 842 insertions(+), 427 deletions(-)
create mode 100644 packages/core/tests/props-binding.test.ts
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..e590917f 100644
--- a/.reviews/components/CleanupIssues.md
+++ b/.reviews/components/CleanupIssues.md
@@ -69,7 +69,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;
diff --git a/.reviews/components/CommentReview.md b/.reviews/components/CommentReview.md
index e3b95ef2..a9027dd6 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();
@@ -168,7 +168,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..df8dc93c 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"
```
@@ -90,7 +90,7 @@ Running type-aware probe to test Oxlint compatibility...
fallback='{"diagnostics":[],"stderr":""}'>
```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\"}"
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..e647b1aa 100644
--- a/.reviews/components/GitHubComment.md
+++ b/.reviews/components/GitHubComment.md
@@ -12,7 +12,7 @@ props:
// 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;
@@ -29,7 +29,7 @@ const commentsResult = yield* fetch(`${api}/issues/${prNumber}/comments`, {
.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) {
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(", ");