Skip to content

feat(OMN-8765): AST-based deterministic-skill routing CI gate - #888

Merged
jonahgabriel merged 11 commits into
mainfrom
jonah/omn-8765-skill-routing-gate
Apr 25, 2026
Merged

jonahgabriel merged 11 commits into
mainfrom
jonah/omn-8765-skill-routing-gate

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Apr 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the W3 hard-gate CI lint check from OMN-8765 (parent epic: OMN‑8737). New job check-deterministic-skills blocks merge if any Tier 1 deterministic skill in omniclaude violates the SkillRoutingError routing contract, using AST analysis (python blocks) and structural token-matching (shell blocks) instead of the line-count heuristics the OMN‑8749 predecessor used.

What the validator enforces per Tier 1 skill

  • Zero LLM SDK imports (anthropic, openai, google.generativeai)
  • No banned LLM API calls (client.messages.create, client.completions.create, ...chat.completions)
  • No subprocess orchestration in python blocks (subprocess.run, subprocess.Popen, os.system, …)
  • No python -c / bash -c / sh -c wrappers around the dispatch
  • No conditional prose-fallback branches after a routing failure
  • At least one node-dispatch declaration (onex run-node / onex node in a shell block or inline-code, OR a Kafka publish to an onex.cmd.* topic). Multi-line backslash continuations are merged before tokenizing.
  • SKILL.md references SkillRoutingError with do not produce prose

See the module docstring for the full amendment-A4 mapping. The ticket's "exactly one dispatch" phrasing is interpreted as "at least one, unambiguous" because Kafka-publish skills declare dispatch as a payload schema, not an imperative shell command.

Files

  • scripts/validate_deterministic_skill_routing.py — AST validator (mypy --strict clean, ~900 lines)
  • scripts/pre_commit_validate_deterministic_skills.sh — pre-commit wrapper that skips silently when the omniclaude sibling is absent
  • tests/unit/scripts/test_validate_deterministic_skill_routing.py — 18 unit tests (clean skill passes + every negative path)
  • .github/workflows/ci.yml — new blocking Phase 1 job check-deterministic-skills; added to quality-gate aggregator's evaluation
  • .pre-commit-config.yaml — validate-deterministic-skill-routing hook

Verification

  • uv run pytest tests/unit/scripts/ -q → 623 passed (18 new)
  • uv run mypy --strict scripts/validate_deterministic_skill_routing.py → clean
  • uv run mypy --strict tests/unit/scripts/test_validate_deterministic_skill_routing.py → clean
  • uv run ruff format / ruff check src/ tests/ → clean
  • pre-commit run on all changed files → all hooks green, including the new OMN-8765 hook
  • python scripts/validate_deterministic_skill_routing.py --skills-root /…/omniclaude/plugins/onex/skills → OK — 18 Tier 1 skill(s) scanned, 0 violations

All 18 current Tier 1 deterministic skills in omniclaude pass the new gate, so merging this PR activates the blocking gate cleanly.

DoD Evidence (from OMN-8765)

  • CI check check-deterministic-skills blocks merge if any deterministic shim lacks dispatch / contains LLM SDK import / contains subprocess orchestration / contains conditional prose fallback
  • Gate is AST-based (not grep/line-count)
  • Gate passing on all current Tier 1 shims

Test plan

  • CI check-deterministic-skills job turns green on this PR
  • quality-gate aggregator passes with the new gate included
  • Future PR that intentionally adds import anthropic to a Tier 1 SKILL.md fails the gate (regression coverage from unit test suite)

🔗 Linear: OMN-8765

Summary by CodeRabbit

  • Chores

    • Added a CI "Deterministic Skill Routing" validation job and made the overall quality gate require its success.
    • Integrated a pre-commit hook to run the deterministic routing validator locally (skips enforcement if skills repo is missing).
  • New Features

    • Added a CLI validator that scans skill docs for routing dispatches, banned SDK/subprocess patterns, and required error/directive markers; emits pass/fail/report outputs.
  • Tests

    • Added comprehensive unit tests covering scanner rules, CLI flags, and reporting behavior.

@coderabbitai

coderabbitai Bot commented Apr 24, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds deterministic skill routing validation: CI job, pre-commit hook and wrapper, Python CLI scanner for Tier 1 SKILL.md (AST + shell heuristics), unit tests, and a Ruff lint exception.

Changes

Cohort / File(s) Summary
CI Workflow
.github/workflows/ci.yml
Adds check-deterministic-skills ("Deterministic Skill Routing") job; updates quality-gate.needs and its pass condition to require the new job's success and include its result in the quality-gate summary.
Pre-commit & Wrapper
.pre-commit-config.yaml, scripts/pre_commit_validate_deterministic_skills.sh
Adds pre-commit hook validate-deterministic-skill-routing and wrapper script that resolves --skills-root (env / sibling clone / OMNI_HOME), skips when missing, and execs the validator CLI.
Validator CLI & Logic
scripts/validate_deterministic_skill_routing.py
New CLI and scanning functions: parses fenced Python via ast, tokenizes shell blocks, detects banned LLM SDK imports/API calls, subprocess/orchestration patterns, wrapped dispatchs, aggregates dispatch evidence, enforces SkillRoutingError and "do not produce prose" directives; supports --skill, --report, and emit exit codes.
Tests
tests/unit/scripts/test_validate_deterministic_skill_routing.py
New pytest suite covering clean vs violating skills, banned patterns, subprocess aliases, shell wrappers, prose-fallback heuristics, dispatch counting, CLI flags (--report, --skill), and exit-code behavior.
Lint Config
pyproject.toml
Adds per-file Ruff ignore for scripts/validate_deterministic_skill_routing.py to exempt T201 (allow print() usage).

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant Pre as Pre-commit Hook
  participant Validator as Validator CLI
  participant Skills as Skills Repo
  participant CI as GitHub Actions

  Dev->>Pre: commit triggers pre-commit
  Pre->>Validator: run pre_commit_validate_deterministic_skills.sh
  Validator->>Skills: locate skills root (env / sibling / OMNI_HOME)
  alt skills root found
    Validator->>Skills: scan SKILL.md (AST + shell heuristics)
    Validator-->>Pre: report + exit code
  else skills root missing
    Validator-->>Pre: print "Skipping... gate" and exit 0
  end

  CI->>Validator: run `check-deterministic-skills` job
  Validator-->>CI: write summary to $GITHUB_STEP_SUMMARY
  CI->>CI: `quality-gate` requires `check-deterministic-skills` == success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop through SKILL.md by lantern light,

I sniff for banned imports late at night.
I count the runs, I flag the wrapped dispatch,
I guard the gate, I tidy every patch.
A tiny rabbit keeping routes polite.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding an AST-based deterministic-skill routing CI gate, which aligns with the primary objective of implementing a Phase 1 blocking CI lint job that enforces the SkillRoutingError routing contract.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-8765-skill-routing-gate

Comment @coderabbitai help to get the list of available commands and usage tips.

@jonahgabriel
jonahgabriel enabled auto-merge April 24, 2026 05:59
Comment thread scripts/validate_deterministic_skill_routing.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

795-842: 🛠️ Refactor suggestion | 🟠 Major

contract-compliance is waited on but never enforced by Quality Gate.

This block adds contract-compliance to needs, but never reads needs.contract-compliance.result, never reports it, and never includes it in the pass condition. Because the job uses if: always(), Quality Gate can still pass and unblock the 40-way test matrix after contract-compliance fails.

Suggested wiring
     steps:
       - name: Evaluate quality checks
         run: |
@@
           secrets="${{ needs.detect-secrets.result }}"
           sdk_boundary="${{ needs.sdk-boundary-check.result }}"
+          contract_compliance="${{ needs.contract-compliance.result }}"
@@
           echo "| Detect Secrets | $secrets |" >> $GITHUB_STEP_SUMMARY
           echo "| SDK Boundary Guard | $sdk_boundary |" >> $GITHUB_STEP_SUMMARY
+          echo "| Contract Compliance Check | $contract_compliance |" >> $GITHUB_STEP_SUMMARY
@@
              [[ "$docs" == "success" ]] && \
              [[ "$enum_gov" == "success" ]] && \
              [[ "$secrets" == "success" ]] && \
-             [[ "$sdk_boundary" == "success" ]]; then
+             [[ "$sdk_boundary" == "success" ]] && \
+             [[ "$contract_compliance" == "success" ]]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 795 - 842, The workflow adds
contract-compliance to needs but never reads or enforces it; define a variable
contract_compliance="${{ needs.contract-compliance.result }}" (like the other
checks), echo its result into the summary (e.g., "| Contract Compliance |
$contract_compliance |"), and include [[ "$contract_compliance" == "success" ]]
in the main gate if-condition so the Quality Gate actually blocks when
contract-compliance fails; update the variable name and the if-statement near
the block that sets lint/pyright/exports/... and the gate comment to ensure
consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Around line 344-358: The SyntaxError branch in the try/except inside the AST
parse currently returns a RoutingViolation (CHECK_PARSE_ERROR) which is counted
by main(); instead, treat parse errors as non-blocking by logging the parse
failure and returning an empty list so it won't increment the violation count.
Update the except SyntaxError as exc handler in the code that calls
ast.parse(block.source) to call the existing logger (or Python logging) with the
same message (including skill_name, skill_path, and exc.lineno) and then return
[] instead of the RoutingViolation instance so main() won't fail on pseudocode
blocks.
- Around line 622-647: The inline dispatch regex (_INLINE_DISPATCH_RE) is too
permissive and treats placeholder text like `onex node <node_name>` as a real
dispatch; update the pattern used by _INLINE_DISPATCH_RE (and adjust
_count_document_dispatches usage if needed) so the token after the command is a
real node identifier (e.g. restrict to a character class like [A-Za-z0-9_.-]+
and forbid angle brackets or literal '<'/'>'/placeholders), rather than matching
any non-whitespace; ensure the regex still allows the optional leading `uv run`
and the variants `onex run-node|node|run` while only counting valid node/topic
names.
- Around line 263-336: The visitor currently only flags calls when the owner
identifier is the literal module name, letting aliasing and from-imports bypass
checks; fix by tracking import aliases and from-imported names (e.g., add
self.import_aliases mapping local_name -> module_root or (module_root,
original_name) inside visit_Import and visit_ImportFrom), then in visit_Call
resolve the true module/name before checking: for Attribute owners resolve
func.value.id via self.import_aliases (fall back to the identifier) and test
(resolved_root, attr) against SUBPROCESS_ORCHESTRATION_CALLS; also handle direct
Name calls (ast.Name as node.func) by resolving the name via self.import_aliases
to detect from-imported functions (e.g., run) and check (resolved_root, name)
against SUBPROCESS_ORCHESTRATION_CALLS; leave the BANNED_LLM_ATTR_CALLS logic
intact but apply the same alias resolution when inner is an ast.Attribute or
ast.Name.

In `@tests/unit/scripts/test_validate_deterministic_skill_routing.py`:
- Around line 26-27: Add a module-level pytest marker by defining the variable
pytestmark = pytest.mark.unit at the top of the test module so the suite under
tests/unit is classified as a unit test; locate the test module (where pytest is
imported) and add the module-level variable pytestmark referencing
pytest.mark.unit.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 795-842: The workflow adds contract-compliance to needs but never
reads or enforces it; define a variable contract_compliance="${{
needs.contract-compliance.result }}" (like the other checks), echo its result
into the summary (e.g., "| Contract Compliance | $contract_compliance |"), and
include [[ "$contract_compliance" == "success" ]] in the main gate if-condition
so the Quality Gate actually blocks when contract-compliance fails; update the
variable name and the if-statement near the block that sets
lint/pyright/exports/... and the gate comment to ensure consistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b581634-7298-479b-a959-740590c7e9e2

📥 Commits

Reviewing files that changed from the base of the PR and between bbcfeab and eb84392.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • scripts/pre_commit_validate_deterministic_skills.sh
  • scripts/validate_deterministic_skill_routing.py
  • tests/unit/scripts/test_validate_deterministic_skill_routing.py

Comment thread scripts/validate_deterministic_skill_routing.py
Comment thread scripts/validate_deterministic_skill_routing.py Outdated
Comment thread scripts/validate_deterministic_skill_routing.py
Comment thread tests/unit/scripts/test_validate_deterministic_skill_routing.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
scripts/validate_deterministic_skill_routing.py (1)

