Skip to content

docs(openspec): add Vale as a second static-tier engine - #71

Draft
thecodedrift wants to merge 6 commits into
mainfrom
openspec/add-vale-rule-engine
Draft

docs(openspec): add Vale as a second static-tier engine#71
thecodedrift wants to merge 6 commits into
mainfrom
openspec/add-vale-rule-engine

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Jul 28, 2026

Copy link
Copy Markdown
Member

Draft — not ready for review. Spec only; no implementation yet. Opened to keep the work on the remote.

Rescoped. This change previously carried the layout, the migration, the engine, and the knowledge topic together — 44 tasks across ten groups. Since an OpenSpec change has to land complete in a single PR, that could never merge. The layout and migration moved to #73; what remains here is 22 tasks across 4 groups.

Makes the scaffolded .taskless/vale/ directory actually execute, and adds the knowledge an agent needs to decide when a rule belongs there at all.

Vale runs as a second static-tier engine: resolve its binary, run vale --config .taskless/vale/.vale.ini --output=JSON --no-exit over the target paths, and map findings into the shared CheckResult, stripping the rules. check-name prefix and normalizing severity. Static-tier is a verified property rather than an assumption — Vale's Tengo script sandbox exposes only text/math/fmt, with os/io/exec reporting "module not found", so a Vale rule cannot reach the host. Trust therefore equals ast-grep's, and a subprocess timeout bounds a runaway loop.

Per-rule scoping lives in .vale.ini matchers[<glob>] rules.<name> = YES/NO, where enables union across matchers and a disable wins regardless of order (all verified). Because one rule's scoping can scatter across several matchers, each Taskless-owned matcher carries a tskl)-namespaced breadcrumb naming its owning rule; Vale accepts and silently drops unknown keys, so they ride inside the config rather than in a sidecar. Verify uses per-rule pass//fail/ fixture directories with an isolating .vale.ini generated at verify time, mirroring ast-grep's valid/invalid.

With two static engines, check runs them concurrently and merges into one result set, and an unavailable engine is reported without aborting the others.

The engine-selection topic ships here rather than with the layout, because it names sg/vale/runtime as things an agent can actually choose between — only true once Vale executes. Two constraints are written into the spec because they are the failure modes: engine selection is a different axis from route's authoring destination, and a different axis from trust tier, since sg and vale are both static-tier and only runtime involves login, reconcile, and signing. Ambiguity defaults to sg, stated as a property — the default names an engine known to be available — so it stays correct wherever a route can be withheld or a binary missing.

stack: openspec-archived will be red until the change is implemented and archived. That is expected: archiving now would delete the spec before anything is built against it.

Where this sits

Each change is independently landable: one OpenSpec change, one PR, archived on merge. The archive gate requires it — a tip PR must leave openspec/changes/ empty — so nothing here is stacked and none of these depend on a shared branch.

PR Change Prerequisites
#70 knowledge prompt export none
#72 Vale binary packages none
#73 partition rules by engine none
#71 (this PR) Vale engine + engine-selection topic #73 (executes the vale/ directory it scaffolds) and #72 (needs a binary to resolve)

Only #71 has prerequisites. The other three can land in any order.

#70 and #71 are coupled by exactly one line: whichever lands second adds the engine-selection topic to TOPICS. Ordering between them doesn't matter.

Downstream, the generator's decision router (TSKL-279) needs a published release containing #70 and #71. It consumes a normal release — no prerelease, no path dependency — so it waits without blocking anything here.

Fixes OSS-21

thecodedrift and others added 6 commits July 29, 2026 12:04
…ngine

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LdEhGzeQfSGJM3nKKNj7Bp
The ingest writer hardcodes .taskless/rules, so 0004 would relayout
existing rules under sg/ while the next `rule create` wrote back into
a directory no engine dispatches from.

The API carries no engine discriminator, so an engine-less payload is
ast-grep by definition — the default is permanent, matching what the
migration does to the same rules on disk. An unrecognized engine
fails loudly instead, since filing a Vale rule under sg/ surfaces as
a broken rule rather than version skew.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDv57zHq7abms3RReSQw6q
…solution

Three additions to the Vale change:

The engine-selection knowledge topic ships here rather than as a
follow-on, since it names the engine directories this change creates.
Written engine-shaped, kept distinct from route's authoring
destination and from trust tier — sg and vale are both static-tier,
and conflating those axes is what the platform's binary classifier
got wrong.

Legacy .taskless/rules/ stays dispatchable alongside sg/rules/, so a
producer can keep using the pre-migration layout and its rules keep
running. That decouples this release from any consumer's.

Vale's binary resolves the way findSgBinary already does — resolve the
platform package from our own module context and exec beside it, no
lifecycle script. That code exists because the upstream postinstall
hardlink breaks under pnpm dlx, leaving a placeholder text file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDv57zHq7abms3RReSQw6q
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDv57zHq7abms3RReSQw6q
An OpenSpec change has to land complete in one PR — the archive gate
requires a tip PR to leave openspec/changes/ empty — and this one had
grown to 44 tasks across ten groups. The layout half moved to
partition-rules-by-engine.

What stays is the engine and the knowledge about it: the Vale runner
and its scoping model, fixture verify, findings mapping, concurrent
multi-engine orchestration, and the engine-selection topic. 22 tasks.

The topic stays here rather than with the layout because it names
sg/vale/runtime as things an agent can actually choose between, which
is only true once Vale executes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDv57zHq7abms3RReSQw6q
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDv57zHq7abms3RReSQw6q
@thecodedrift
thecodedrift force-pushed the openspec/add-vale-rule-engine branch from 4f6d941 to ab995da Compare July 29, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR intentionally ships no release note (bypasses the changeset requirement)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant