Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
769fbea
chore(mds-core): deprecate apply_fixes in favor of apply_fixes_increm…
dean0x Aug 16, 2026
7dbb970
chore(mds-core): simplify PR5 deprecation docs (style + stale line nu…
dean0x Aug 16, 2026
55ff504
fix: address self-review issues [#209]
dean0x Aug 16, 2026
7dfa6dd
fix(mds-core): address Evaluator alignment findings [#209]
dean0x Aug 16, 2026
82f46fb
fix(mds-core): fix 3 blocking QA scenarios for PR5 deprecation [#209]
dean0x Aug 16, 2026
6a55d55
docs(mds-lint): fix two KNOWLEDGE.md defects flagged by code review
dean0x Aug 16, 2026
821ec23
docs(releasing): add pre-flight grep for deprecated since = attribute…
dean0x Aug 16, 2026
6255aed
ci: add cargo doc gate to prevent intra-doc link rot (OD-209-G)
dean0x Aug 16, 2026
49b4b60
docs(mds-lint): soften absolute completeness claim in removal tracker…
dean0x Aug 16, 2026
f3518e2
fix(mds-core): tighten api_surface.rs deprecation pins and mutation-c…
dean0x Aug 16, 2026
06e14e8
docs(changelog): fix deprecated section completeness and placement [#…
dean0x Aug 16, 2026
adc3dcd
fix(mds-core): match deprecated attribute order and wrap note in fix.rs
dean0x Aug 16, 2026
f127797
docs(plan): amend AC-209-04 to whitelist pre-existing config.rs:287,2…
dean0x Aug 16, 2026
1c8444f
docs(mds-core): fix ragged rustdoc wrap, deprecation tense, and work-…
dean0x Aug 16, 2026
69973b0
docs(mds-lint): correct stale #304 line-number claim in removal tracker
dean0x Aug 16, 2026
9419a3b
docs(releasing): fix since-version rule and remove stale file enumera…
dean0x Aug 16, 2026
3887125
fix(ci): revert rust job name and remove transition residue from rust…
dean0x Aug 16, 2026
0028424
docs: clarify from_rules_checked retention and fix AC-209-13 test set…
dean0x Aug 16, 2026
a2029da
docs(plan): update AC-209-04 and test plan to cover multi-line expect…
dean0x Aug 16, 2026
2e2646a
docs(plan): fix AC-209-04 blind audit grep — require shape-tolerant f…
dean0x Aug 16, 2026
7998fa0
fix(mds-core): correct AD-209-2 mutation-control claim in api_surface…
dean0x Aug 16, 2026
81f0735
docs(plan): align AC-209-05 and test-plan step 5 to single-command 11…
dean0x Aug 16, 2026
4424272
docs(plan): fix audit blindspot and mutation-control two-command spli…
dean0x Aug 16, 2026
1e29288
docs(mds-lint): remove self-referential 'this PR' and stale line coun…
dean0x Aug 16, 2026
0af73fc
fix: address self-review issues [#209]
dean0x Aug 16, 2026
9655347
fix: correct three more falsifiable expected outcomes in the PR5 plan…
dean0x Aug 16, 2026
709dbf8
fix: reconcile design decision D8 with the AC-209-04 whitelist [#209]
dean0x Aug 16, 2026
44bf6d0
docs(mds-core): fix two surviving reviewer findings [#209]
dean0x Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

23 changes: 21 additions & 2 deletions .devflow/features/mds-lint/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ LintDiagnostic.fix_removals (FixLineSpan) OR .fix_edits (TextEdit)

- **Resting a security invariant on `debug_assert!`** (PF-005): `debug_assert!` is compiled out of release. The old `SanitizedReport` returned `None` for `source()`/`related()` behind a `debug_assert!` that no CLI error populates the aux graph — real in tests, absent in the shipped binary. Enforce invariants with data transformation, not assertions.

- **Calling `apply_plan_unchecked()` on a production write path**: Production code that writes back to disk MUST use `apply_fixes()` or `apply_fixes_incremental()`. The `_unchecked` suffix makes the bypass explicit at every call site.
- **Calling `apply_plan_unchecked()` on a production write path**: Production code that writes back to disk MUST use `apply_fixes_incremental()`. The `_unchecked` suffix makes the bypass explicit at every call site.

- **Adding a ModuleCache "optimization"**: Per-file fresh resolve is intentional. A shared cache would be unsafe because runtime vars are per-call.

Expand Down Expand Up @@ -574,11 +574,30 @@ LintDiagnostic.fix_removals (FixLineSpan) OR .fix_edits (TextEdit)
- **PF-007** (cross-surface goldens can't catch divergence): `fix_edits` is emitted unconditionally (null when None) across all surfaces; differential tests cover cross-surface parity.
- **PF-013** (vacuous negative security tests): Every ESC-injection test now pairs a NEGATIVE assertion (raw byte absent) with a POSITIVE one (escaped form present) and a non-vacuity guard (diagnostics non-empty, expected rule matched). T-9 was rewritten from a vacuous YAML-rejection vector to a reachable duplicate-import + U+0085 NEL vector.
- **PF-014** (sanitize inputs, not rendered artifacts): The `SanitizedReport` design — pre-sanitize message/help/labels before miette renders — is the PF-014-correct boundary. Post-processing the rendered frame corrupts miette's own ANSI SGR codes; CI uses `NO_COLOR=1` and pipes stderr so the failure would stay green. T-ESC-6 pins this on the colour path.
- **ADR-001** (span-guided rewrite + compile-equivalence gate): All `--fix` edits are span-guided byte rewrites. `TextEdit` ranges are validated fail-closed. `apply_plan_unchecked` is explicitly named to make ADR-001 bypass visible.
- **ADR-001** (span-guided rewrite + compile-equivalence gate): All `--fix` edits are span-guided byte rewrites. `TextEdit` ranges are validated fail-closed. `apply_plan_unchecked` is explicitly named to make ADR-004 reverify-gate bypass visible.
- **ADR-004** (three-tier --fix safety model, reverify gate): `apply_fixes_incremental`'s batch-first strategy with bounded per-edit fallback is the AC-F-20 implementation.
- **ADR-002** (v0.4.0 whitespace contract, interior-verbatim): The `empty-block` rule's "whitespace-only-Text body" definition is directly downstream of this contract.
- **ADR-003** (@extends FM emission): The `unused-variable` rule is suppressed on `@extends` children.
- **PF-012** (span source-identity): For test attribution (B1 tests), `SerializedError` has no `file` field — use `MdsError::TypeMismatch { src, .. }` pattern-match.
- `crates/mds-core/tests/api_surface.rs` — pins the public lint API signatures.
- `.devflow/features/mds-fmt/KNOWLEDGE.md` — `mds fmt` knowledge base; `atomic_write_file` is shared between both subcommands via `output.rs`.
- `.devflow/features/source-map-security/KNOWLEDGE.md` — source map path-containment choke-point.

## v0.5.0 Removal Tracker: apply_fixes

`mds::fix::apply_fixes` is deprecated as of v0.4.0. The six ADR-004 reverify-gate
tests that must be ported or retired before removal are enumerated below by name —
**test names are the durable key; line numbers drift as `fix.rs` evolves**. GitHub
issue #304 carries behavioral context (which ADR-004 behavior each test pins); its
line numbers predate the `#[expect(...)]` insertions made by PR #303 (issue #209) and
have since drifted.

Six tests to port or retire (use `grep -n 'fn <name>' crates/mds-core/src/lint/fix.rs`
to locate current lines — test names are the durable key):

- `a4_partial_overlap_still_rejected_after_dedup`
- `l_fix_rev1_a5_rejection_message_pins_stable_prefix_and_suffix`
- `reverify_preexisting_untargeted_survives_and_fix_applies`
- `reverify_new_untargeted_diagnostic_is_rejected`
- `tier_b_unused_function_standalone_apply_succeeds`
- `l_fix_rev1_output_delta_causes_rejection`
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --workspace
# Intra-doc link gate (AC-209-11 / OD-209-G): broken intra-doc links in
# mds-core degrade silently to literal text without this check.
# --no-deps excludes dependency crates; -p mds-core scopes the check to mds-core.
- name: Rustdoc (warnings are errors)
run: RUSTDOCFLAGS="-D warnings" cargo doc -p mds-core --no-deps

msrv:
name: MSRV (Rust 1.88)
Expand Down
35 changes: 34 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
only re-exported from `index.ts`, which the package `exports` map does not resolve —
so consumers could receive the value but not name its type. Purely additive.

### Deprecated

- **`mds::fix::apply_fixes` is deprecated in favor of `apply_fixes_incremental` (#209).**
The replacement applies the same ADR-004 three-tier reverify-gate safety contract with a
batch-first attempt plus a bounded per-edit fallback, salvaging the safe subset of fixes
when some edits fail the reverify gate rather than refusing the whole batch.

Two behavioral differences require manual migration:

- **Closure bound**: `apply_fixes` takes `F: FnOnce`; `apply_fixes_incremental` requires
`F: Fn` because the reverify closure may be called more than once. A move-once closure
cannot be migrated mechanically.
- **New reachable outcome**: `apply_fixes_incremental` can return
`FixOutcome::PartiallyFixed` when some edits are accepted and some are refused.
`apply_fixes` never returns `PartiallyFixed`. Because `FixOutcome` is
`#[non_exhaustive]`, existing wildcard arms compile unchanged, but a wildcard that
swallows `PartiallyFixed` silently discards partial results.

Scheduled for removal in v0.5.0; tracked in GitHub issue #304. The six ADR-004
regression tests pinned only against `apply_fixes` must be ported or retired before
the v0.5.0 tag (see #304 for the enumerated list with line numbers and the behavior
each test pins).

- **`mds::LintConfig::from_rules` is deprecated in favor of `LintConfig::from_rules_checked` (#224).**
The replacement returns both the config and an unknowns report in a single `#[must_use]`
call, making it structurally impossible to silently skip unknown-rule detection.
`from_rules` still accepts any rule name without error; unknown names have no effect.

Migration: change `LintConfig::from_rules(map)` to `LintConfig::from_rules_checked(map)`
and handle the `Option<UnknownRuleNames>` second return value. No removal is scheduled
before v1.0.0; this function will remain available throughout the v0.x series (contrast
`apply_fixes` above, which is scheduled for removal at v0.5.0).

### **BREAKING** — File-method `basePath` rejection, TypeScript option types, and WASM `basePath` rejection (#180, #213)

#### `compileFile` and `checkFile` now reject `basePath` (#180)
Expand Down Expand Up @@ -194,7 +227,7 @@ via struct literals. Use the named constructor or builder listed for each:
- **`RejectedEdit`** — use `RejectedEdit::new(edit, reason)`.
- **`FixPlan`** — use `FixPlan::default()` for an empty plan; its fields are `pub`, so they
remain directly readable and writable from external crates.
- **`LintConfig`** — use `LintConfig::from_rules(rules)` or `LintConfig::default()` for no overrides.
- **`LintConfig`** — use `LintConfig::from_rules_checked(rules)` or `LintConfig::default()` for no overrides.
- **`LintDiagnostic::sanitized_for_render()`** — a new method that returns a sanitized clone
suitable for miette render boundaries. `mds-cli`'s diagnostic render path now delegates to
this method instead of assembling sanitized copies itself, keeping the escape logic co-located
Expand Down
7 changes: 7 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ npm run build -w @mdscript/mds-wasm
npm run build --workspaces --if-present
npm test --workspaces --if-present
node scripts/verify-versions.mjs
# Verify #[deprecated(since = ...)] attributes match the release version.
# bump-version.mjs rewrites manifests and CHANGELOG only -- never .rs files.
# Every hit's version must be <= X.Y.Z. A deprecation introduced in THIS release
# must equal X.Y.Z; pre-existing ones keep their original version.
# ADR-009: if the grep returns no hits, plant a temporary `since = "x.y.z"` in any
# .rs file, confirm the grep finds it, then remove it before proceeding.
grep -rn 'since = ' crates/ --include='*.rs'

# Source hygiene and pre-merge check gates
node scripts/verify-no-control-bytes.mjs
Expand Down
2 changes: 1 addition & 1 deletion crates/mds-core/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub trait FileSystem: Send + Sync {
/// - the resolved path escapes the established project root ([`NativeFs`] only)
///
/// Returns [`MdsError::ResourceLimit`] when the resolved path exceeds
/// [`MAX_PATH_SEGMENTS`] segments.
/// `MAX_PATH_SEGMENTS` segments.
fn normalize_in_dir(&self, dir: &str, relative: &str) -> Result<String, MdsError>;

/// Return the directory portion of a normalized file key.
Expand Down
2 changes: 1 addition & 1 deletion crates/mds-core/src/lint/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub fn find_unknown_rule_names(rules: &HashMap<String, Severity>) -> Option<Unkn
/// has no failure mode (PF-005 — a `debug_assert!` here would be a no-op in
/// release, and a release `assert!` would be a panic in a library).
///
/// Each name is WIRE-escaped with [`sanitize_control_chars_wire`] before it is
/// Each name is WIRE-escaped with [`crate::sanitize_control_chars_wire`] before it is
/// interpolated (spec §7.5 per-field rule: a rule name is a single-line
/// identifier, never prose). A rule name is an arbitrary caller-supplied map key
/// and JSON `\uXXXX` escapes decode to real control bytes, so the escape is what
Expand Down
6 changes: 3 additions & 3 deletions crates/mds-core/src/lint/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ impl miette::Diagnostic for LintDiagnostic {
///
/// This type is `#[non_exhaustive]`: new fields may be added in minor releases.
/// Obtain values from `mds::lint_str`, `mds::lint`, and similar lint API functions,
/// or construct via [`LintResult::new`] (and chain [`.truncated()`] / [`.standalone()`]);
/// or construct via [`LintResult::new`] (and chain [`LintResult::truncated`] / [`LintResult::standalone`]);
/// do not construct via struct literal.
#[non_exhaustive]
#[derive(Debug)]
Expand All @@ -641,7 +641,7 @@ impl LintResult {
/// Construct a `LintResult` from a diagnostic list.
///
/// Defaults: `truncated = false`, `is_standalone = false`.
/// Chain [`.truncated()`] or [`.standalone()`] to override.
/// Chain [`Self::truncated`] or [`Self::standalone`] to override.
///
/// This is the supported construction path for external crates — struct literals
/// are not available because this type is `#[non_exhaustive]`.
Expand All @@ -655,7 +655,7 @@ impl LintResult {
/// (`LintResultBuilder::build`) sorts after truncation, so results produced
/// by `lint`/`lint_source` always carry the canonical offset order.
/// A `LintResult` built via this constructor preserves caller order on all
/// surfaces — [`to_canonical_json`] included — because `sort_diagnostics`
/// surfaces — [`LintResult::to_canonical_json`] included — because `sort_diagnostics`
/// is the single ordering choke point (AD-202-1) and it is not called here.
///
/// # Examples
Expand Down
Loading
Loading