feat(cli): discover runtime rules under the runtime engine directory - #81
Conversation
f8f600b to
8b73474
Compare
8b73474 to
cfabc37
Compare
On a stack, one changeset describes the whole change and lives on the bottom PR — the one targeting `main`, which is where the check has to pass. Branches above inherit that file, so it is never *added* in a child's own diff and every mid-stack PR failed. Labelling them `skip-changeset` records a deliberate "ships no release note," which is false, so the workflow bypasses on the base ref instead. The `on: branches: [main]` filter does not hold for stacked PRs in practice — the workflow still runs on a PR based on a feature branch — so the base is re-checked inside the step rather than trusted. Observed on #80, #81, and #73, all of which ran and failed the check with a non-main base.
On a stack, one changeset describes the whole change and lives on the bottom PR — the one targeting `main`, which is where the check has to pass. Branches above inherit that file, so it is never *added* in a child's own diff and every mid-stack PR failed. Labelling them `skip-changeset` records a deliberate "ships no release note," which is false, so the workflow bypasses on the base ref instead. The `on: branches: [main]` filter does not hold for stacked PRs in practice — the workflow still runs on a PR based on a feature branch — so the base is re-checked inside the step rather than trusted. Observed on #80, #81, and #73, all of which ran and failed the check with a non-main base.
cfabc37 to
1f2a539
Compare
…laim Review pushed back on three statements, and two of them were wrong. `propagate_stack.py` does return you to the branch you started on — normal completion, rebase conflict, balloon guard, and push failure all check it out before returning. Only a failure to check out a *child* (exit 6, typically that branch being held by a worktree) returns early without it. That is the path that actually bit, so the warning now names it instead of describing the script's normal behavior. The `branches: [main]` claim is reframed rather than dropped. It is documented to filter on the base branch, and the guard should be unreachable — but #73, #80, and #81 each produced a failing `Require a changeset` check run with an `openspec/partition-engine-*` base, and a workflow that never triggers produces no check run at all. The comment now cites that evidence and presents the check as a defensive guard, without teaching that Actions filters are unreliable in general. Also replaces the "structural failures are acceptable" guidance with the opposite rule: a mid-stack PR that is red is incomplete, and the fix belongs in it. That advice let this stack sit on 20 failures that turned out to be two real defects.
…l target `branches: [main]` reads as "only PRs whose base is main," and filtered that way when a PR had one base. Under GitHub's stacked-PR support a stacked PR targets `main` eventually, so the filter matches the eventual target and these workflows run on mid-stack PRs too — which is why #73, #80, and #81 each produced a failing `Require a changeset` run with an `openspec/partition-engine-*` base. That makes the base-ref guard required rather than defensive, and generalizes: a workflow whose correctness depends on "is this the PR that merges to main" has to establish that itself. Corrects the OpenSpec archive-check section too, which told readers a stacked PR would not run that check at all.
On a stack, one changeset describes the whole change and lives on the bottom PR — the one targeting `main`, which is where the check has to pass. Branches above inherit that file, so it is never *added* in a child's own diff and every mid-stack PR failed. Labelling them `skip-changeset` records a deliberate "ships no release note," which is false, so the workflow bypasses on the base ref instead. The `on: branches: [main]` filter does not hold for stacked PRs in practice — the workflow still runs on a PR based on a feature branch — so the base is re-checked inside the step rather than trusted. Observed on #80, #81, and #73, all of which ran and failed the check with a non-main base.
…laim Review pushed back on three statements, and two of them were wrong. `propagate_stack.py` does return you to the branch you started on — normal completion, rebase conflict, balloon guard, and push failure all check it out before returning. Only a failure to check out a *child* (exit 6, typically that branch being held by a worktree) returns early without it. That is the path that actually bit, so the warning now names it instead of describing the script's normal behavior. The `branches: [main]` claim is reframed rather than dropped. It is documented to filter on the base branch, and the guard should be unreachable — but #73, #80, and #81 each produced a failing `Require a changeset` check run with an `openspec/partition-engine-*` base, and a workflow that never triggers produces no check run at all. The comment now cites that evidence and presents the check as a defensive guard, without teaching that Actions filters are unreliable in general. Also replaces the "structural failures are acceptable" guidance with the opposite rule: a mid-stack PR that is red is incomplete, and the fix belongs in it. That advice let this stack sit on 20 failures that turned out to be two real defects.
…l target `branches: [main]` reads as "only PRs whose base is main," and filtered that way when a PR had one base. Under GitHub's stacked-PR support a stacked PR targets `main` eventually, so the filter matches the eventual target and these workflows run on mid-stack PRs too — which is why #73, #80, and #81 each produced a failing `Require a changeset` run with an `openspec/partition-engine-*` base. That makes the base-ref guard required rather than defensive, and generalizes: a workflow whose correctness depends on "is this the PR that merges to main" has to establish that itself. Corrects the OpenSpec archive-check section too, which told readers a stacked PR would not run that check at all.
1f2a539 to
4b16774
Compare
There was a problem hiding this comment.
Pull request overview
Updates runtime rule discovery to follow the engine-partitioned .taskless/ layout by deriving the runtime rules directory from ENGINE_LAYOUTS.runtime, aligning the CLI implementation and tests with migration 0004 without changing runtime rule semantics.
Changes:
- Switch runtime rule discovery to read
.taskless/runtime/rules/viaENGINE_LAYOUTS.runtime.rulesDirectory(and remove the now-redundantfilesystem/layout.tsconstants). - Update/extend CLI tests to assert runtime vs static dispatch based on directory placement and to validate the pre-migration runtime path is not treated as a secondary source.
- Refresh inline/documentation references to the new runtime rules path.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/rules/runtime/discover.ts | Derives runtime rules directory from ENGINE_LAYOUTS.runtime and updates discovery docs/comments accordingly. |
| packages/cli/src/filesystem/layout.ts | Removes old layout constants now superseded by ENGINE_LAYOUTS. |
| packages/cli/src/rules/runtime/run-set.ts | Updates comments to reflect the new live runtime rules location. |
| packages/cli/src/types/runtime-rule.ts | Updates format documentation to the new runtime rules directory. |
| packages/cli/test/runtime-check.test.ts | Updates runtime check path fixtures to .taskless/runtime/rules/.... |
| packages/cli/test/engine-dispatch.test.ts | Adds assertions that directory placement determines runtime vs static discovery, and rejects legacy runtime path. |
| openspec/changes/partition-rules-by-engine/tasks.md | Marks group completion and updates narrative around the group test-failure counts. |
4b16774 to
a57073b
Compare
|
@claude /review |
|
Claude finished @thecodedrift's task in 2m 31s —— View job Review: discover runtime rules under the runtime engine directory
Overall assessment: looks good, no blocking issuesCorrectness
Test adequacy
Clarity
Not checked: build/lint/test execution and CI status — those are reported on the PR itself. Branch: |
a57073b to
5e68a4d
Compare
cc682c6 to
94e71b5
Compare
489d6f9 to
b21b706
Compare
Thanks — no action needed from this one, and the specific check you did (that One thing did change on this branch since your review: the stale note in — AI Coding Agent |
The one inline finding is addressed and resolved. It was flagged as grammar, but the sentence was stale rather than awkward — it documented group 1 leaving 20 tests red, which stopped being true once that group was made self-consistent. Replaced with an accurate note. — AI Coding Agent |
Runtime discovery reads `.taskless/runtime/rules/<name>/`, deriving the path from `ENGINE_LAYOUTS.runtime` so the engine layout stays the single definition rather than a string repeated per module. Migration 0004 moved the tree here byte-for-byte, so signatures and reconciliation are unaffected — only the search path changed. Nothing reads `runtime/rule-tests/` yet; 0004 moves it and the fixtures wait for a consumer. This closes out task 2.3: a rule under `runtime/rules/` now reaches the harness, so directory-based dispatch is asserted for both engines. A runtime-shaped capture filed under `sg/rules/` is covered too — it stays static, because the directory decides. pnpm test is green: 416 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The constants existed so migration 0004's PR could repoint every reader without the engine model. `engines.ts` states the same paths as part of the layout each engine owns, and the last reader (runtime discovery) now reads them from there, so a second definition would only be able to disagree.
…eality The note said group 1 alone leaves 20 tests failing until later groups land. That stopped being true when group 1 was made self-consistent: it now repoints every reader as well as moving the trees, and is green on its own at 396. Copilot flagged the sentence for grammar; the substance was the bigger problem, since the note documented an expected-red state that no longer exists and that turned out to be two real defects rather than a consequence of the split.
This PR became the bottom of the stack when #80 merged, so the release note has to be visible in its own diff. One change gets one changeset: this extends the existing note rather than adding a second entry for something that ships once.
b21b706 to
5546c4c
Compare
Stack (root → tip):
Runtime discovery now reads
.taskless/runtime/rules/, deriving the path fromENGINE_LAYOUTS.runtimerather than repeating the string. Only the search path moves —runtime-check.test.tsandruntime-harness.test.tsseed the engine layout and otherwise pass unmodified, which is the evidence that rule semantics are untouched.This is what closes the loop on directory-based dispatch: a rule under
runtime/rules/can now actually reach the harness, so both engines are asserted end to end. A new test files a runtime-shaped capture undersg/rules/and confirms it stays static — the directory decides, not the rule's shape.Runtime deliberately gets no legacy tolerance. The pre-
0004fallback covers.taskless/rules/only. Since0004moves the runtime tree andcheckmigrates before discovering, a leftoverruntime-rules/is never a second source. A test asserts this, so the asymmetry with ast-grep is visible rather than accidental.Part of a merge-down stack (see the base branch).
Refs OSS-24
Built on top of #79
Migration
0004moves.taskless/to an engine-partitioned layout: ast-grep rules go tosg/rules/andsg/rule-tests/, the runtime tree toruntime/rules/andruntime/rule-tests/, andvale/is scaffolded but inert until the Vale engine lands. Files move without their bytes being touched, so runtime rule signatures survive the migration.Also anchors the
sgconfig.ymlgitignore pattern to.taskless/. The pattern was unanchored, so it would match a same-named file anywhere in the tree.This PR carries the OpenSpec change docs for the whole effort, including the spec deltas the later PRs implement.
This is the bottom of a merge-down stack — the units are only correct together, since this migration relocates rules that nothing yet knows how to find. Nothing here reaches
mainon its own: the PRs above merge down into this branch, and the accumulated result is what merges once. The change directory stays unarchived here by design; the archive gate skips non-tip PRs and the tip archives it.Refs OSS-24
Built on top of #80
The directory a rule sits in under
.taskless/is its engine. Dispatch reads the path and never parses a rule file to decide who owns it, so adding an engine is a directory, not a discriminator.planEngineDispatchresolves which engine directories are present; an unrecognized directory is ignored rather than guessed at, so a.taskless/written by a newer CLI degrades to running the engines this one understands instead of handing a rule to the wrong parser.Two compatibility behaviors are deliberate and worth reviewing as such:
0004.taskless/rules/still runs as ast-grep. An unmigrated checkout, or a producer that keeps naming the old path, executes rather than being silently ignored. Both sources are scanned and findings de-duplicated on the match itself, since a rule id can legitimately exist in only one of them.rules[].contentis documented as an ast-grep definition — so absence means ast-grep, permanently, not for a migration window. An unrecognized engine is the opposite case: it means the payload is newer than the CLI, so it throws and writes nothing rather than filing the rule where the wrong parser will read it.Part of a merge-down stack (see the base branch). Not independently shippable: rules have moved but runtime discovery still reads the old path until the next PR.
Refs OSS-24