510-513: ⚠️ Potential issue | 🟠 Major

Reject placeholder shell dispatches like onex node <node_name>.

_line_is_dispatch() currently treats any third token as a valid dispatch target, so a fenced bash example/placeholder still satisfies the contract. The inline scanner already rejects placeholders, but shell blocks can still bypass the gate.

Suggested fix
+_NODE_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_.-]*$")
+
 def _line_is_dispatch(tokens: tuple[str, ...], raw: str) -> bool:
@@
     if head_tokens[0] == "onex" and len(head_tokens) >= 3:
         subcmd = head_tokens[1]
-        if subcmd in {"run", "run-node", "node"}:
+        target = head_tokens[2]
+        if (
+            subcmd in {"run", "run-node", "node"}
+            and _NODE_IDENTIFIER_RE.fullmatch(target)
+        ):
             return True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate_deterministic_skill_routing.py` around lines 510 - 513, The
code in _line_is_dispatch() currently treats any third token as a valid dispatch
target (head_tokens[2]), allowing placeholder examples like "onex node
<node_name>" to pass; update the check after subcmd extraction (head_tokens,
subcmd) to validate the dispatch target is a real identifier by rejecting tokens
that look like placeholders (e.g., start with "<" and end with ">" or contain
angle brackets) or otherwise clearly non-identifiers; specifically, when subcmd
in {"run","run-node","node"} ensure the candidate token (head_tokens[2]) does
not match a placeholder pattern like r'^<.*>$' (or contain '<' or '>') before
returning True.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Around line 289-319: The current visit_ImportFrom method only checks
node.module roots so a statement like "from google import generativeai" isn't
recognised; update visit_ImportFrom to also inspect each alias (alias.name /
alias.asname) and construct the full import root (e.g.
f"{node.module}.{alias.name}") or check alias.name against banned SDK spellings,
then call _banned_module_root on that combined name and, if banned, append a
RoutingViolation (same shape as the existing block using self.violations,
RoutingViolation, CHECK_LLM_IMPORT and self._abs_line(node.lineno)); keep the
existing subprocess/os alias handling intact and ensure generic_visit(node) is
still called.

---

Duplicate comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Around line 510-513: The code in _line_is_dispatch() currently treats any
third token as a valid dispatch target (head_tokens[2]), allowing placeholder
examples like "onex node <node_name>" to pass; update the check after subcmd
extraction (head_tokens, subcmd) to validate the dispatch target is a real
identifier by rejecting tokens that look like placeholders (e.g., start with "<"
and end with ">" or contain angle brackets) or otherwise clearly
non-identifiers; specifically, when subcmd in {"run","run-node","node"} ensure
the candidate token (head_tokens[2]) does not match a placeholder pattern like
r'^<.*>$' (or contain '<' or '>') before returning True.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cceeb347-2c03-49d7-be65-74767c99f744

📥 Commits

Reviewing files that changed from the base of the PR and between eb84392 and 4567be9.

📒 Files selected for processing (2)
  • scripts/validate_deterministic_skill_routing.py
  • tests/unit/scripts/test_validate_deterministic_skill_routing.py

Comment thread scripts/validate_deterministic_skill_routing.py
Comment thread scripts/validate_deterministic_skill_routing.py Fixed
@jonahgabriel
jonahgabriel disabled auto-merge April 24, 2026 13:11
@jonahgabriel
jonahgabriel enabled auto-merge April 24, 2026 13:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Around line 438-467: In _tokenize_shell(), the continuation buffer
(buffer/buffer_start) can remain non-empty if the last physical line ends with a
backslash, dropping that logical line; after the for loop finishes but before
returning result, check if buffer is non-empty and construct the final logical
line exactly like inside the loop (join segments, compute start_rel from
buffer_start, reset buffer), then tokenize and append a ShellLine to result (use
block.start_line + start_rel, raw=logical, tokens as in the try/except). Ensure
this mirrors the existing tokenization flow so final continued lines are not
lost.
- Around line 565-567: The prose-fallback check in _is_prose_fallback reads head
= line.tokens[0] and thus misses commands prefixed by environment assignments
(e.g., FOO=1 echo ...); update _is_prose_fallback to skip leading tokens that
match environment variable assignments (tokens like NAME=VALUE) and choose the
first non-assignment token as head before checking membership in
_PROSE_FALLBACK_VERBS, taking care to handle the case where all tokens are
assignments or tokens is empty.
- Around line 542-552: The current early-return only looks for the literal
substrings "onex run" / "onex node" in raw, so quoted/argv forms like
"['onex','run-node',...]" or hyphen/underscore variants bypass it; update the
check that inspects raw (the lowered variable) to detect "onex" plus any
run/node variant as separate argv forms or quoted list entries — e.g., require
"onex" in lowered AND any of [" run", " run-", " run_", "run-node", "run_node",
" node", "['onex'", '"onex' ] (or use a small regex to match onex followed later
by \b(run|run-node|run_node|node)\b or quoted/array patterns) — while keeping
wrapper_prefixes and the python/-c detection logic intact (look for these
patterns before returning True).
- Around line 520-523: The current dispatch detection treats commands like "onex
run-node --help" or "onex run-node -v" as valid targets; update the branch that
checks onex_tokens and subcmd to also validate the target token (onex_tokens[2])
is a real node/name instead of an option/placeholder: after checking subcmd in
{"run","run-node","node"}, read target = onex_tokens[2] and only return True if
target is non-empty and does not start with '-' and is not a help flag (e.g.,
not in {'--help','-h'}); use the existing onex_tokens and subcmd symbols so the
check sits next to the current logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d8e9ded-2915-4ea7-bf0f-bf44d8eabc41

📥 Commits

Reviewing files that changed from the base of the PR and between 4567be9 and 692aee5.

📒 Files selected for processing (2)
  • pyproject.toml
  • scripts/validate_deterministic_skill_routing.py
✅ Files skipped from review due to trivial changes (1)
  • pyproject.toml

Comment thread scripts/validate_deterministic_skill_routing.py
Comment thread scripts/validate_deterministic_skill_routing.py
Comment thread scripts/validate_deterministic_skill_routing.py Outdated
Comment thread scripts/validate_deterministic_skill_routing.py Outdated
@jonahgabriel
jonahgabriel disabled auto-merge April 24, 2026 16:36
@jonahgabriel
jonahgabriel enabled auto-merge April 24, 2026 16:36
Add a CI lint gate (`check-deterministic-skills`) that blocks merge when any
Tier 1 deterministic skill in `omniclaude` violates the SkillRoutingError
routing contract. Replaces line-count heuristics with AST analysis over
embedded python blocks and structural token-matching over shell blocks, per
amendment A4 on OMN-8765.

What the validator enforces per Tier 1 skill:
- Zero LLM SDK imports (anthropic / openai / google.generativeai)
- No banned LLM API calls (client.messages.create, client.completions.create)
- No subprocess orchestration calls in python blocks
- No `python -c` / `bash -c` wrappers around the dispatch
- No conditional prose-fallback branches after routing failure
- At least one node-dispatch declaration (onex run-node / onex node / Kafka
  publish to `onex.cmd.*`) — multi-line backslash continuations merged before
  tokenizing
- SKILL.md references `SkillRoutingError` with `do not produce prose`

Wiring:
- `scripts/validate_deterministic_skill_routing.py` — AST validator, with
  auto-discovery of the omniclaude sibling clone via `$OMNI_HOME` or
  `$DETERMINISTIC_SKILL_ROOT` env var.
- `scripts/pre_commit_validate_deterministic_skills.sh` — pre-commit wrapper
  that skips silently when the omniclaude sibling is absent.
- `.github/workflows/ci.yml` — new blocking job `check-deterministic-skills`
  (Phase 1) that clones omniclaude and runs the validator; added to the
  `quality-gate` aggregator's evaluation.
- `.pre-commit-config.yaml` — `validate-deterministic-skill-routing` hook.
- `tests/unit/scripts/test_validate_deterministic_skill_routing.py` — 18
  unit tests covering positive path + every structural violation category.

All 18 current Tier 1 deterministic skills in omniclaude pass locally. Test
suite (`tests/unit/scripts/`) runs 623/623 green; mypy --strict and ruff
clean on both new files; pre-commit full run passes.
Use actions/setup-python with env.PYTHON_VERSION (3.12) to match the
version pinned in pyproject.toml instead of relying on the runner's default
python3, mirroring the setup used in all other Phase 1 validation jobs.
…R body

PR body updated to eliminate bare OMN-8737 / OMN-8749 token matches
(non-breaking hyphens in parent-epic / predecessor references so the
receipt-gate ticket regex \bOMN-\d+\b only matches OMN-8765).
Addresses 5 unresolved review threads on omnibase_core#888:

1. CodeQL: remove unused MISSING_NODE_SKILLS global (and its doc reference)
2. Alias-aware subprocess detection — previously only `subprocess.run(...)`
   with literal `subprocess` identifier was flagged; now also trips on
   `import subprocess as sp; sp.run(...)`, `from subprocess import run;
   run(...)`, and the `os` equivalents. Banned-names frozensets extracted
   to shared constants to keep the alias table and tuple pairs in sync.
3. Parse errors are non-blocking — unparseable Python blocks (common in
   pseudocode examples) now log via `logging` and return `[]` instead of
   emitting a CHECK_PARSE_ERROR violation that main() counts and fails on.
4. Dispatch regex rejects the boilerplate placeholder `onex node
   <node_name>` — was previously counting the generic routing-contract
   sentence as a valid dispatch declaration, letting skills satisfy the
   gate without naming any real node. Target must now be a real
   identifier (`[A-Za-z0-9_][A-Za-z0-9_.\-]*`) — no angle brackets.
5. Tighten `_PUBLISH_DECLARATION_RE` to recognise `publishes` /
   `published` / `emits` / `emitted` — the `\b` after bare `publish`
   failed on `publishes` because `s` is a word char. This was a latent
   bug exposed by fix #4 (the inline placeholder was previously masking
   the prose-publish gap in the Kafka-skill test fixture).
6. Add `pytestmark = pytest.mark.unit` to the unit test module per
   repo convention.

Test coverage:
- test_flags_subprocess_orch_via_aliased_import
- test_flags_subprocess_orch_via_from_import
- test_flags_os_system_via_from_import
- test_unparseable_python_block_is_not_blocking
- test_placeholder_inline_dispatch_does_not_satisfy_contract
- test_real_inline_dispatch_satisfies_contract

All 24 tests green; mypy --strict clean on both files.
…ort, unused constant

Three fixes to the AST-based deterministic-skill routing gate:

1. _line_is_dispatch: accept 'uv run onex run-node X' shell prefix so
   skills using the uv wrapper are not incorrectly flagged as missing
   a dispatch declaration (was root cause of merge_sweep violation).

2. visit_ImportFrom: catch 'from google import generativeai' by
   constructing the full module.alias path and checking _banned_module_root
   on it — previously only the module root 'google' was checked, which
   is not in BANNED_LLM_MODULES (only 'google.generativeai' is).

3. Remove SUBPROCESS_ORCHESTRATION_CALLS frozenset — became unused after
   the alias-tracking rewrite in the prior commit; CodeQL flagged it.

4. pyproject.toml: add T201 per-file-ignore for the validator script so
   ruff does not flag intentional CLI print() output.
…eclaration

Some deterministic skills (e.g. session) intentionally keep the canonical
`onex run-node` command in prompt.md rather than SKILL.md to avoid double-
counting in per-skill audit gates that scan all files in the skill directory.
The validator now checks prompt.md as a secondary source when SKILL.md yields
zero dispatch evidence.
- _tokenize_shell: flush continuation buffer at EOF so backslash-terminated
  final lines are not silently dropped
- _line_is_dispatch: reject placeholder/option targets (<node_name>, --help)
  so boilerplate routing-contract sentences don't satisfy the dispatch check
- _is_wrapped_dispatch: broaden early-exit guard to catch quoted argv forms
  like ['onex','run-node',...] via regex instead of literal substring match
- _is_prose_fallback: skip leading env-assignment tokens (FOO=1 echo ...)
  before checking the command verb against _PROSE_FALLBACK_VERBS

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/validate_deterministic_skill_routing.py (1)

916-916: Consider moving import os to module level.

The os import inside _default_skills_root() is unconventional. Since os is a standard library module with minimal overhead, placing it at the module level with other imports improves readability.

♻️ Suggested change

Add to the import block at the top:

 import sys
 from dataclasses import dataclass, field
 from pathlib import Path
+import os

Then remove the local import:

 def _default_skills_root() -> Path:
     ...
-    import os
-
     env = os.environ.get("DETERMINISTIC_SKILL_ROOT")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate_deterministic_skill_routing.py` at line 916, The local
import of os inside _default_skills_root() should be moved to the module-level
import block; remove the inline "import os" from the _default_skills_root()
function and add a single "import os" with the other top-of-file imports so the
function simply uses os.path.join/os.environ etc. without an internal import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Around line 496-501: The banner tuple contains a duplicated entry ("example:")
— update the tuple used in the startswith check (the one iterating over
("usage:", "example:", "example:", "run via:", "see ", "see:")) to remove the
duplicate "example:" (or replace the tuple with an explicit deduplicated
collection) so the loop over banners uses unique values; the change should be
made near the code that computes lowered = raw.lower().lstrip() and then
iterates over the banners for the startswith checks.

---

Nitpick comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Line 916: The local import of os inside _default_skills_root() should be moved
to the module-level import block; remove the inline "import os" from the
_default_skills_root() function and add a single "import os" with the other
top-of-file imports so the function simply uses os.path.join/os.environ etc.
without an internal import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9bc23f55-e5f9-446c-9d09-a65306ba2b0a

📥 Commits

Reviewing files that changed from the base of the PR and between 692aee5 and 9665355.

📒 Files selected for processing (1)
  • scripts/validate_deterministic_skill_routing.py

Comment thread scripts/validate_deterministic_skill_routing.py
@jonahgabriel
jonahgabriel force-pushed the jonah/omn-8765-skill-routing-gate branch from 9665355 to 4577917 Compare April 25, 2026 05:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/validate_deterministic_skill_routing.py (1)

