4 Commits

Author SHA1 Message Date
zernie f52fa12c4f fix(lint): spec-refs reaches every compiled surface, and compile emits prettier-clean artifacts (#195)
Closes #190.

spec-refs re-derives a compiled file's references from its spec, but
called compileClaude directly and skipped every spec whose _specType was
not "claude". Four of the five compiled surfaces — skills, subagents,
railway orchestrators, generated harness files — got no re-derivation at
lint time, so an artifact committed while its refs were live stayed green
forever after the target was deleted. Measured here: compile reported
`Skill not found` on examples/SKILL.md while lint reported `hash valid`.

Smaller than the issue estimated. #190 expected a per-type ref accessor
because each compiler reaches its refs differently; they do not — all
four already return the same errors: CompileError[], and only the options
differ. One exhaustive dispatcher, so a fifth spec type is a tsc error
rather than a silent skip.

compile also emitted artifacts that prettier --check then rejected, on
all seven examples/ artifacts — which is why they sat un-recompiled
carrying a retired marker position, and which blocked the fix above. Two
independent defects: both frontmatter renderers padded inside the ---
fence, and the frontmatter/body join added a blank line placeIntegrityHeader
then added again. Fixed at the join, not in the stamper: seal hashes the
content it receives, so trimming there hashes one string and writes
another (measured — it broke integrity on three artifacts).

Also: check.mjs now declares and prints the CI jobs it does NOT run, with
a test asserting that list against ci.yml. It covered one job of six
while its name and its "18/18 passed" read like all of them, and that
cost a cycle. The test earned itself immediately — the hand-written list
missed e2e and named two commands that do not exist.

Also: @openai/codex is pinned. latest was 0.153.1, whose tarball 404s
while its metadata still resolves, so the install step failed on every
branch until pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 02:44:22 +05:00
zernie 900dc7e06a refactor(spec)!: name things for what they are; retire the vigiles/experimental subpath (#169)
Three exported symbols were named for something other than what they are, and
two mechanisms were doing one job.

RENAMES (old names kept one major, @deprecated, removed one major AFTER this one):
  claude()          → instructionFile()     compiles CLAUDE.md AND AGENTS.md, so a
                                            name from one harness was never right
  instructions``    → prose``               builds a prose FRAGMENT; the plural read
                                            as "the instruction file", which is what
                                            instructionFile() builds
  agent()           → experimental_agent()  the shape is not settled, and the tag was
                                            never there (git log -S empty)
  SkillSpec.result: → postcondition:        `result` already meant the subagent output
                                            contract one screen down; the doc comment
                                            had to spend a line disambiguating them

The alias window is not politeness. Measured: the previous rename shipped with
ZERO overlap (18.1.1 old names only, 19.0.0 new only). A container rollback put
the old node_modules under new sources, PreToolUse stopped loading, and a
PreToolUse that does not load denies EVERY Bash command — including the one that
fixes it. One window per major makes that pair unrepresentable.

SUBPATH RETIRED. vigiles/experimental is gone; its members moved to the door
their feature already uses. Of two mechanisms for one promise the subpath was the
weaker: it marks the import line, out of view by the time anyone reads the call.
Measured on the only user-facing example — with the prefix aliased away at
import, the marker survived at 0 of 5 call sites. So the prefix stays, the advice
to alias it away became an explicit prohibition, and the six define* entry points
are DECLARED prefixed rather than gaining it at re-export.

That de-aliasing is what unblocked moving the naming check into ESLint, and the
ORDER was load-bearing: measured before it, dropping the public/internal
exemption produced 14 findings, all from the re-export indirection. Measured
after, across all of src/ with no exemption: 22 tagged declarations, 0
unprefixed. The rule turns on silent.

EXPORT CLEANUP. vigiles/linting export * → curated, 103 → 79 symbols; its header
claimed to be curated with an `export *` one line below, and claimed the builders
were also at the package root (191 exports there, zero matches). ClaudeTool and
HookEvent deleted — zero references, and both had drifted off the dialect they
claimed to mirror; HookEvent listed 5 events, two of which do not exist, against
the 31 the vendor documents.

DOCS. experimental.md deleted and distributed per feature: the skill half into
skills.md §Status, the hook half into a new compiled-hooks.md §Status, and the
emit half — 111 of its 177 lines, a topic doc wearing a stability page's clothes
— into docs/emit-channel.md, which gains the "which channel when" comparison it
never had. The one cross-cutting sentence went to the README.

TWO GATES WERE GREEN AND WRONG, both found by this work:
  - api:check reported "verified for 11 entries — no drift" while reading
    dist/experimental.d.ts from a PREVIOUS build (tsc does not clean dist/) for a
    source file that no longer existed. Now asserted against package.json
    exports, with a mutation proving exit 1 on a phantom entry.
  - check-export-prefixes caught MY error: the R3 services first landed on
    vigiles/eval, where `paid_` means "this call can bill you" — and
    startServices takes a ContainerRuntime and calls no model. Naming it
    paid_experimental_startServices would have redefined paid_ as "expensive in
    some sense". They went to the package root.

SEVENTEEN review findings landed on this branch, all real. Two mechanical
closures came out of them: src/example-imports.test.ts resolves every relative
import under examples/ instead of matching a string, and the naming rule now
ENUMERATES the three export paths it visits rather than describing its coverage
in the abstract. One attempt is recorded as REFUTED: teaching the doc gate to
flag our own exports as free variables measured 66 findings across 93 blocks, so
that gate's original scoping was right.

BREAKING CHANGE: vigiles/experimental is removed. Its exports are unchanged in
name and behaviour and are now reached from `vigiles` (the emit channel, the R3
disposable-service tier). Every renamed symbol keeps a working @deprecated alias
for this major.
2026-08-21 07:54:51 +05:00
zernie 34a4f7a556 fix: no raw NUL bytes in source, and a gate that keeps it that way (#160)
src/scan-files.test.ts carried two literal NUL bytes inside a template literal
where the escape was meant. Git decides "binary" per blob by looking for a NUL,
and a binary blob gets no line diff and NO THREE-WAY MERGE — so every branch
touching the file conflicted, unresolvably, because there were no hunks to pick.

Measured: with the bytes escaped, `main <- fix-scan` and `main <- fix-shadow`
each merge with ZERO conflicts. They had been parked for a day as "conflicting
with each other"; they never were.

The escape is the form the rest of the codebase already uses (skill-contract,
scan-core, coverage-artifact, coverage-probe — eight sites), and the string it
produces is byte-identical, so scan-files.test.ts still passes its 21 tests.

Adds scripts/check-text-sources.mjs as an 18th `check` gate. It refuses to pass
on an empty scan, since a scan finding nothing is indistinguishable from a scan
looking in the wrong place. Proven by mutation: planting one raw NUL fails it
with the file, the count and the offset.

Not a .gitattributes marker: that restores diff and merge while leaving the byte
in the source, hiding the defect instead of removing it.


Claude-Session: https://claude.ai/code/session_01Uvk2Zx66BAuxuLGLFL2umd

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-18 22:08:28 +05:00
zernie 1ebef8320a ci: one list of commands instead of two — CI now calls npm run check (#151)
* ci: один список команд вместо двух — CI зовёт `npm run check`

Сегодня три PR уехали в `main` красными: список гейтов гонялся по памяти
(пять команд), а джоб `check` гонял тринадцать. Упавшая —
`tsc -p test/types/tsconfig.json` — живёт в ОТДЕЛЬНОМ tsconfig, которого
корневой тайпчек не видит, и три `@ts-expect-error`-ассерта тихо
перестали срабатывать.

Длинный список, который надо помнить, это не чинит. Чинит то, что список
СТАЛ ОДИН и CI его ВЫЗЫВАЕТ: `scripts/check.mjs` + `run: npm run check`
вместо тринадцати шагов. Это стандартный паттерн поля (pre-commit / just /
mise): определение задач — источник правды, CI его дёргает. Прежняя моя
идея «завести гейт на совпадение двух списков» снята: она была подпоркой
под дубль, который правильнее убрать.

Стадии несущие, а не косметика. Часть команд ПИШЕТ файлы, которые другие
ЧИТАЮТ (`generate types` → `.vigiles/generated.d.ts`, `api-extractor` →
`api-surface/*`, `build` → `dist/`), поэтому писатели сериализованы, а
параллельно идёт только read-only группа. 17/17 за 70 с против ~3 мин
последовательно.

Имена падающих шагов не потеряны — раннер печатает, что именно упало, и
`--list` показывает команды без запуска.

🔴 Два шага `uses: ./` НЕ входят в `npm run check` намеренно: они догфудят
собственный composite Action, у которого нет shell-эквивалента. Один из
них (`eval-check`) при первой правке был снесён случайно — он стоял между
двумя command-шагами, и правка по диапазону его проглотила. Возвращён,
причина записана прямо над ним.

Раннер сразу поймал два собственных промаха: неотформатированный
`check.mjs` и протухший `generated.d.ts` после добавления скрипта `check`.

Проверено: `npm run check` 17/17 ✓ · ci.yml парсится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uvk2Zx66BAuxuLGLFL2umd

* feat!: словарь скилла уехал на сам билдер — experimental_skill.input/.step

BREAKING: `input` и `step` больше не самостоятельные экспорты. Достаются
как `experimental_skill.input(…)` / `.step(…)`. Пользователей нет.

Зачем. Префикс `experimental_` — привычка, а привычка уже один раз
протекла: `skill()` отгрузился со стабильным именем при доке «`skill()`
is experimental». На самих `input`/`step` тега и префикса не было вообще,
то есть словарь экспериментальной фичи выглядел стабильным. Повесив их на
билдер, маркировку делаем СТРУКТУРНОЙ: до `input()` не добраться, не
назвав `experimental_skill`.

Забрано ровно два символа, и это замер, а не вкус. Перепроверено
независимо от вердикта субагента: `input` и `step` встречаются только в
скилл-спеках, а `cmd` (5 агентских спек), `file`, `ref`, `result`
(8 агентских) общие — их спрятать в неймспейс скилла нельзя.

🔴 По дороге закрыта асимметрия, которую правка иначе бы УХУДШИЛА: дверь
`vigiles/claude-code` своих `input`/`step` не экспортировала никогда, и
типизированный автор брал их из второй двери. После снятия standalone-
экспортов он остался бы без них совсем. Теперь CC-билдер несёт те же
хелперы.

Форма — `Object.assign`, не `export namespace`: последний запрещён нашим
же линтом (`no-namespace: error`). Генерик пережил: в api-репорте
`typeof skillSpec & { input; step }`, `ae-forgotten-export` нет.

Честная граница записана и в коде, и в доке: `const { input } =
experimental_skill` снимает маркер внутри файла. Гарантия у́же —
немаркированное имя не пересекает границу пакета.

Доки: `docs/skills.md` — пример на алиасе (`experimental_skill as skill`),
маркер держится на строке импорта. `docs/experimental.md` — вступление
утверждало, что префикс носит всё из `vigiles/experimental`, что после
переезда `experimental_skill` в `vigiles/spec` стало неверным; плюс своя
секция. `docs/README.md` — доиндексированы `testing-matrix.md` и
`non-js-harnesses.md`. `safety.md` намеренно НЕ индексирован: он сам
пишет «INTERNAL / WIP — not surfaced in the public docs yet».

Гейты: npm run check 17/17 ✓ · vitest 2954 ✓.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uvk2Zx66BAuxuLGLFL2umd

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-17 00:30:14 +05:00