You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🟡 BASE_SHA fallback semantics wrong on push events — Line 193: github.event.pull_request.base.sha || github.event.before
When triggered by a push (not a PR), this falls back to github.event.before — the previous commit on the same branch. So a direct push to a feature branch benchmarks against its own parent, not against main. This produces misleading regression results.
Suggestion: Either guard this step with if: github.event_name == 'pull_request' (or 'pull_request_target'), or explicitly resolve the main branch SHA via git merge-base HEAD origin/main.
🟡 upload-artifact now fires on every event — Line 118: Changed github.ref == 'refs/heads/main' to always().
This uploads test artifacts on every PR, every push, every fork trigger. Could clutter CI and increase storage costs with no clear user need on non-main events.
Suggestion: Keep always() but scope it: if: always() && github.ref == 'refs/heads/main', or at minimum add if: always() && failure() to only upload on failure.
📁 benchmarks/bench_dsl_diagnostics.py
🔴 Unhandled crash: missing optional dependency — build_report lines with "dependencies": {name: importlib.metadata.version(name) for name in ("onnx", "numpy")} is constructed outside the try block. importlib.metadata.version raises PackageNotFoundError when onnx/numpy isn't installed (e.g., a fresh worker image that only needs the parser), so the script dies with a raw traceback and produces no report at all. Move environment/dependency collection inside the try, or wrap each lookup in a per-package fallback (None).
🔴 ZeroDivisionError in compare_parsing — ratio = current["median_s"] / baseline["median_s"]. A tiny corpus or a heavily optimized baseline can yield a perf_counter delta of 0.0, crashing the run. Guard with baseline["median_s"] or 1e-9 (and note the fallback in the report).
🟡 report() idempotency assumed — measure(collector.report, ...) invokes the same bound method ~1005 times per case. If report() mutates state (clears internal buffers, appends to a shared list, or caches on the collector), every measurement after the first is wrong or raises. Either clone the collector per iteration (collector.report → a lambda rebuilding a fresh collector) or assert idempotency explicitly.
🟡 --worker-root silently defaults mode — in main, result = diagnostics_worker(args) if args.worker_mode == "diagnostics" else parse_worker(args) treats a missing--worker-mode identically to "current". Since both flags are argparse.SUPPRESSed internal API, a caller typo or an un-updated script will quietly run parse mode instead of failing. Require both together: if args.worker_root and not args.worker_mode: parser.error(...).
🟡 Silent type assumption on IR hashing — hashlib.sha256(parse_one(name, source).dump().encode("utf-8")) assumes dump() returns str. If a future change makes it return bytes, every worker crashes deep in the subprocess and the top-level report only surfaces a redacted one-line RuntimeError, hiding the real traceback. Decode defensively (if isinstance(ir, bytes): ir = ir.decode(...)) or log exc details into report["error"].
🟡 Broad except Exception erases tracebacks in CI — report["error"] = f"{type(exc).__name__}: {exc}" keeps the report schema stable (good), but a KeyError/AttributeError in a check now surfaces as an opaque string, forcing bisect-by-rebuild. Add a debug flag (--traceback) that includes traceback.format_exc(), or stash sys.exc_info() detail behind --verbose.
🟡 Hardcoded exit_code == 1 for all diagnostic cases — checks["cli_exit_code"] asserts code 1 uniformly. If the CLI ever distinguishes "too many errors" (the error_limit case has 20) or usage errors with a different code, this check fails for the wrong reason. Parameterize the expected exit code per case, the same way expected/hint already are.
💭 Markdown fence breakout — markdown_report interpolates case["rendered"] raw into ```text fences. Current output is trusted, but a rendered diagnostic containing ``` or the literal word text on its own line would break the report. Consider using a 4-backtick fence or escaping.
💭 Double trailing newline in --json mode — json_text already ends with "\n" and print(..., end="\n") adds another. Cosmetic, but it makes byte-exact golden-file comparisons flaky.
💭 Very long lines — the checks dict, the HTML/CSS literal, and the lines.append(...) calls run well past 120 cols. If the repo has a formatter/linter, run it before merge so this doesn't create review noise later.
💭 subprocess.run(..., text=True, encoding="utf-8") in run_worker will UnicodeDecodeError if the child emits non-UTF8 (e.g., a Windows console codepage leak). errors="replace" is a cheap safety net.
Positive notes worth keeping: the top-of-file stdlib-only import discipline (deferred until the worker selects a checkout) is exactly the right shape for A/B measurement; the is_relative_to(worker_root) assertion in parse_worker is a genuinely good guard against accidentally benchmarking the wrong checkout; and revision() correctly refusing to label an unpacked baseline with its enclosing repo's SHA avoids a classic A/B mislabeling bug.
🔴 Breaking ctor: dataclass over DSLParseError — Task 1 Step 3: @dataclass on an Exception subclass generates an __init__ that shadows the parent's positional signature; str(error) falls back to args unless __str__ is defined.
Suggestion: Write an explicit __init__ preserving the existing positional order plus an explicit __str__; add a test for DSLSyntaxError("msg", 3, 5) behaving as before.
🔴 Committed doc embeds agent directives and ends in git push — header "REQUIRED SUB-SKILL..." and Task 5 Step 6.
Suggestion: This file lands in version control, so any harness that ingests repo docs receives an instruction to autonomously commit and push. Move the execution directive out of the repo and drop the push step from the plan.
🔴 Validation gate contradiction — Task 2 Step 5 gates DSLParser.parse() before IR, but Task 4 Step 3 routes the driver's DSL branch exclusively to ExtendedDSLParser.parse(...).
Suggestion: As written, the base parser's validation path is dead code in production. State the dispatch rule: does the driver fall back to DSLParser for non-extended files, or does the extended parser subsume it?
🟡 E112 untracked — Task 3 Step 1 tests E110/E111/E112, but Task 5 Step 4 omits E112 and no step defines what separates the three codes.
Suggestion: Pin a code→condition table in one place and include E112 in the final self-review.
🟡 Two render paths for one failure — Task 4 Step 1 requires errors to hold the complete plain rendering while Step 4 renders diagnostics instead of printing errors.
Suggestion: Either guarantee the two are identical by construction (.errors as a projection of .diagnostics) or document it. Otherwise renderers diverge, and non-CLI consumers keep reading a field the CLI ignores.
🟡 col semantics undefined — Task 2 Step 1 asserts exact 1-based columns on tab input; Task 1 Step 3 computes display columns by expanding tabs.
Suggestion: Specify whether col/end_col are source character offsets or display columns post-expansion. If fields and getters disagree, spans misalign on any tab-indented file.
🟡 Per-line stop vs block recovery unspecified — Task 2 Step 3 halts a line at its first structural error; Task 3 Step 3 pushes and pops a block stack.
Suggestion: Define the interaction. For if x followed by a malformed line, is the frame pushed first, and does it then emit a spurious E111 at EOF? Add that combined case to the suite.
🟡 Undefined "normal mode" — Task 4 Step 4: exit 2 "without traceback in normal mode".
Suggestion: Define the switch (--verbose, env var) or drop the qualifier. An undefined branch can't be tested.
🟡 Vocabulary drift — max_errors, limit_reached, diagnostic_limit, diagnostic_limit_reached across Tasks 1–4.
Suggestion: Standardize on one pair (max_errors + limit_reached) and reuse it in the result fields.
🟡 render_error(stream) -> str — Task 1 Step 3.
Suggestion: Writing to a stream and returning the text is a dual side effect; callers will either double-print or guess which is authoritative. Prefer returning str and let the CLI own the write.
💭 Full-suite fallback can't be in scope — Task 5 Step 2 falls back to pytest tests -q, which may surface failures Step 3's boundary forbids fixing.
Suggestion: Pre-declare acceptable unrelated failures, or pin the fallback to the DSL test set.
💭 memory/memory.md write inside a feature PR — Task 5 Step 5.
Suggestion: The plan already refuses to create unrelated infrastructure; the same logic applies to the memory edit.
💭 end_col=None span width unspecified — Task 1 Step 3.
Suggestion: State the fallback (full line vs. len(message)) so spans are deterministic.
📁 docs/topics/09-DSL诊断-CI与Benchmark.md
🔴 Maintenance Risk: Hardcoded SHA 997d2aa — Line 20
This SHA will go stale after any rebase, and the doc acknowledges this but still hardcodes it. Anyone following these instructions 3 months from now will get a dangling reference or, worse, a silently wrong baseline.
Suggestion: Replace with a descriptive placeholder (<PR-base-SHA>) and add a one-liner: git log --oneline main | head -20 to find the correct commit. Alternatively, store the baseline SHA in a small metadata file committed to the PR, not in prose.
The doc references ubuntu-latest and self-hosted runners, but all commands use powershell fences. A contributor on macOS/Linux or in GitHub Actions (where shell is bash) will need to guess whether python -m pip install -e . and the benchmark invocation work identically.
Suggestion: Use bash or shell fences, or add a note: "以下命令适用于 PowerShell;bash 用户去除 powershell 后等价。"
The doc says error samples are "不放入正常 benchmarks/cases/" but never says where they actually live. A new contributor can't find them.
Suggestion: Add the path, e.g. "内置用例定义在 benchmarks/error_cases.json 中" or whichever module/file they reside in.
🟡 Unresolved Reference: "设计文档" — Line 62
"设计文档的解析倍率目标是 1.5x" — no link, no title, no path. This is the single most important performance target in the file.
Suggestion: Inline the target or add a relative link: [设计文档](../01-design.md#performance) or similar.
🟡 Worktree Cleanup Mechanism Not Specified — Line 67
"退出步骤时清理" — in CI, this could be a shell trap, an explicit teardown step, or an action (actions/checkout with cleanup). Without specifying, a future CI change might remove cleanup.
Suggestion: Name the mechanism, e.g. "通过 git worktree remove 显式清理,或使用 actions/checkout@v4 的 cleanup 参数。"
💭 --json vs --json-output distinction — Line 24
The parenthetical is accurate but easy to miss. Since this is a documented API surface, consider making it a callout or adding a short example showing both forms used together:
--json --json-output foo.json
💭 Scope limitation is clear but could be a heading — Line 73
"这里不计算常量合并次数…" is an important out-of-scope statement buried at the end. Promoting it to a ### 不包含 subsection would make it easier to discover during future scope discussions.
🔴 Double file read / TOCTOU — When input_path is a .dsl file and dsl_source is None, the file is opened and read twice: once in compile() for validation (line ~250), once in _parse() (line ~373). If the file is modified between reads, validation and parsing operate on different content. Suggestion: read once in compile(), pass the string to _parse() or refactor _parse to accept pre-read source.
🔴 Removed fallback parser causes unhandled crashes — _parse() previously fell back to DSLParser when ExtendedDSLParser failed. That fallback is removed, and in compile()'s exception handler, non-DSLSyntaxError exceptions are re-raised (raise on the DSL path). Any parser error that isn't DSLSyntaxError (e.g., internal bugs, ValueError, IndexError) will now crash instead of returning a CompileResult. This is a breaking behavioral change.
🟡 Validation + parse does redundant work — ExtendedDSLParser is instantiated twice and the source is processed twice (validate then parse). Consider returning validation results from a single parse() call, or passing the validator/parser instance between compile() and _parse().
🟡 diagnostics: list[Any] is too loose — Any defeats static analysis. If there's a diagnostic type (e.g., DSLSyntaxError or a dedicated Diagnostic class), use it explicitly. At minimum, document what types are expected.
🟡 DSL-specific fields on the general result struct — diagnostic_limit_reached and diagnostic_limit are only populated on DSL validation failure but live on CompileResult unconditionally. Consumers need to know when they're meaningful. Consider nesting them in an optional sub-struct or documenting the invariant.
💭 source or "" in validate call — source is already a str at this point (either from dsl_source or f.read()), so or "" is a no-op safety net. Harmless but noise.
📁 scratchv/frontend/__init__.py
🟡 Exporting implementation details as public API — OP_SIGNATURES and SourceBuffer sound like internal utilities, not frontend public API. Once in __all__, downstream consumers may import and depend on them, making future refactoring (renaming, restructuring) a breaking change. Consider keeping them in their own modules and only re-exporting what's intentionally part of the public surface.
from .dsl_validatorimport (
DSLValidator,
OP_SIGNATURES,
SourceBuffer,
)
💭 __all__ ordering — Groups aren't consistent (classes, then exceptions, then functions, then back to classes). Consider grouping by type or module of origin for scanability.
📁 scratchv/frontend/dsl_errors.py
🔴 Bug: Silent error loss — add(): When limit_reached is true, excess errors are dropped silently and only surfaced via report(). Any caller iterating collector.errors directly won't know errors were suppressed. The old code appended a synthetic warning error so the count was visible. Consider either appending the note to _errors as before, or exposing limit_reached more prominently (e.g., in __repr__, or having errors include the suppression note).
🟡 Dedup key may be too aggressive — add() line ~356: Key is (filename, line, col, error_code, message). Two genuinely distinct errors at the same location (e.g., two different fix hints, or two tokens producing identical messages) will be silently dropped. If dedup is the intent, document the rationale; if not, consider narrowing the key to (filename, line, col, error_code) only, since error_code + location should uniquely identify an issue.
🟡 errors property sorts on every access — sorted(self._errors, ...) allocates a new list each call. Fine if called rarely (like in report()), but if any hot path calls collector.errors in a loop, this becomes O(n log n) per iteration. Consider caching the sorted result and invalidating on add()/clear().
🟡 end_col has no validation — DSLSyntaxError.end_col is Optional[int] with no check that end_col >= col. The max() in format_error prevents crashes, but a col=5, end_col=3 silently degrades to a single-char marker. A debug assertion or docstring note would help.
💭 render_error doesn't honor FORCE_COLOR — Standard env var (FORCE_COLOR=1) for forcing ANSI on non-TTY output (common in CI). Easy add: if "FORCE_COLOR" in os.environ: use_color = True.
💭 Empty DSLParseError — Works for compatibility, but a one-line docstring about which exceptions subclasses it catches (e.g., "catch this to handle all DSL parse errors including future variants") would save the next reader a guess.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Scope
Documentation only. No compiler source code or tests are changed.
Validation