Skip to content

Releases

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 11 Nov 15:30

0.12.0 — every record now says whether it still means anything 🔍

Phase E added no capability. It made the records the plugin already keeps reviewable: a lifetime total that
names the day the books opened, an ignore line that is named when it excludes nothing, and a mute list that can
be shrunk by one entry after a plan. Nothing expires on its own; a human retires what no longer describes anything.

✨ Features

  • A lifetime total says when the books opened. scanCountsSince is the selected bucket's own start, and for
    an aggregate the oldest contributing bucket's — never the document's, which would date a young workspace before
    it existed. The caption gains · since 2026-09-14; a document with no origin renders the 0.11.0 caption.
  • An ignore line that excludes nothing is named. The walk records, per source line, whether that pattern
    excluded anything, and the report prints .scanignore: 3 patterns, 1 matched nothing. Two lines with the same
    effect are counted separately, so the duplicate you may now delete is visible.
  • The mute list can be reviewed and shrunk by one entry. /guidance-suppress --review lists every entry
    with its date and reason against the last scan, and never writes; --remove <siteId> [--dry-run] plans one
    removal, and the same line without the flag applies it.

🧪 Behaviour changes

  • Absence is stated, never assumed. unused is null, never 0 and never an empty list, when the scan was
    truncated, hit a cap or read fewer files than its baseline: an incomplete scan cannot prove a suppression dead.
  • An old counter stays undated rather than borrowing a date. normalizeState no longer stamps now onto a
    loaded document without since; legacy root counters have no origin, so _unattributed and its aggregates are silent.
  • A removal is not a fix. It un-mutes one site and moves no counter: the counters document is byte-identical
    through a removal, lastScan.suppressed keeps the count it was given, and the next scan counts the finding
    again. The words stay apart: muted, unused, resolved — the last one does not exist in the product.
  • A copy that never answers says so. The live panel pass found that Копировать JSON could look dead
    forever, because a clipboard write parked by an unfocused window never settles: the label now races a bounded
    wait and says "The clipboard did not answer". That is not "refused", and a late success does not flip it back.
  • The transcript row each panel action leaves is the accepted price. execute renders every command line the
    panel runs, so a mount and a scan cost one visible JSON block each; a cache or a second data path would be worse.

🛠 Hardening

  • The removal writer is narrower than the append writer. One entry, one invocation, one confirmed rewrite:
    temp-then-rename, every other byte and the entry order preserved, a malformed line preserved exactly, the landed
    bytes re-parsed before the rename, a failed write leaving the file byte-identical, and an unknown id is a no-op.
  • The ignore patterns never reach stats.json. Rows stay in memory on the scan value and only
    { patterns, idle } is persisted — an ignore line is a path fragment and that file is profile-wide.

🧩 Compatibility

  • version stays 1. Every field is additive and absent on an older document, which renders the 0.11.0 panel.
    The four tools are unchanged, there is no new command — review and removal are flags on it — and no new
    dependency and no new chart.

📝 Skills and docs

  • ADR-0004 amends ADR-0003's "never a delete" to append and one confirmed
    removal
    , and states the residual it cannot design away: a removal cannot undo what a previous scan reported.