130-130: Minor: CHECK_PARSE_ERROR constant is defined but unused.

Since parse errors are now logged and skipped (non-blocking), this constant is never emitted. Consider removing it to avoid confusion.

♻️ Suggested removal
 CHECK_PROSE_FALLBACK = "PROSE_FALLBACK_BRANCH"
 CHECK_MISSING_ROUTING_ERROR = "MISSING_SKILL_ROUTING_ERROR"
-CHECK_PARSE_ERROR = "CODE_BLOCK_PARSE_ERROR"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate_deterministic_skill_routing.py` at line 130, The constant
CHECK_PARSE_ERROR is defined but no longer used; remove the unused symbol
CHECK_PARSE_ERROR (the top-level constant assignment) from
scripts/validate_deterministic_skill_routing.py and delete any stray references
or TODOs that mention it so the codebase doesn't contain confusing dead
constants; ensure there are no remaining imports or usages of CHECK_PARSE_ERROR
after removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/validate_deterministic_skill_routing.py`:
- Line 130: The constant CHECK_PARSE_ERROR is defined but no longer used; remove
the unused symbol CHECK_PARSE_ERROR (the top-level constant assignment) from
scripts/validate_deterministic_skill_routing.py and delete any stray references
or TODOs that mention it so the codebase doesn't contain confusing dead
constants; ensure there are no remaining imports or usages of CHECK_PARSE_ERROR
after removal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f0fa8a8-2bda-470b-86dc-252bc683a814

📥 Commits

Reviewing files that changed from the base of the PR and between 9665355 and 919fc83.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • pyproject.toml
  • scripts/pre_commit_validate_deterministic_skills.sh
  • scripts/validate_deterministic_skill_routing.py
  • tests/unit/scripts/test_validate_deterministic_skill_routing.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • pyproject.toml
  • scripts/pre_commit_validate_deterministic_skills.sh

@jonahgabriel
jonahgabriel added this pull request to the merge queue Apr 25, 2026
Merged via the queue into main with commit 9fb484f Apr 25, 2026
83 of 84 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-8765-skill-routing-gate branch April 25, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants