Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Cowork driver spike.** Added [`docs/contributing/cowork-driver-spike.md`](docs/contributing/cowork-driver-spike.md) — a time-boxed investigation (per §4a of the harness v2 design) of whether `claude-cowork` can be driven programmatically by the empirical compat harness today. Verdict: **not feasible**; cowork is a Claude Desktop app product with no public API/CLI surface. The `claude-cowork` runtime stays on `scripted-assisted` until Anthropic ships a Cowork CLI mode, Sessions API, or documented filesystem-import path. Adjacent finding (not a cowork replacement): the public-beta Skills API (`POST /v1/skills` + `container.skills[]` on `/v1/messages`) supports a fully-automatable *new* runtime — captured in the spike doc as a potential follow-up, gated on a separate design decision.
- **Subscription-only compat harness billing.** The harness now bills a Claude Pro/Max subscription instead of the API: both token-consuming surfaces (the `claude-code` runtime driver and the LLM judge) route through one shared `claude` CLI invoker (`runtimes/shared/claude-cli.ts`) that injects the operator's `CLAUDE_CODE_OAUTH_TOKEN` and deletes every API credential from the child env, so the CLI cannot fall back to API billing. The operator's own token is sourced at preflight — env var if set, otherwise an interactive prompt — so a run only ever spends the operator's personal plan. The judge was migrated off `@anthropic-ai/sdk` (dependency removed) onto the CLI, parsing a strict JSON verdict with one retry instead of the SDK's forced-tool call (`judge-system.md` now asks for a JSON object). `RunMetadata` gains `authMode` and the report methodology discloses subscription auth + parsed-not-forced verdicts. Premise (zero API billing under the OAuth token) still pending the manual smoke test.

### Added

