docs(openspec): partition rules by engine directory - #73
Draft
thecodedrift wants to merge 6 commits into
Draft
Conversation
This was referenced Jul 28, 2026
Split out of add-vale-rule-engine, which spanned 44 tasks across ten groups — more than one PR should carry, and the archive gate requires a change to land complete in a single PR. This half is the part that carries the risk: the relayout, migration 0004, directory dispatch, ingest defaulting, legacy-path tolerance, and running ast-grep over the committed config. It adds no engine and changes nothing a user observes, so a difference in check output after it lands has exactly one candidate explanation. Vale itself and the engine-selection topic stay in add-vale-rule-engine, which needs this layout to exist first. 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
Move `.taskless/rules/`, `rule-tests/`, and `sgconfig.yml` under `sg/`, and the runtime tier to `runtime/rules/` and `runtime/rule-tests/`. Every move is content-preserving: runtime capture bytes determine their server-side reconciliation hashes, and `sgconfig.yml`'s `ruleDirs: [rules]` is relative to the config file, so nothing needs rewriting. Scaffold an inert `vale/` engine directory with its native `.vale.ini`, and `.gitkeep` every otherwise-empty scaffolded directory. `runMigrations` now throws when `taskless.json`'s version exceeds the highest migration this CLI knows, instructing the user to upgrade, unless the global `--allow-version-mismatches` flag is passed — previously it returned silently and let an older CLI half-read a newer scaffold. This is task group 1 of `partition-rules-by-engine`. The readers (`check`, `verify`, runtime discovery) are updated in groups 2-4; until then 20 tests in check/verify/runtime-check fail because the rules moved out from under them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Group 1 alone leaves 20 tests failing, so no unit of this change is independently shippable: the stack merges down and reaches main as one commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An unanchored gitignore pattern matches at any depth, so the `sgconfig.yml` line migration 0001 wrote also matches `.taskless/sg/sgconfig.yml` — the committed config this layout makes the source of truth would never be tracked. 0001 now writes `/sgconfig.yml`, and 0004 rewrites the unanchored line in existing checkouts, collapsing it with the anchored form when both are present. Belongs to task group 1; kept as its own commit since group 1 was already reviewed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add `rules/engines.ts` as the single place that answers "which engine owns this rule": the top-level `.taskless/<engine>/` directory does, and no rule file is ever parsed to decide. `sg` maps to ast-grep and `runtime` to the harness; `vale` is recognized but has no executor yet, and a directory that is not a known engine is ignored rather than handed to someone else's parser. `check` now calls `ensureTasklessDirectory` itself, keeping the migration trigger that `generateSgConfig` used to provide, then scans each ast-grep source it finds. The legacy `.taskless/rules/` stays readable alongside `sg/rules/`, so a producer that keeps naming the old path still runs; findings from the two are merged and identical matches collapsed. Service-delivered rules are filed by the engine their payload identifies — `sg` when it identifies none, permanently, since the API carries no engine discriminator. An engine this CLI does not know throws before touching the filesystem instead of silently defaulting to ast-grep. The remaining hardcoded `.taskless/rules` literals (verify, files, check, rules, the detect probe) now resolve through the same module, each tolerating both layouts. Reconcile needed no change: reported paths derive from the discovery root and the server joins on content signature, both verified by test. Test failures drop from 20 to 9, all in runtime-check.test.ts (group 3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thecodedrift
force-pushed
the
openspec/partition-rules-by-engine
branch
from
July 30, 2026 17:00
e08424d to
9cf8674
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the on-disk rule layout multi-engine before any second engine exists, and changes nothing a user can observe:
checkreports exactly what it did before, from a different path.Rules partition into
.taskless/<engine>/{config, rules/, rule-tests/}—sg/,vale/(scaffolded empty and inert),runtime/. The containing directory determines the engine, so dispatch never parses a file, and each engine's committed native config becomes the source of truth. Ephemeralsgconfig.ymlgeneration leaves the check path entirely; because that is what used to trigger migrations,checknow callsensureTasklessDirectorydirectly.Migration
0004does the moving, content-preserving throughout:rules/→sg/,runtime-rules/→runtime/rules/.sgconfig.yml'sruleDirs: [rules]is relative to the config, so it survives unedited. Runtime capture bytes are untouched, which matters because those bytes determine reconciliation hashes — editing during the move would invalidate every signature.runMigrationsalso starts throwing on a scaffold newer than the CLI understands, rather than returning silently, unless--allow-version-mismatchesis passed.Two decisions keep this from needing anyone else's cooperation. Ingest defaults to
sg, permanently — the delivery API carries no engine discriminator (verified: noengine,analysisType, orruleTypefield anywhere in the generated types), so an engine-less payload is ast-grep by definition; an unrecognized engine fails loudly rather than defaulting into a directory the wrong parser reads. And both layouts stay readable — legacy.taskless/rules/dispatches as ast-grep alongsidesg/rules/, de-duplicated — so an unmigrated checkout still runs and no producer has to cut over.Why this is separate
Split out of #71, which had grown to 44 tasks across ten groups. Since an OpenSpec change must land complete in one PR, a change that large cannot land at all. This half is where the risk lives — the relayout and the migration — so isolating it means a difference in
checkoutput after merge has exactly one candidate explanation.stack: openspec-archivedwill 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.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-24