📦 Package

  • 258 files, 989 kB packed, 2.9 MB unpacked — no new module, zero runtime
    dependencies, Apache-2.0.
  • 506 tests in 18 files: 502 passed, four skipped (this machine's symlink limit). The final tree was then
    run 40 times back to back to answer one question the earlier phases had only recorded: a flake that
    showed up as EBUSY in test/stats.test.mjs's own teardown, and — once the retry was in — as ENOENT
    reading the counters file, one run in twenty each. The retry alone still gave 19 green of 20; the cause was
    underneath it, a fixed 20 ms sleep racing the plugin's 2-second debounced save, so settle() now waits for
    the write that is actually in flight and the retry only covers the platform's lock. 20 green, 0 red, and
    the harness sweeps the temp homes it leaves behind.

🇷🇺 Коротко по-русски

Развернуть
  • Пожизненный итог говорит, когда открылись книги: scanCountsSince — это дата, начало выбранной папки, а для
    сводки — начало самой старой из входящих в неё; дата документа не подходит, она была бы раньше самой папки.
    Подпись в панели получает · since 2026-09-14, а документ без начала рендерится ровно как в 0.11.0.
  • Строка .scanignore, которая ничего не исключает, теперь названа: отчёт печатает
    .scanignore: 3 patterns, 1 matched nothing. — это единственное место, где строку файла можно назвать
    простаивающей, и две строки с одинаковым эффектом считаются отдельно, чтобы дубликат был виден.
  • Список подавлений можно просмотреть и уменьшить на одну запись: /guidance-suppress --review сверяет файл
    с последним сканированием (дата, причина, сколько совпало, сколько нет) и ничего не пишет;
    /guidance-suppress --remove <siteId> [--dry-run] сначала показывает план, а без флага удаляет ровно одну
    запись.
  • Отсутствие доказательства не превращается в ноль: unused — это null, а не 0 и не пустой список, когда
    сканирование было усечено, упёрлось в кап или прочло меньше файлов, чем база; битая строка — это проблема
    разбора, а не «неиспользуемая».
  • Старый счётчик остаётся без даты, а не занимает чужую: normalizeState больше не ставит now документу без
    since, дату получает только действительно новый документ или папка, поэтому _unattributed и сводки с ним
    даты не печатают.
  • Удаление — не исправление: оно снимает заглушение с одного места и не двигает ни один счётчик, документ
    счётчиков остаётся байт в байт, а следующее сканирование снова считает находку находкой.
  • Копирование, которое не отвечает, так и говорит: метка гоняется с ограниченным ожиданием и пишет «буфер
    обмена не ответил», это не «отказал», и поздний успех её не переворачивает. Строка команды, которую каждая
    кнопка панели оставляет в транскрипте, — принятая цена, а не недосмотр. Пакет: 258 файлов,
    989 kB в упаковке, 2.9 MB распакованного
    , 506 тестов в восемнадцати файлах (502 прошли,
    четыре пропущены — все из-за symlink в этой машине). Финальное дерево прогнано 40 раз подряд: сначала
    флейк проявлялся как EBUSY при уборке временного дома, а после ретрая — как ENOENT при чтении файла
    счётчиков, по одному разу на двадцать прогонов; ретрай дал 19 из 20, а причина была глубже — фиксированные
    20 мс гонялись с двухсекундным дебаунсом записи, поэтому settle() теперь ждёт реально идущую запись.
    Итог: 20 зелёных, 0 красных, и обвязка подметает временные дома прошлых прогонов. Зависимостей ноль.

🇨🇳 中文摘要

展开
  • 终身合计现在说明账本从哪天开始:scanCountsSince 是一个日期,取所选工作区自己的起点,汇总时取其中最早的一个;
    不用文档的开始日期,那会把年轻的工作区标到它存在之前。面板的终身说明加上 · since 2026-09-14,而没有起点
    的文档渲染得和 0.11.0 一模一样。
  • 什么都不排除的 .scanignore 行现在会被点名:报告打印 .scanignore: 3 patterns, 1 matched nothing. —— 这是
    产品里唯一允许把某行叫作「闲置」的句子;效果相同的两行分别计数,因此可以删掉的重复行看得见。
  • 屏蔽列表可以查看,也可以减掉一条:/guidance-suppress --review 把文件与最近一次扫描对照(日期、原因、匹配
    几条、几条未匹配)且不写任何东西;/guidance-suppress --remove <siteId> [--dry-run] 先给出计划,去掉
    --dry-run 后只删除一条记录。
  • 没有证据就不写成零:扫描被截断、触到上限或读到的文件比基线少时,unused 是 null,不是 0,也不是空
    数组;格式坏掉的行是解析问题,永远不会被算作「未匹配」。
  • 旧计数器宁可没有日期,也不借用别人的:normalizeState 不再给没有 since 的文档盖上 now,只有真正新建的
    文档或工作区才有日期,因此 _unattributed 以及包含它的汇总都不打印日期。
  • 删除不是修复:它解除一处屏蔽,不动任何计数器 —— 计数器文档逐字节不变,下一次扫描仍把该发现算作发现。
  • 不会应答的复制会说出来:标签与一段有界等待赛跑,并显示「剪贴板没有响应」;这不是「拒绝」,迟到的成功也不会
    把标签翻回去。面板每个动作在对话里留下一行命令,这是被接受的代价,不是疏忽。打包:258 个文件、
    989 kB、解包 2.9 MB
    ,506 个测试 / 18 个文件(502 通过,4 跳过,都是这台机器无法创建
    符号链接所致)。最终代码树连跑 40 次:抖动先以清理临时 home 时的 EBUSY 出现,加上重试后又以读取计数器
    文件时的 ENOENT 出现,各约每 20 次 1 次;只加重试仍是 19/20,根因更深 —— 固定的 20 毫秒在和插件两秒的
    防抖写入赛跑,所以 settle() 现在等待真正进行中的写入。结果:20 次通过、0 次失败,测试夹具也会清扫
    上一次运行留下的临时目录。运行时依赖仍为零。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.11.0...v0.12.0

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 11 Nov 12:32

0.11.0 — the panel can act, and every action asks first ✍️

0.10.0 made the counters honest about whose numbers they are. 0.11.0 gives the panel hands: it writes for
the first time, to exactly two files, one entry at a time — behind a visible plan, a second, distinct
confirmation, and a refusal that is reported as a refusal. Same four tools, no new dependency.

✨ Features

  • Mute one finding, never a rule or a line. /guidance-suppress <siteId> [--dry-run] appends one entry
    to <workspace>/.suppress, keyed by the 8-character site hash phase B compares, capped at 200 entries, a
    200-character reason and 256 KiB. A row offers Mute, the plan shows under it, only a second click appends.
    The muted finding keeps its place, order and count: 85 findings, 3 suppressed in the scan, Suppressed: 3 in the last scan (ls-13 2, ls-02 1) - muted, still findings in the report, muted in the panel.
  • Offer a .scanignore line, and say what it does. /guidance-scanignore <pattern> [--dry-run] appends
    exactly one line, never rewrites, and judges identity by effect (report/ and /report/ are one pattern).
    It makes the next scan read less and mutes nothing; the plan names the file's own line, the count it would
    reach and how wide the pattern is, and the panel keeps "reads less" apart from "muted" in all three languages.
  • Reset one slice instead of everything. --scan-only, --workspace <name|id|path> and --days N narrow
    --reset --yes, which keeps the meaning it always had; each plan names the target, each result names what
    stayed.
  • Copy as JSON. The panel copies the envelope it already holds: no host call, no file, no new command —
    and the button is absent when there is no report or no clipboard.

🧪 Behaviour changes

  • The panel is no longer read-only, and it is not a writer either: every action is a command the host
    runs — four human commands now, not two — so a scan or a render still writes nothing, a single click writes
    nothing, and a refusal can never look like a success.
  • Muted is not fixed. A suppressed site keeps its place in the scan and is never reported as "resolved" by
    the phase B comparison: the number sits beside the findings, it is never subtracted from them.
  • No reset, at any scope, opens the suppression file: its messages end "The suppression file was not
    touched: muting is not a counter", and a --days reset adds that lifetime rule and guide totals carry no day.

🛠 Hardening

  • The reader stays forgiving; the writer does not. Append-only, one fixed path, realpath-contained,
    temp-then-rename, and the result re-parsed through the scanner's own reader before the rename: a symlink, a
    directory or a malformed file is a refusal, and a failed write leaves the original byte for byte. .scanignore
    has no cap where .suppress does — deliberate, and the plan states the count without claiming headroom.
  • One identity, three readers. lib/site.js is the single site derivation the scanner, the counters and
    the file all use, so a mute cannot name a different site than the scan that found it.

🧩 Compatibility

  • version stays 1. suppressed and suppressedRules are additive and clamped to findings; a
    document written before this release loads and renders byte-identically — zero prints nothing at all.
  • The four tools are untouched. The chat row gains a (suppressed) suffix and nothing else.

📝 Skills and docs

📦 Package

  • 258 files, 971 kB packed, 2.9 MB unpacked — two new modules (lib/site.js,
    lib/suppress.js), zero runtime dependencies, Apache-2.0.
  • 463 tests in 18 files: 459 passed and four skipped, every skip this machine's symlink restriction.

🇷🇺 Коротко по-русски

Развернуть
  • Заглушить одно место — не правило и не строку: /guidance-suppress <siteId> [--dry-run] [--reason <text>]
    дописывает одну запись в <workspace>/.suppress, ключ — тот же 8-символьный хеш места, что и в фазе B;
    капы 200 записей, 200 символов причины, 256 KiB. В панели у строки есть кнопка Заглушить, план
    показывается под ней, и дописывает только второй, отдельный клик.
  • Заглушённая находка остаётся находкой: она сохраняет место, порядок и счёт, получает suppressed: true,
    и её по-прежнему считают все поверхности — скан печатает 85 findings, 3 suppressed, отчёт
    Suppressed: 3 in the last scan (ls-13 2, ls-02 1) - muted, still findings, а панель помечает строку
    muted. Заглушено — не исправлено, и сравнение фазы B никогда не назовёт такое место «исчезнувшим».
  • Игнорирование — не заглушение: /guidance-scanignore <pattern> [--dry-run] дописывает ровно одну строку,
    никогда не перезаписывает файл, а совпадение считает по эффекту (report/ и /report/ — один шаблон).
    Следующее сканирование читает меньше; панель так и говорит, ни в одном из трёх языков не смешивая слова.
  • Сброс теперь можно сузить: --scan-only, --workspace <name|id|path>, --days N; голый
    --reset --yes стирает ровно то же, что и раньше. План называет цель, результат — что осталось.
  • Ни один сброс, ни в каком объёме, не открывает файл подавлений: его сообщения кончаются словами «файл
    подавлений не тронут: заглушение — не счётчик». Сброс по --days добавляет, что пожизненные итоги правил
    и гайдов не имеют дня и потому остаются.
  • «Копировать JSON» копирует конверт, который у панели уже есть: без вызова хоста, без файла и без новой
    команды; кнопки нет, если нет отчёта или буфера обмена. Пакет: 258 файлов, 971 kB в упаковке,
    2.9 MB распакованного
    , 463 теста в восемнадцати файлах (459 прошли, четыре пропущены —
    все из-за ограничения на symlink в этой машине), зависимостей по-прежнему ноль.

🇨🇳 中文摘要

展开
  • 屏蔽一条发现 —— 不是一条规则,也不是一行:/guidance-suppress <siteId> [--dry-run] [--reason <text>]
    往 <workspace>/.suppress 追加一条记录,键是 B 阶段用的同一个 8 字符位置哈希;上限 200 条、原因 200
    字符、文件 256 KiB。面板在对应行提供屏蔽按钮,计划显示在该行下方,只有第二次、单独的一次点击才写入。
  • 被屏蔽的发现仍然是发现:它保留位置、顺序与计数,加上 suppressed: true,所有界面照旧统计它 —— 扫描打印
    85 findings, 3 suppressed,报告打印 Suppressed: 3 in the last scan (ls-13 2, ls-02 1) - muted, still findings,面板给该行打上 muted 标签。屏蔽不等于修好,B 阶段的比较永不会把它说成「已消失」。
  • 忽略不是屏蔽:/guidance-scanignore <pattern> [--dry-run] 只追加一行,从不重写文件,判定按效果
    (report/ 与 /report/ 是同一条模式)。下一次扫描读得更少;面板在三种语言里都把这两个承诺分开说。
  • 重置可以只清一片:--scan-only、--workspace <name|id|path>、--days N;裸的 --reset --yes 仍然
    清掉它一直清掉的那些。计划说明目标,结果说明留下了什么。
  • 任何范围的重置都不会打开屏蔽文件:消息结尾是「屏蔽文件未被触碰:屏蔽不是计数器」。按 --days 重置还会
    说明规则与指南的终身合计没有日期,因此保留。
  • 「复制为 JSON」复制的是面板本来就持有的信封:不调用宿主、不读写文件、不新增命令;没有报告或没有剪贴板时
    按钮不出现。打包:258 个文件、971 kB、解包 2.9 MB,463 个测试 / 18 个文件
    (459 通过,4 跳过,都是这台机器无法创建符号链接所致),运行时依赖仍为零。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.10.0...v0.11.0

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 11 Nov 07:23

0.10.0 — whose numbers are these, and did the fix land? 🧭

0.9.0 taught a counter to explain itself. 0.10.0 makes it say whose numbers they are: scan counters
are partitioned per workspace, the last scan names its branch, and history answers "when did a rule fire"
and "is this site still there". Same four tools, same 33 rules, same version: 1, no new dependency.

✨ Features

  • The panel says which project it is showing. A Workspace: {name} chip and a switcher sit in the
    header; /guidance-stats --workspace <name|id|path> and --all do the same from the command line.
    Scan counters are kept apart per workspace inside the one stats.json; guidance counters stay
    profile-wide, because a hit rate measures the corpus, not a directory.
  • The last scan names its branch. A completed scan reads exactly <workspace>/.git/HEAD — one hop
    through a worktree's gitdir: pointer, a 1 KiB cap on each file, no git process, no enumeration —
    and prints ; branch main or ; detached at a1b2c3d. A failed read prints nothing at all: no
    "unknown" token, no guess (ADR-0002).
  • Twenty scans of history, and one honest comparison. scanByDay and per-rule first/last-seen
    dates answer "when", a 20-deep scanHistory answers "what the recent scans found", and a comparison
    line answers "did the fix land": Since the scan of 2026-11-09 08:12: 3 no longer present, 82 still open, 1 new. The report adds a five-row Recent scans block, the panel the same line and a last-seen date.

🧪 Behaviour changes

  • /guidance-stats defaults to the session's workspace, not to every workspace: the unqualified
    number that mixed projects is the defect this release removes. With no session workspace it aggregates
    and says why; --all asks for the aggregate explicitly.
  • A site is compared by its hash, not by its line. The key is FNV-1a over path, rule id and snippet,
    so a comment inserted above it changes nothing, and only eight hex characters are stored — a hash of a
    path, never a path, and never the snippet; an edited snippet is one site gone and one new, never a fix.
  • "Not comparable" is a first-class answer, with seven guard tokens: no-baseline, fewer-files,
    truncated, no-files, sites-unknown, branch-changed and rule-table-changed. They print raw,
    like a skip reason, and the comparison then carries no numbers — never three zeros.

🛠 Hardening

  • Every addition is bounded: history depth 20, ten rules per entry, four skip reasons, 200 site keys,
    40 diff rows, five rows on screen, one 1 KiB cap per file read, one gitdir: hop and nothing more.
  • One new read surface, and it only makes a label: .git/HEAD is read on the completed-scan path
    only, never from an argument, and the scan tool's result and text are unchanged — the model sees no label.

🧩 Compatibility

  • version stays 1. The partition, the branch member and the history are additive, and a bump
    would be silent data loss: the normaliser reads any other version as an empty document. A 0.9.0
    document loads intact, its scan counters becoming the _unattributed bucket, named rather than hidden.
  • Version skew costs a line, never a row. A host without the new fields renders the 0.9.0 panel and
    report exactly; an absent history, comparison or label renders shorter rather than as an invented zero.
    engines unchanged: DSH >=0.2.0-rc.1 <0.3.0-0, Node >=22.19.0.

📝 Skills and docs

📦 Package

  • 256 files, 943 kB packed, 2.8 MB unpacked, zero runtime dependencies, Apache-2.0.
  • 373 tests in 17 files — 372 passed plus one symlink-walk test that skips itself where the host
    forbids creating symlinks.

🇷🇺 Коротко по-русски

Развернуть
  • Панель говорит, чьи это числа: чип Рабочая папка: {name} и переключатель в шапке, флаги
    --workspace <name|id|path> и --all у команды. Счётчики сканера у каждой рабочей папки свои,
    счётчики гайдов общие на профиль: доля попаданий измеряет корпус, а не каталог.
  • Последний скан называет ветку: читается ровно <workspace>/.git/HEAD — один переход по gitdir:,
    кап 1 KiB на файл, без git и без перечисления, — и печатается ; branch main или
    ; detached at a1b2c3d. Не прочиталось — не печатается ничего, без «unknown».
  • История на 20 сканов и честное сравнение: scanByDay и даты первого/последнего появления правила
    отвечают «когда», а строка сравнения — «исправили ли»: Since the scan of …: 3 no longer present, 82 still open, 1 new. В отчёте появляется блок Recent scans из пяти строк, в панели — та же
    строка, пять строк истории и дата последнего появления у каждого правила.
  • /guidance-stats по умолчанию показывает рабочую папку сессии, а не все сразу: без сессионной
    папки отчёт агрегирует и объясняет почему, --all просит агрегат явно.
  • Сайт сравнивается по хешу, а не по строке: ключ — FNV-1a от пути, id правила и сниппета, поэтому
    вставленный комментарий больше не переименовывает всё ниже; хранятся восемь hex-символов — хеш
    пути, а не путь, и не сниппет.
  • «Сравнение невозможно» — полноценный ответ: семь токенов (no-baseline, fewer-files, truncated,
    no-files, sites-unknown, branch-changed, rule-table-changed) печатаются сырыми, и тогда в
    сравнении нет ни одного числа — никаких трёх нулей.
  • version остаётся 1: всё аддитивно, документ 0.9.0 читается целиком, его счётчики сканера
    становятся бакетом _unattributed. Пакет: 256 файлов, 943 kB в упаковке, 2.8 MB
    распакованного
    , 373 теста в семнадцати файлах (372 прошли, один пропускается), зависимостей
    по-прежнему ноль.

🇨🇳 中文摘要

展开
  • 面板说清数字属于谁:标题栏里有 工作区:{name} chip 与切换器,命令行有 --workspace <name|id|path>
    与 --all。扫描计数器按工作区分开,指南计数器仍是整个 profile 的:命中率衡量的是语料,不是目录。
  • 最近一次扫描带分支标签:只读 <workspace>/.git/HEAD —— 透过 worktree 的 gitdir: 只跟一跳、每个
    文件 1 KiB 上限、不运行 git、不枚举目录 —— 打印 ; branch main 或 ; detached at a1b2c3d;
    读不到就什么都不打印,没有 “unknown”。
  • 20 条扫描历史加上诚实的比较:scanByDay 与规则首次/末次出现日期回答「何时」,比较行打印
    Since the scan of …: 3 no longer present, 82 still open, 1 new;文本报告多出五行 Recent scans,
    面板多出同一行比较、五行历史与每条规则的最后出现日期。
  • /guidance-stats 默认显示当前会话的工作区,而不是所有工作区混在一起:没有会话工作区时报告会聚合
    并说明原因,--all 显式要求聚合。
  • 位置按哈希比较而不是按行:键是路径、规则 id 与代码片段的 FNV-1a,插入一行注释不会再把它下面每个
    位置改名;只存八个十六进制字符 —— 是路径的哈希,不是路径,也不是片段。
  • 「无法比较」是一等答案:七个守卫 token(no-baseline、fewer-files、truncated、no-files、
    sites-unknown、branch-changed、rule-table-changed)原样打印,此时比较里没有任何数字 ——
    绝不给三个零。
  • version 仍是 1:所有字段都是追加的,0.9.0 的文档完整读入,其扫描计数器成为 _unattributed 桶。
    打包:256 个文件、943 kB、解包 2.8 MB,373 个测试 / 17 个文件(372 通过,
    1 跳过),运行时依赖仍为零。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.9.0...v0.10.0

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 11 Nov 07:23

0.9.0 — a counter that explains itself 🔎

A ranked rule used to read ls-17 121: an id only the source explains, a number with no unit, on a
screen whose period switch does not apply to it. 0.9.0 gives every ranked counter the words it was
missing — its confidence, a count that says 121 findings, and the rule's own sentence — and adds one
caption saying the ranked totals are lifetime. Same four tools, same 33 rules, same scanner behaviour,
no new dependency.

✨ Features

  • A rule row explains itself. ls-17 121 becomes ls-17 · medium · 121 findings with the rule's
    own sentence — "Build the DOM with createElement/append instead of document.write(), which discards
    the parsed document." — clamped to two lines underneath, the whole message in the row's title, and
    no second line at all when the host sends none. The confidence token stays raw
    (high/medium/low), like a rule id.
  • A guide chip names its guide. html (121) keeps its visible id and count and carries the
    catalog's description as a hover hint, so the ranked hints can be read without a retrieval call.
  • The chat row explains a finding without adding a line. The scan table reads the per-finding
    message and snippet it already received as titles on the rule and place cells — the two facts
    it used to throw away.

🧪 Behaviour changes

  • One caption under "Rules that matched" while the scope is lifetime: "Lifetime totals, not the
    {days}-day window", naming the window actually selected, so the period switch and the ranked counts
    no longer look like they answer the same question. It is the release's only new panel string, and it
    lands in the English, Russian and Chinese dictionaries in one commit — the parity test fails
    otherwise.
  • /guidance-stats --json can carry the meaning. renderJson(snapshot, { rules, guides }) adds
    message, confidence and guideId to every topRules row, description to every
    topScanGuides row, and the top-level scanCountsScope: 'lifetime'. The command passes both
    tables; renderJson(snapshot) with no options still returns the earlier document byte for byte, so
    an older consumer cannot tell the enrichment exists.
  • Nothing in the scanner moved. Same 33 rules, same caps, same refusals, same text, same finding
    shapes: this release changes what the counters say, not what they count.

🧩 Compatibility

  • Version skew costs a caption or a hint, never a row. A host without the enriched fields leaves
    this panel rendering the terse ids and counts it always did, with no caption. A rule id the current
    table no longer knows keeps its row with empty text, an empty confidence and a null guide — the
    count is still true when its explanation is gone.
  • engines is unchanged: DSH >=0.2.0-rc.1 <0.3.0-0, Node >=22.19.0.

📝 Skills and docs

  • The contract is written down where the next reader looks: docs/notes/legacy-scan-contract.md, the
    panel's docs/notes/client-panel-spike.md, and the AGENTS.md bullet a later panel change has to
    keep true.

📦 Package

  • 256 files, 913 kB packed, 2.7 MB unpacked, zero runtime dependencies, Apache-2.0. The file list
    does not move: the growth is source inside lib/report.js, index.js and client.js.
  • 257 tests in 15 files — 256 passed plus one symlink-walk test that skips itself where the host
    forbids creating symlinks.

🇷🇺 Коротко по-русски

Развернуть
  • Панель объясняет счётчики: строка ls-17 121 читается как ls-17 · medium · 121 findings, под ней
    собственное предложение правила (две строки, полный текст в title; пустое сообщение не оставляет
    второй строки). Токен уверенности (high/medium/low) остаётся сырым, как id правила.
  • Чип гайда html (121) сохраняет видимые id и число, а описание из каталога уходит в подсказку.
  • Строка находки в диалоге берёт уже полученные message и snippet как title у ячейки правила и
    ячейки места — без новой строки на каждую находку.
  • Одна подпись под «Сработавшие правила», пока счёт идёт за всё время: «Итоги за всё время, а не за
    {days} дн.». Она называет выбранное окно, поэтому переключатель периода и ранжированные числа больше
    не выглядят ответом на один вопрос. Это единственная новая строка панели, и она есть в словарях EN,
    RU и ZH сразу.
  • JSON прирастает, форма не меняется: renderJson(snapshot, { rules, guides }) добавляет message,
    confidence, guideId к строкам правил, description — к строкам гайдов, и
    scanCountsScope: 'lifetime'; вызов без опций возвращает прежний документ байт в байт.
  • Расхождение версий стоит подписи или подсказки, но не строки: правило, которого таблица уже не
    знает, остаётся со счётчиком и пустым текстом.
  • Пакет: 256 файлов, 913 kB в упаковке, 2.7 MB распакованного, 257 тестов в 15 файлах (256
    прошли, один пропускается там, где нельзя создать символическую ссылку), зависимостей по-прежнему
    ноль; поведение сканера не изменилось.

🇨🇳 中文摘要

展开
  • 面板让计数器自己解释:ls-17 121 变成 ls-17 · medium · 121 findings,下面附上规则自己那句话(最多
    两行,完整内容放在 title;宿主没有句子就只剩一行)。置信度保持原样,和规则 id 一样不翻译。
  • 指南 chip html (121) 仍显示 id 与计数,目录里的描述放进 title 作为提示。
  • 对话里的发现表格把已经收到的 message 与 snippet 用作规则单元格和位置单元格的 title,不再为
    每条发现多加一行。
  • 「命中的规则」下方多一行说明:「累计总数,不是最近 {days} 天的窗口」。它点明当前选中的窗口,上方的时间
    切换与排名数字不再看起来在回答同一个问题。这是本版唯一新增的面板字符串,EN、RU、ZH 三份词典同时
    加入。
  • JSON 只增加字段:renderJson(snapshot, { rules, guides }) 为规则行补上 message、confidence、
    guideId,为指南行补上 description,并给出 scanCountsScope: 'lifetime';不带参数调用仍然逐字节
    返回旧文档。
  • 版本错配只丢一行说明或一个提示,不会丢一行数据:规则表已经不认识的 id 仍然保留计数,只是文字为空。
  • 打包:256 个文件、913 kB、解包 2.7 MB,257 个测试 / 15 个文件(256 通过,1 个在无法创建
    符号链接的环境里跳过),运行时依赖仍为零;扫描器行为没有变化。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.8.2...v0.9.0

v0.8.2

Choose a tag to compare

@github-actions github-actions released this 10 Nov 22:18

0.8.2 — the mark nobody can see 🫥

0.8.1 shipped a manifest the host refuses to load. Nothing about the code changed between the two: a
UTF-8 byte-order mark was written into package.json, the file looks identical in every editor and in
git diff, and the install fails with SyntaxError: Unexpected token. 0.8.2 removes the mark and adds
the check that would have caught it.

🛠 Fix

  • package.json no longer starts with a BOM. The host validates the manifest with JSON.parse,
    which throws on a leading U+FEFF; require() strips the mark and npm pack does not mind, so the
    suite and the CI gate were both green while every install of 0.8.1 failed. A DSH install of 0.8.1
    reports installation rejected: Cannot validate installed package … SyntaxError: Unexpected token '', and a published package cannot be corrected in place, so the fix is a new version.
  • A guard now fails on a BOM instead of a user. test/plugin.test.mjs reads the first three bytes
    of every file the host parses — package.json, index.js, client.js, the three locale
    dictionaries and the two corpus JSON files — and asserts they are not EF BB BF. Verified by putting
    the mark back: the test goes red with package.json starts with a UTF-8 BOM; the host parses it with JSON.parse, which fails on one, then green once it is stripped.

🧪 Behaviour changes

  • None. Same four tools, same 33 rules, same caps, same JSON shapes, same panel: 0.8.1's ls-12
    narrowing is unchanged, and the only difference a scan can show is that 0.8.2 exists. 0.8.1 is
    superseded by this version; deprecating it on npm with a pointer here takes one authenticated
    npm deprecate, which trusted publishing cannot do — it can only publish — so that command is the
    owner's to run.

📦 Package

  • 256 files, 908 kB packed, 2.7 MB unpacked, zero runtime dependencies, Apache-2.0, and
    245 tests in 15 files (244 passed plus one symlink-walk test that skips itself where the host
    forbids creating symlinks).

🇷🇺 Коротко по-русски

Развернуть
  • 0.8.1 уехала с BOM в package.json — невидимой меткой UTF-8. Код тот же, файл выглядит так же, а
    установка падает: хост проверяет манифест через JSON.parse, который на этой метке бросает
    SyntaxError: Unexpected token. require() метку срезает, npm pack на неё не жалуется — поэтому
    тесты и CI были зелёными, а ставилось ничего.
  • В 0.8.2 метка убрана; опубликованный пакет на месте не правится, поэтому это новая версия, а 0.8.1
    этой версией перекрыта. Пометить её как deprecated на npm со ссылкой сюда — одна команда
    npm deprecate с вашим логином: у trusted publishing есть право только на публикацию.
  • Добавлен тест, который читает первые три байта у всего, что хост парсит (package.json, index.js,
    client.js, три словаря локалей и два JSON корпуса) и требует, чтобы это не были EF BB BF.
    Проверено возвратом метки: тест краснеет с текстом про BOM, после снятия — зелёный.
  • Поведение не меняется: те же четыре инструмента, те же 33 правила, те же капы и формы JSON.
  • Пакет: 256 файлов, 908 kB в упаковке, 2.7 MB распакованного, 245 тестов в 15 файлах,
    зависимостей по-прежнему ноль.

🇨🇳 中文摘要

展开
  • 0.8.1 的 package.json 带上了 UTF-8 BOM。代码没变、文件看起来也没变,但安装会失败:宿主用
    JSON.parse 校验清单,遇到这个标记直接抛 SyntaxError: Unexpected token;而 require() 会去掉它、
    npm pack 也不报错,所以测试与 CI 全是绿的,装上却用不了。
  • 0.8.2 去掉该标记;已发布的包无法就地修正,因此发新版本,由 0.8.2 取代 0.8.1。要在 npm 上把它标为
    deprecated 并指向这里,需要一次带登录的 npm deprecate —— trusted publishing 只有发布权限。
  • 新增测试读取宿主会解析的每个文件的前三个字节(package.json、index.js、client.js、三个语言词典
    与两个语料 JSON),要求不是 EF BB BF。把标记放回去验证过:测试变红并给出 BOM 说明,去掉后恢复绿色。
  • 行为没有变化:仍是四个工具、33 条规则、同样的上限与 JSON 结构。
  • 打包:256 个文件、908 kB、解包 2.7 MB,245 个测试 / 15 个文件,运行时依赖仍为零。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.8.1...v0.8.2

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 10 Nov 21:47

0.8.1 — the scanner stops giving advice that breaks a clamp 🧷

A finding is only worth its fix if the fix is safe. ls-12, the 2009-prefixed-flexbox marker, also fired on
the standard clamp block, where "use unprefixed display: flex" deletes the truncation the block exists for.
It now flags only the spellings a clamp cannot serve — same 33 rules, same caps, nothing to migrate.

🛠 Hardening

  • The rule no longer fires on the line-clamp idiom. This block is not the 2009 flexbox:

    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;

    display: -webkit-box is the block container -webkit-line-clamp needs, and
    -webkit-box-orient: vertical is how the clamp stacks its lines, so a display: flex rewrite would break
    both. ls-12 reports neither line now. On the live bench project the wrong finding at
    src/components/ui/PostCard.astro:110-112 is gone: that scan went from 10 findings to 9, with ls-12
    from 1 to 0.

  • What it still flags cannot be a clamp. display: -webkit-flex, display: -webkit-inline-flex,
    -webkit-box-pack, -webkit-box-align, -webkit-box-flex, -webkit-box-ordinal-group,
    -webkit-box-direction, and -webkit-box-orient: horizontal — each of those is prefixed flexbox with no
    clamp reading, so the advice to use unprefixed display: flex and gap stays correct there. Same rule id,
    same guide (css-layout), same high confidence, same message text.

  • The cost, stated rather than buried. Precision was bought with recall: a page that genuinely uses a bare
    display: -webkit-box as flexbox is now unreported. A rule is one line regex and cannot see the
    -webkit-line-clamp line above the marker, so it cannot tell the idiom from the old spelling, and we chose
    silence on the two spellings the idiom owns. The boundary and its reasoning are recorded in
    docs/notes/legacy-scan-parity.md.

🧪 Behaviour changes

  • This repository still reports 85 findings, and that is a coincidence, not a no-op. The composition
    moved: test/fixtures/scan/dirty/styles.css was retargeted from display: -webkit-box to
    display: -webkit-flex so the fixture keeps carrying a true ls-12 marker (the fixture set still returns
    3 files and 8 findings, one of them ls-12), a bare -webkit-box inside a test comment stops matching, and
    eight prefix spellings added to a test collapse — under 0.8.0's run rule — into a single finding. ls-12
    still accounts for 3 findings here, at different places. Re-run the scan and expect the same total with
    a different report.
  • Nothing else in a report moves. Same 33 rules, same 18 code extensions, same SCAN_LIMITS, same
    ordering, same lines and occurrences fields and the same JSON shape: a diff of two scans differs only in
    ls-12 entries.

📝 Skills and docs

  • .scanignore now says it may park a single file (issue #10): a pattern can name one file rather than a
    directory, and that file is skipped while its siblings are read — measured on a workspace of three pages
    with one named, 2 files and 2 findings with one scanignore skip. Naming the parked path explicitly still
    returns 0 files and the same skip: the ignore wins over an explicit path, deliberately, because the file may
    only ever make a scan read less. The contract and the glossary both say so now.
  • The rule's boundary is written down, so whoever widens ls-12 next meets the reason it was narrowed
    before changing it: docs/notes/legacy-scan-parity.md.

📦 Package

  • 256 files, 907 kB packed, 2.7 MB unpacked (measured by npm pack --dry-run) — no file
    added or removed, still zero runtime dependencies, Apache-2.0, and 244 tests in 15 files (243 passed
    plus one symlink-walk test that skips itself where the host forbids creating symlinks).

🇷🇺 Коротко по-русски

Развернуть
  • ls-12 больше не срабатывает на идиому line-clamp: display: -webkit-box + -webkit-line-clamp +
    -webkit-box-orient: vertical — это не флексбокс 2009 года, и совет «перейди на display: flex» ломает
    обрезку, ради которой блок и написан.
  • Остаётся то, что clamp-у служить не может: display: -webkit-flex, display: -webkit-inline-flex,
    -webkit-box-pack|align|flex|ordinal-group|direction и -webkit-box-orient: horizontal; id правила,
    гайд, уверенность и текст сообщения не менялись.
  • Цена названа прямо: страница, где голый display: -webkit-box действительно используется как флексбокс,
    теперь не репортится — это точность за счёт полноты, и граница записана в parity-ноте.
  • Bench-проект src: 10 находок → 9, ls-12 1 → 0, ложная находка PostCard.astro:110-112 ушла;
    остальные правила не сдвинулись.
  • В этом репозитории по-прежнему 85 находок, и это совпадение, а не «ничего не изменилось»: фикстура
    dirty перенацелена на display: -webkit-flex, голый -webkit-box в комментарии теста больше не
    матчится, восемь новых написаний в тесте схлопнулись в одну находку; ls-12 здесь по-прежнему 3, но в
    других местах.
  • .scanignore в контракте и глоссарии теперь говорит, что паттерн может называть один файл (issue #10):
    соседние страницы читаются, а явно названный припаркованный путь даёт 0 файлов — игнор сильнее явного
    пути, и это осознанно.
  • Пакет: 256 файлов, 907 kB в упаковке, 2.7 MB распакованного, 244 теста в 15 файлах,
    зависимостей по-прежнему ноль.

🇨🇳 中文摘要

展开
  • ls-12 不再命中 line-clamp 写法:display: -webkit-box + -webkit-line-clamp +
    -webkit-box-orient: vertical 不是 2009 年的弹性盒,建议改成 display: flex 会破坏这段代码的本意——
    截断。
  • 仍然命中的是无法用于 clamp 的前缀写法:display: -webkit-flex、display: -webkit-inline-flex、
    -webkit-box-pack|align|flex|ordinal-group|direction 与 -webkit-box-orient: horizontal;规则 id、
    指南、置信度与文案均未改动。
  • 代价已写明:确实把裸 display: -webkit-box 当作弹性盒的页面现在不再报告——用召回率换精确度,边界记录在
    parity 笔记中。
  • bench 项目 src:10 条发现 → 9 条,ls-12 1 → 0,PostCard.astro:110-112 的错误发现消失,
    其它规则没有变化。
  • 本仓库仍然是 85 条发现,但这是巧合,不是「什么都没变」:dirty 夹具改为 display: -webkit-flex,
    测试注释里的裸 -webkit-box 不再命中,新测试里的八种写法合并为一条发现;ls-12 仍是 3 条,只是位置
    不同。
  • .scanignore 在契约与术语表中写明可以只停一个文件(issue #10):同目录其它页面照常读取,而显式点名
    被停的文件返回 0 个文件——忽略优先于显式路径,这是有意为之。
  • 打包:256 个文件、907 kB、解包 2.7 MB,244 个测试 / 15 个文件,运行时依赖仍为零。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.8.0...v0.8.1

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 10 Nov 19:45

0.8.0 — one ladder, one finding 🪜

A block of twenty legacy lines used to be twenty findings, because the scanner counted hits per line. It
now counts code sites: a run of one rule's hits on neighbouring lines is one finding, and every surface
prints the range it stands for. Same rules, same caps, same zero dependencies — and smaller numbers, which
are not the same thing as a cleaner project.

✨ Features

  • A finding is one place in the code. Hits of one rule in one file collapse into a single finding while
    the next hit's line is at most RUN_LINE_GAP = 2 lines after the previous one — the next line, or one
    blank line later. A wider gap, a different file or a different rule starts a new finding.

  • Every finding carries lines and occurrences, always. A single hit is lines: [154] with
    occurrences: 1, so a consumer never needs a branch for the new fields, and occurrences is also
    lines.length.

  • The scan text prints a range instead of a line. A single hit still renders path:line:column byte for
    byte; a run renders path:first-last, with (N hits) only when the run skips a line:

    - src/components/Header.astro:642:5 ls-09 (medium)
    - src/components/ui/PostCard.astro:110-112 (2 hits) ls-12 (high)
    - src/styles/report-template.css:306-313 ls-05 (medium)
    

    110-112 alone would claim three hits where there are two, hence the count; a dense run needs none.

  • The panel's chat row shows the same range through one helper: path:110-112 for a run and
    path:2-4 (2) when it skips a line, falling back to path:line when an older host sends only line.

🧪 Behaviour changes

  • Counts fall, and that is collapse, not repair. The bench project's src (58 files) goes from 22
    findings to 10
    , with ls-05 from 13 to 2: the eight-hit and five-hit ladders in
    report-template.css are now two findings. This repository goes from 97 to 85. 22 line references
    existed before and 22 exist after
    — nothing was lost, it was grouped. The 97 was never a quality metric
    (most of it is this repository's own rule table storing markers as data), and 85 is not "twelve problems
    fixed": a total that drops after upgrading says the counting changed, not the code.
  • maxFindings counts findings, not hits. A 40-line ladder now costs one of the 200 slots instead of
    forty, so the same caps admit far more code. SCAN_LIMITS was not touched: nothing was widened, the unit
    changed.
  • Ordering follows the site: path, then first line, then column, then rule id — a collapsed run sorts
    by where it starts, like any other finding.
  • guides counts findings too, so the per-guide table stays consistent with the list above it.

🧩 Compatibility

  • The two new fields are additive: line, column, snippet and message keep their meaning and now
    describe the first hit of the site, so code that printed path:line:column keeps printing something
    true. Nothing to change to keep working; use lines when you want the range.
  • Rule messages were not rewritten to mention ranges — the range belongs to the finding, so a rule's text
    stays the one sentence it always was.
  • The panel reads lines and occurrences defensively and falls back to line, so a client talking to an
    older host renders exactly what it used to.

📦 Package

  • 256 files, 906 kB packed, 2.7 MB unpacked (measured by npm pack --dry-run) — no file
    added or removed, still zero runtime dependencies, Apache-2.0, and 242 tests in 15 files (241
    passed plus one symlink-walk test that skips itself where the host forbids creating symlinks).

🇷🇺 Коротко по-русски

Развернуть
  • Находка теперь — одно место в коде, а не одна строка: серия попаданий одного правила на соседних
    строках схлопывается в одну находку, и все поверхности печатают диапазон.
  • У каждой находки всегда есть lines и occurrences: у одиночного попадания lines: [154],
    occurrences: 1, так что потребителю не нужна ветка для нового поля.
  • Текст печатает path:642:5 для одиночного хита и path:110-112 для серии; (2 hits) появляется только
    там, где серия пропускает строку, иначе 110-112 соврало бы про три хита вместо двух.
  • Числа падают: bench-проект src — 22 находки → 10, ls-05 — 13 → 2; этот репозиторий —
    97 → 85. Это схлопывание, а не починка: 22 ссылки на строки были и остались 22, а меньшее число
    после обновления говорит о смене подсчёта, а не об очистке кода.
  • maxFindings считает находки, а не хиты: лестница из 40 строк занимает один слот из 200. SCAN_LIMITS
    не меняли — изменилась единица измерения.
  • Порядок — путь, первая строка, колонка, id правила; строка в чате показывает диапазон через тот же
    хелпер и терпит старый хост, у которого есть только line.

🇨🇳 中文摘要

展开
  • 一条发现现在是一处代码位置,不是一行:同一规则在相邻行上的连续命中会合并成一条发现,各处文本都改为
    打印范围。
  • 每条发现都带 lines 与 occurrences:单次命中即 lines: [154]、occurrences: 1,调用方不必为
    新字段写分支。
  • 文本对单次命中仍打印 path:642:5,对连续命中打印 path:110-112;只有跨过一行时才追加 (2 hits),
    否则 110-112 会把两次命中说成三次。
  • 数字会下降:bench 项目的 src 从 22 条变成 10 条,ls-05 从 13 变成 2;本仓库从 97 变成
    85
    。这是合并,不是修复:行号引用更新前是 22 处,更新后仍是 22 处;升级后数字变小说明计数方式
    变了,不代表代码变干净。
  • maxFindings 数的是发现而不是命中:40 行的连片命中只占 200 个名额中的一个;SCAN_LIMITS 未作改动,
    改的是计数单位。
  • 排序为路径、首行、列、规则 id;聊天里的那一行通过同一个辅助函数显示范围,并在老宿主只给 line 时保持
    原有渲染。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.7.3...v0.8.0

v0.7.3

Choose a tag to compare

@github-actions github-actions released this 10 Nov 18:36

0.7.3 — the panel stops quoting a scan without its caveats 🧾

0.7.2 taught the scan's own text to say what it skipped. The panel draws its summary from the same JSON,
and it was still printing a count with no word about the paths the walk never read: a live run showed
Просканировано 70 файлов, 97 замечаний and nothing else. Both caveats are now on screen, on all three
surfaces the panel owns. Same tool, same arguments, same caps, no new dependency.

✨ Features

  • The button's answer says what it did not read. Under Scanned 70 files, 97 findings the panel now
    prints Skipped 280 (278 extension, 2 dot-directory), naming the top three reasons and adding +N more
    inside the parentheses when a scan skipped for more — and nothing at all when nothing was skipped.
  • The last-scan line carries the same two facts, shorter. Last scan: 2026-11-10 · 70 files · 97 findings · skipped 280 · truncated — the total and the flag only, never the breakdown.
  • The chat tool row adds the skip line under its table, for a JSON and a summary answer: the
    summary format prints Skipped 280 path(s): 278 extension, 2 dot-directory as prose and the row parses
    that line back, so one reader serves both formats and no second implementation appears.
  • Truncation reuses the 0.7.0 string — Truncated: the scan hit a cap, so this list is incomplete. —
    because a second wording for the same fact is a second thing to keep in sync. There is no fifth key.

The counts differ between workspaces, and that is the point of them: a live run of this repository from
the GUI reported 280 skipped paths, a clean checkout reports 279, and neither is wrong — a scan counts
what is in the directory when it runs, so a .tmp scratch file or a harness service file is one more
skipped path like any other.

🧪 Behaviour changes

  • lastScan grows from four keys to six — { at, files, findings, rules, skipped, truncated }.
    skipped holds the top four reasons (count descending, then name), {} when nothing was skipped;
    truncated is true when the run stopped at any cap. version stays 1: the fields are additive.
  • The printed sum is the sum of the four reasons the state kept. A scan that skipped for five or more
    reasons prints fewer than its own skippedTotal, because the state document is a summary: keeping all
    eight reason rows would make every state file carry a scan's whole skip table. That is the contract, not
    a rounding bug — the fresh scan result, the button, the chat row and the tool's own text still carry every
    reason and its own total. The long-form contract is in
    docs/notes/legacy-scan-contract.md.
  • A stale stats.json upgrades silently. A 0.7.2 file has neither field, so the first report renders
    the shorter line — no skipped 0, no invented truncated — and the next completed scan rewrites the
    document with six keys. No migration step, no version bump, nothing to run by hand.
  • A refusal still leaves lastScan alone, on the tool and on the command alike: an error is counted
    as an error call, and the stored summary keeps describing the last scan that actually finished.

🧩 Compatibility

  • Reason tokens are never translated, on any surface: dot-directory stays dot-directory in EN, RU
    and ZH, exactly as a rule id stays ls-13. They are values a reader matches against a scan.
  • Still one hand-written client file, still no host method and no new dependency; the data was already in
    the answer the panel received. Nothing to enable or configure.
  • No path and no snippet appears anywhere new: the three surfaces show counts and reason names only, and
    the chat table remains the one place where files and lines appear.

📦 Package

  • 256 files, 905 kB packed, 2.6 MB unpacked (measured by npm pack --dry-run) — no file added or
    removed, still zero runtime dependencies, Apache-2.0, and 234 tests in 15 files (233 passed plus
    one symlink-walk test that skips itself where the host forbids creating symlinks).

🇷🇺 Коротко по-русски

Развернуть
  • Панель больше не показывает числа скана без оговорок: под строкой итога появилось
    Пропущено 280 (278 extension, 2 dot-directory) — до трёх причин, дальше +N ещё, а если пропусков не
    было, строки нет вовсе.
  • Строка последнего скана тоже честная: Последнее сканирование: 2026-11-10 · 70 файлов · 97 замечаний · пропущено 280 · обрезано — только сумма и флаг, без разбивки.
  • Строка в чате под таблицей показывает те же пропуски и для JSON, и для summary: текст
    Skipped 280 path(s): … разбирается обратно тем же читателем, что и таблица.
  • В stats.json у lastScan теперь шесть ключей (at, files, findings, rules, skipped, truncated),
    версия состояния остаётся 1: skipped — верхние четыре причины, truncated — правда, если скан
    упёрся в любой кап.
  • Поэтому печатаемая сумма — это сумма сохранённых четырёх причин: при пяти и более причинах она меньше
    skippedTotal самого скана. Это контракт, а не баг: свежий результат скана по-прежнему знает все причины.
  • Старый stats.json обновляется молча: без полей он читается как {}/false, строка просто короче, а
    следующий завершённый скан перезаписывает файл шестью ключами. Токены причин не переводятся нигде — как ls-13.

🇨🇳 中文摘要

展开
  • 面板不再只报数字不报前提:结果下方新增 已跳过 280 个(278 extension, 2 dot-directory) —— 最多列三个
    原因,其余用 另有 N 项,没有跳过时整行不出现。
  • 最近一次扫描那一行同样诚实:最近一次扫描:2026-11-10 · 70 个文件 · 97 条发现 · 跳过 280 · 已截断 ——
    只给总数和截断标记,不给分解。
  • 聊天里的工具卡片在表格下方补上同一行,JSON 与 summary 两种答案都有:summary 文本中的
    Skipped 280 path(s): … 会被同一个读取器反解回来。
  • stats.json 里的 lastScan 从四个键变成六个(at, files, findings, rules, skipped, truncated),状态
    版本仍是 1:skipped 只保留前四个原因,truncated 表示扫描是否触到任一上限。
  • 因此打印出来的总数是「保存下来的四个原因」之和:原因达到五个以上时,它会小于扫描自身的 skippedTotal。
    这是契约而不是缺陷 —— 最新一次扫描的结果仍然知道全部原因。
  • 旧的 stats.json 会静默升级:缺少字段时按 {}/false 处理,那一行只是更短;下一次完成的扫描会用六个键
    重写文件。原因名在任何地方都不翻译,就像规则 id。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.7.2...v0.7.3

v0.7.2

Choose a tag to compare

@github-actions github-actions released this 10 Nov 18:18

0.7.2 — the scanner learns which directories are none of its business 🚧

Dot-directories are no longer read, and a workspace-root .scanignore says which directories and files to
leave out. The scan text now itemises what it skipped and why — Skipped 279 path(s): 277 extension, 2 dot-directory — and a malformed or unreadable .scanignore is reported instead of failing the scan. The
tool, its arguments, its caps and its refusal codes are unchanged.

🧪 Behaviour changes

  • Dot-directories are never walked. A directory whose name starts with a dot is skipped with reason
    dot-directory — the usual noise names (.git, .turbo, .next, .cache) were already in the
    scanner's own skip list, so this now covers .github and .vscode too. The check runs whether the walk
    meets the directory or a path names it explicitly, and it costs one skip entry per directory, not one
    per file inside, so build output and scraped dumps stop inflating a report.
  • .scanignore at the workspace root. One pattern per line, # for comments, one-segment patterns
    matching a name at any depth (report/), patterns containing / anchored at the root (src/app.js),
    * inside one segment, a trailing / for directories only. No negation, no **, no ?. A line
    outside that subset is reported, not fatal: Ignored 1 line in .scanignore (line 7: bad pattern), and an
    unreadable file produces Could not read .scanignore; the scan ran without ignore patterns.
  • Skips are itemised per reason. Right after Scanned N file(s) … the summary now says
    Skipped 279 path(s): 277 extension, 2 dot-directory — counted over every skip, not the 100 that
    the skipped sample can carry, and ordered by count so the same scan always renders the same line.
  • This repository, measured: . returns 70 files and 97 findings, truncated: false, skips
    {extension: 277, dot-directory: 2}; lib alone returns 6 files and 18 findings. The 97 is still
    mostly the rule table — 61 of them live in lib/scan-rules.js and test/scan-rules.test.mjs, where the
    markers are data — so it measures what the repo contains, not how old its code is.

🛠 Hardening

  • A scan can still be truncated, and that is the honest answer. A real Astro project with 58 source
    files scans src as 58 files, 22 findings before and after this release; scanned as . it hits the
    200-file cap with 41 findings and stays truncated: true, because outside its dot-directories it
    holds 241 candidate files (tmp-deploy 82, report 66, src 58, …). Six lines of .scanignore
    naming those artifact directories give 77 files, 22 findings, all of them in src and no truncation.
  • The fix for truncation is .scanignore, not a bigger cap. The cap stays at 200 files precisely so a
    scan of a wrongly-shaped workspace cannot turn into a tarball of every asset the repo ships.

🧩 Compatibility

  • Same tool, same arguments, same caps, same refusal codes; the only text that changed is the new
    Skipped … path(s) line and the two .scanignore diagnostics. scanned.skippedByReason and
    scanned.ignoreProblems are additive fields inside the same result; state files and counters are
    untouched, and a .scanignore diagnostic is never counted as a file skip.
  • .scanignore is not a security boundary. It is a convenience for a project that knows what it means
    by "here", and the scanner is already confined to the workspace by realpath containment — an ignore
    file can only make it read less than it was allowed to.

📦 Package

  • 256 files, 901 kB packed, 2.6 MB unpacked (measured by npm pack --dry-run) — no file added or
    removed, still zero runtime dependencies, Apache-2.0, sigstore provenance from the release workflow.

🇷🇺 Коротко по-русски

Развернуть
  • Сканер больше не заходит в каталоги, чьё имя начинается с точки: такой каталог получает причину
    dot-directory — одной записью на каталог, а не на каждый файл внутри, и это работает и когда путь
    назван явно.
  • В корне рабочей папки читается .scanignore: одна шаблонная строка на строку, # — комментарий,
    шаблон из одного сегмента совпадает с именем на любой глубине (report/), шаблон со / привязан к
    корню, * не переходит через /, слэш на конце — только каталоги.
  • Ошибка в .scanignore не роняет скан: текст говорит Ignored 1 line in .scanignore (line 7: bad pattern),
    а нечитаемый файл — Could not read .scanignore; the scan ran without ignore patterns.
  • Пропуски теперь перечислены по причинам: Skipped 279 path(s): 277 extension, 2 dot-directory, и
    считаются по всем пропускам, а не по 100, которые помещаются в выборку.
  • Этот репозиторий: . даёт 70 файлов и 97 находок, {extension: 277, dot-directory: 2}, lib — 6 и 18.
  • Скан может остаться обрезанным: у реального Astro-проекта 241 подходящий файл при капе в 200, и лечится это
    .scanignore (77 файлов, 22 находки), а не увеличением капа; сам .scanignore — не граница безопасности.

🇨🇳 中文摘要

展开
  • 扫描器不再进入以点开头的目录:这类目录以 dot-directory 为原因记为一条跳过(一个目录一条,而不是
    里面的每个文件一条),即使路径被显式点名也一样。
  • 工作区根目录的 .scanignore 会被读取:一行一个模式,# 是注释,单段模式在任意深度匹配同名项(report/),
    含 / 的模式锚定在根目录,* 不跨 /,结尾 / 只匹配目录。
  • .scanignore 里出错不会中断扫描:文本会说 Ignored 1 line in .scanignore (line 7: bad pattern);
    文件读不到则说 Could not read .scanignore; the scan ran without ignore patterns.
  • 跳过现在按原因列出:Skipped 279 path(s): 277 extension, 2 dot-directory,统计的是全部跳过,
    而不是只能放进样本的 100 条。
  • 本仓库实测:. 得到 70 个文件、97 条发现,{extension: 277, dot-directory: 2};lib 得到 6 个文件、18 条发现。
  • 扫描仍可能被截断:真实的 Astro 项目在点目录外有 241 个候选文件,超过 200 的上限;正确的做法是写 .scanignore
    (77 个文件、22 条发现),而不是调大上限。它也不是安全边界 —— 扫描器本来就被限制在工作区内。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.7.1...v0.7.2

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 10 Nov 17:19

0.7.1 — the scanner stops reading documentation 📖

A .md or .mdx file is no longer scanned: it is reported as skipped, with reason extension, and the
count in skippedTotal includes it. Code is unaffected — the allowlist is 18 source extensions, the caps,
the refusals and the rule table are exactly as they were. The change exists because prose quotes
anti-patterns: a README that says "use dialog.showModal() instead of $.fn.modal" is documentation, not
legacy code, and the panel's scan button was reporting 73 findings on a project whose only defect was its
documentation.

🧪 Behaviour changes

  • Prose is skipped, not read. .md and .mdx left the allowlist, so a Markdown file met while walking
    is skipped with reason extension and counted in skippedTotal; a .md path named explicitly gets
    the same skip instead of an error. A project whose only defect is its documentation now scans clean.
  • A scan of this repository, before and after: 200 files with 73 findings, 71 of them in Markdown
    and truncated: true → 68 files with 82 findings, 0 in Markdown, truncated: false.
  • That rise is not a regression, and the note will not call it one. 61 of the 82 are the rule table
    itself — lib/scan-rules.js (18) and test/scan-rules.test.mjs (43) store every marker as data — 9 are
    deliberate fixtures, and the rest sit in tests and one audit repro. The old 200-file cap was consumed by
    the bundled corpus, so lib/ and test/ were never reached; the scan now covers the code.
  • Counters follow: a Markdown-only project records a clean scan instead of a findings one, with
    findings: 0. No state-file field changed.

🧩 Compatibility

  • Same tool, same arguments, same caps (16 paths, 200 files, 512 kB per file, 8 MB per call, 200 findings),
    same refusal codes, same text. Only the allowlist shrank, so a path that used to produce findings can now
    come back empty — that is the intended answer, not an error.
  • Engines unchanged: DSH >=0.2.0-rc.1 <0.3.0-0, Node >=22.19.0. The panel's scan button needs no change,
    and /guidance-scan still refuses to run without an explicit path.

📦 Package

  • 256 files, 895 kB packed, 2.6 MB unpacked (measured by npm pack --dry-run) — no file added or
    removed, still zero runtime dependencies, Apache-2.0, sigstore provenance from the release workflow.

🇷🇺 Коротко по-русски

Развернуть
  • .md и .mdx больше не сканируются: файл попадает в пропущенные с причиной extension и считается в
    skippedTotal, а явно указанный .md даёт тот же пропуск, а не ошибку.
  • Причина: проза цитирует антипаттерны. README, который говорит «используйте dialog.showModal() вместо
    $.fn.modal», — документация, а не legacy-код; кнопка панели ставила 73 замечания проекту, у которого
    дефект был только в документации.
  • Скан этого репозитория: было 200 файлов и 73 находки (71 в markdown, truncated: true), стало 68 файлов
    и 82 находки, в markdown 0, truncated: false.
  • Рост числа — не регресс: 61 из 82 — сама таблица правил (lib/scan-rules.js и её тест), 9 — фикстуры.
    Прежний лимит в 200 файлов съедал корпус, и до lib/ и test/ обход не доходил.
  • Инструмент, аргументы, лимиты, отказы и текст сканера не изменились — сузился только список расширений.
    Счётчики: скан проекта, где есть только markdown, теперь записывается как clean с findings: 0.
  • Зависимостей по-прежнему ноль, файлов в пакете 256, движки те же.

🇨🇳 中文摘要

展开
  • .md 与 .mdx 不再被扫描:遍历时遇到就以原因 extension 记入跳过并计入 skippedTotal;显式指定的 .md
    路径也是同样的跳过,而不是报错。
  • 原因是散文会引用反模式。README 里写“用 dialog.showModal() 代替 $.fn.modal”属于文档,不是过时代码;
    此前面板的扫描按钮会给一个只有文档有问题的项目报出 73 条发现。
  • 本仓库实测:之前 200 个文件、73 条发现(71 条在 markdown 里,truncated: true);现在 68 个文件、82 条发现,
    markdown 中 0 条,truncated: false。
  • 数字上升不是退化:82 条里有 61 条来自规则表本身(lib/scan-rules.js 及其测试),9 条是刻意的测试夹具。
    旧的 200 文件上限被语料吃掉了,遍历根本到不了 lib/ 和 test/。
  • 工具、参数、上限、拒绝规则和输出文本都没有变,缩小的只是扩展名白名单。计数上,只有文档的项目现在记为
    clean、findings: 0。
  • 依旧零运行时依赖,包内 256 个文件,引擎版本不变。

Install: dsh plugin --profile desktop add dsh-modern-web-guidance

Full diff: v0.7.0...v0.7.1