- **First-class local HTML resources (#112).** `.html`/`.htm` files are now discovered, parsed, link- and anchor-validated, checked for well-formedness, and link-rewritten on bundle — using the same `ParseResult` contract and validation framework as markdown. A parse5-backed parser extracts `<a href>` and `<img src>` links plus `id`/`name` fragment anchors; `ResourceRegistry` routes HTML through it and persists optional `anchors`/`parseErrors` on `ResourceMetadata`. Anchor validation now uses a format-neutral fragment index (each file's markdown heading slugs or HTML `id`/`name`, with its case-matching policy carried per entry), enabling cross-format anchor checks (md↔html) with HTML ids matched case-sensitively and markdown slugs case-insensitively. A new `MALFORMED_HTML` code (default `info`) surfaces parser well-formedness diagnostics. On bundle, `<a href>`/`<img src>` values are rewritten by offset-splicing the original source (never re-serialized), so unchanged markup round-trips byte-for-byte and original attribute quoting is preserved (a rewritten value that would be unsafe unquoted is wrapped in quotes). Scope is `<a href>` + `<img src>` only; `<link>`/`<script>`/`<iframe>`/`<source srcset>`/CSS `url(...)` are deferred (asset/machinery references, not the content link graph). `<base href>` is not honored — relative hrefs resolve against the file's own directory (see the breaking note below for the `ResourceMetadataSchema` tightening that shipped with this work).
- **`DUPLICATE_RESOURCE_ID` validation code (default `error`).** When two files resolve to the same resource id after path normalization (e.g. `My Guide.md` and `my-guide.md` both → `my-guide-md`), `vat resources validate` now reports it as an `error` issue naming both files, instead of aborting the entire run with an uncaught `Duplicate resource ID` exception. Documented under [Resource Registry Codes](./docs/validation-codes.md).

### Changed (breaking, pre-1.0)

- **`ResourceMetadataSchema` is now `strict()`.** Shipped with first-class HTML support (#112): the resource-metadata schema rejects unknown top-level fields instead of silently accepting them, so a typo or stale field in code that constructs `ResourceMetadata` now fails at parse time rather than passing through. Move any extra data into a recognized field or drop it.
- **Resource ids now carry a file-extension suffix.** `generateIdFromPath` appends `-<ext>` to every resource id (e.g. `guide.md` → `guide-md`, `guide.html` → `guide-html`, `README.md` → `readme-md`). This makes a markdown file and a same-stem HTML file distinct resources instead of colliding — the prerequisite for first-class HTML resources sharing a directory with their markdown source. Resource ids are internal, path-derived identifiers (never hand-authored in config or frontmatter), but anything that referenced an id by its old bare form must use the suffixed form — most visibly `vat rag query --resource-id` filters and re-indexed chunk ids (re-index to regenerate).
- **`vat resources validate` gains per-code severity configuration, and external-URL findings no longer fail the build by default.** Resource findings now use the same configurable severity framework as `vat skills`: each is a documented code (e.g. `LINK_BROKEN_FILE`, `EXTERNAL_URL_DEAD`) with a default severity, overridable per project under `resources.validation.severity` / `resources.validation.allow`. External-URL findings now default to `warning` and no longer flip the exit code (fixing a bug where they always failed the command); set their severity to `error` to restore failing. Severity now also accepts an `info` level. The never-implemented `resources.validation.checkLinks`/`checkAnchors`/`allowExternal` keys are removed.
- **`validation.severity` / `validation.allow` keys are validated against real codes.** A mistyped code key (e.g. `LNIK_OUTSIDE_PROJECT`) is now a config-load error instead of a silent no-op.
- **Corpus seed entries now require `bucket`, `confidence`, and `maturity` metadata fields.** `PluginEntrySchema` in `vat corpus scan`'s seed loader gains three required enum fields: `bucket: 'official' | 'community'`, `confidence: 'first-party' | 'curated' | 'listed'`, and `maturity: 'production' | 'experimental' | 'example'`. The bundled `corpus/seed.yaml` is updated; downstream callers running custom seeds must add the fields to every entry. `bucket` is the load-bearing discriminator (`official` entries report named findings; `community` entries are aggregate-only in follow-up work). The other two are descriptive metadata used by triage tooling.
Expand All @@ -27,6 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Transformers.js integration tests now skip on Windows CI instead of flaking.** `transformers-embedding-provider.integration.test.ts` and the Transformers.js block of `comparison.integration.test.ts` skip on Windows (in addition to skipping when the optional `@xenova/transformers` dependency is absent), matching the existing `onnx-embedding-provider` test. These tests download a model over the network and load the `onnxruntime-node` native backend — both flaky in Windows CI. Such a failure was previously mislabeled `@xenova/transformers is not installed` by an over-broad `catch` in the provider's `loadPipeline` (the package was installed; the model download/inference is what failed), which is also why an availability-only guard did not prevent it.
- **Config-first skill discovery now honors `..` in `skills.include` patterns.** `vat build`, `vat verify`, and `vat skills validate` all funnel through `discoverSkillsFromConfig`, which previously passed every include pattern to a single downward-only crawl rooted at `projectRoot` — so an include like `"../../docs/skills/*/SKILL.md"` (common in monorepos where SKILL.md sources live alongside, not inside, the package) silently matched zero skills. `vat audit` accepted the same config only because it has a separate filesystem-first walker. Each include pattern is now split into a literal base + glob remainder via `picomatch.scan`, patterns are grouped by their resolved absolute base, and the crawler runs once per base — making config-first discovery agree with audit. User-supplied excludes stay anchored to `projectRoot` so patterns like `docs/private/**` keep their original meaning, and a pattern resolving to a nonexistent base now silently produces zero matches.
- **Anchor validation no longer reports a false `LINK_BROKEN_ANCHOR` for un-indexed target files (#112).** Previously a fragment link to any file the resource registry had not parsed (e.g. a target outside the crawl) was reported as a broken anchor. Anchor checks now skip targets absent from the fragment index — affecting markdown and HTML alike — while genuinely missing fragments in indexed files are still reported.
- **`vat resources validate` no longer crashes on same-stem `.md` + `.html` sibling files (#116).** Making HTML first-class added `.html`/`.htm` to the crawl, and same-stem siblings (e.g. `index.md` + `index.html`) previously produced an uncaught `Duplicate resource ID` exception that aborted the whole command. Fixed by the extension-suffixed ids above (siblings now get distinct ids), with `DUPLICATE_RESOURCE_ID` as a graceful backstop for any genuine post-normalization collision.
- **Post-build link checks now cover bundled HTML (#116).** `checkBrokenPackagedLinks` and the unreferenced-file check previously scanned only `.md`, so a broken `<a href>`/`<img src>` inside a packaged `.html`/`.htm` file shipped with a green build. Both checks — and the reachability traversal — now extract HTML links via the same parser, so broken links in packaged HTML surface as `PACKAGED_BROKEN_LINK` (failing the build) and an HTML file referenced only by other HTML is no longer falsely flagged `PACKAGED_UNREFERENCED_FILE`.

## [0.1.38] - 2026-05-18

Expand Down
7 changes: 4 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions docs/validation-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ Static-analysis codes that fire anywhere markdown is analyzed — `vat resources
- **Why it matters:** A committed document declaring a dependency on a gitignored target breaks portability — anyone cloning the repo gets the document but not the target. It also risks treating local-only or generated content as if it were part of the published artifact. Distinct from the skills-packaging code [`LINK_TO_GITIGNORED_FILE`](#link_to_gitignored_file), which guards against leaking ignored data into a *bundle*; this code fires in the `vat resources validate` path and the two coexist intentionally.
- **Fix:** Link a tracked target, or un-ignore the file in `.gitignore` if it should be committed.

## HTML Well-Formedness Codes

Unlike the link codes above, this code is HTML-specific and is **not** a link code. It fires only in the `vat resources validate` path — emitted by `ResourceRegistry` while parsing `.html`/`.htm` resources — and does not run under `vat skills validate`, `vat skills build`, or `vat audit`.

### `MALFORMED_HTML`

- **Default:** `info`
- **What:** An HTML resource has well-formedness problems (unclosed tags, stray characters, misnested elements) reported by the HTML parser.
- **Why it matters:** Malformed markup parses unpredictably across browsers and tools, and can hide or mangle the links VAT extracts. Surfaced as `info` because browsers are lenient and most pages still render.
- **Fix:** Fix the markup the parser flags. Raise severity via `validation.severity.MALFORMED_HTML` to enforce well-formedness.

## Frontmatter Link Codes

Validation codes that fire when a collection's frontmatter schema declares a URI-family `format` (`uri-reference`, `uri`, `iri-reference`, `iri`) on a field and `vat resources validate` walks those values through the same engine as markdown link checking. Disabled per-collection via `validation.checkFrontmatterLinks: false` or globally via `vat resources validate --no-check-frontmatter-links`. See [Frontmatter link validation](./guides/collection-validation.md#frontmatter-link-validation).
Expand Down Expand Up @@ -229,6 +240,17 @@ Only meaningful when actually bundling a skill; fire from `vat skills build` (an
- **Why it matters:** This code indicates VAT's own link rewriter produced an inconsistent bundle — a file was expected but wasn't written to the output. Unlike `LINK_MISSING_TARGET`, which flags source issues, this flags a post-build integrity failure.
- **Fix:** Report the issue — this indicates a VAT bug. As a temporary workaround, set `severity.PACKAGED_BROKEN_LINK` to `ignore` while the underlying bug is fixed.

## Resource Registry Codes

*Fire when building the resource registry — `vat resources validate` and any command that crawls resources.*

### `DUPLICATE_RESOURCE_ID`

- **Default:** `error`
- **What:** Two files resolve to the same resource id after path normalization (e.g. `My Guide.md` and `my-guide.md` both produce `my-guide-md`).
- **Why it matters:** Resource ids must be unique — a collision means one file silently shadows the other in lookups, link resolution, and bundling. This surfaces as a reported issue rather than aborting the whole run with an uncaught error.
- **Fix:** Rename one of the files so they produce distinct resource ids.

## Quality Codes

*Stance: see [Length and Shape](./skill-quality-and-compatibility.md#length-and-shape) and [Authoring](./skill-quality-and-compatibility.md#authoring).*
Expand Down
4 changes: 4 additions & 0 deletions packages/agent-schema/schemas/validation-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"LINK_DROPPED_BY_DEPTH",
"PACKAGED_UNREFERENCED_FILE",
"PACKAGED_BROKEN_LINK",
"DUPLICATE_RESOURCE_ID",
"SKILL_LENGTH_EXCEEDS_RECOMMENDED",
"SKILL_TOTAL_SIZE_LARGE",
"SKILL_TOO_MANY_FILES",
Expand Down Expand Up @@ -65,6 +66,7 @@
"LINK_BROKEN_ANCHOR",
"LINK_UNKNOWN",
"LINK_TO_GITIGNORED",
"MALFORMED_HTML",
"FRONTMATTER_MISSING",
"FRONTMATTER_INVALID_YAML",
"FRONTMATTER_SCHEMA_ERROR",
Expand Down Expand Up @@ -121,6 +123,7 @@
"LINK_DROPPED_BY_DEPTH",
"PACKAGED_UNREFERENCED_FILE",
"PACKAGED_BROKEN_LINK",
"DUPLICATE_RESOURCE_ID",
"SKILL_LENGTH_EXCEEDS_RECOMMENDED",
"SKILL_TOTAL_SIZE_LARGE",
"SKILL_TOO_MANY_FILES",
Expand Down Expand Up @@ -160,6 +163,7 @@
"LINK_BROKEN_ANCHOR",
"LINK_UNKNOWN",
"LINK_TO_GITIGNORED",
"MALFORMED_HTML",
"FRONTMATTER_MISSING",
"FRONTMATTER_INVALID_YAML",
"FRONTMATTER_SCHEMA_ERROR",
Expand Down
12 changes: 12 additions & 0 deletions packages/agent-schema/src/validation-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const CODE_REGISTRY = {
'Report the issue — this indicates a VAT bug. As a temporary workaround, set severity.PACKAGED_BROKEN_LINK to ignore while the underlying bug is fixed.',
'packaged_broken_link',
),
DUPLICATE_RESOURCE_ID: entry(
'error',
'Two files resolve to the same resource id after path normalization.',
'Rename one of the files so they produce distinct resource ids.',
'duplicate_resource_id',
),
SKILL_LENGTH_EXCEEDS_RECOMMENDED: entry(
'warning',
'SKILL.md line count exceeds the recommended limit; longer files degrade skill triggering.',
Expand Down Expand Up @@ -326,6 +332,12 @@ export const CODE_REGISTRY = {
'Link a tracked target or un-ignore it.',
'link_to_gitignored',
),
MALFORMED_HTML: entry(
'info',
'HTML resource has well-formedness issues reported by the parser.',
'Fix the malformed markup (unclosed tags, stray characters). Informational by default; raise severity via validation.severity to enforce.',
'malformed_html',
),
FRONTMATTER_MISSING: entry(
'error',
'Schema requires frontmatter but the file has none.',
Expand Down
17 changes: 17 additions & 0 deletions packages/agent-schema/test/validation-codes-html.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';

import { CODE_REGISTRY } from '../src/validation-codes.js';
import { createRegistryIssue } from '../src/validation-issue.js';

describe('MALFORMED_HTML code', () => {
it('is registered as info', () => {
expect(CODE_REGISTRY.MALFORMED_HTML.defaultSeverity).toBe('info');
expect(CODE_REGISTRY.MALFORMED_HTML.reference).toBe('#malformed_html');
});

it('builds an info-severity issue', () => {
const issue = createRegistryIssue('MALFORMED_HTML', 'Malformed HTML: missing-end-tag', { line: 3 });
expect(issue.severity).toBe('info');
expect(issue.line).toBe(3);
});
});
8 changes: 8 additions & 0 deletions packages/agent-schema/test/validation-codes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ describe('CODE_REGISTRY', () => {
expect(CODE_REGISTRY.PACKAGED_BROKEN_LINK.defaultSeverity).toBe('error');
});

it('registers DUPLICATE_RESOURCE_ID as an error-severity overridable code', () => {
expect(CODE_REGISTRY.DUPLICATE_RESOURCE_ID).toBeDefined();
expect(CODE_REGISTRY.DUPLICATE_RESOURCE_ID.defaultSeverity).toBe('error');
expect(CODE_REGISTRY.DUPLICATE_RESOURCE_ID.description.length).toBeGreaterThan(10);
expect(CODE_REGISTRY.DUPLICATE_RESOURCE_ID.fix.length).toBeGreaterThan(10);
expect(CODE_REGISTRY.DUPLICATE_RESOURCE_ID.reference).toBe('#duplicate_resource_id');
});

it('enforces warning defaults for best-practice and meta-codes', () => {
for (const code of [
'SKILL_LENGTH_EXCEEDS_RECOMMENDED',
Expand Down
Loading
Loading