Skip to content

feat(cli): Add deterministic detect command for rule routing - #24

Merged
thecodedrift merged 8 commits into
feat/local-rule-routingfrom
feat/local-rule-detect
Jun 15, 2026
Merged

feat(cli): Add deterministic detect command for rule routing#24
thecodedrift merged 8 commits into
feat/local-rule-routingfrom
feat/local-rule-detect

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Jun 11, 2026

Copy link
Copy Markdown
Member

Add taskless detect --json: an offline, deterministic scan of the working directory that emits the signals the routing recipes consume.

It reports configured linters (evidenced by config files, pyproject.toml [tool.x] tables, or package.json dependencies), inferred languages and frameworks, and the styles of the repo's own existing rules. There is no LLM, no network, and no auth — the command is a stable, testable input that keeps the routing agent from hallucinating the repo's tooling.

By design, detect reports only repo signals; it never matches a request against a catalog of packaged rules. That judgment is effectively infinite across ecosystems and is left to the existing recipe (repo + WebFetch), not baked into the CLI.

Implementation is a focused scanner module (detect/scan.ts) behind a thin command, a Zod output schema, and integration tests covering linter detection across eslint/ruff/rubocop/biome/stylelint, language/framework inference, the no-packaged-rule-claims contract, and the offline/no-auth guarantee.

Stacked on #23 (the change contract). Review/merge that first.

Refs TSKL Runtime Rules


Stack generated by Git Town

Add `taskless detect --json`: an offline, deterministic scan of the repo
for configured linters (config files, pyproject tool tables, package
deps), inferred languages/frameworks, and the repo's own rule styles.
No LLM, no network, no auth — it emits stable signal JSON to feed the
routing recipe. Includes a focused scanner module, output schema, and
integration tests covering detection, language/framework inference,
no-packaged-rule-claims, and the offline/no-auth contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Adds a new taskless detect subcommand intended to provide a deterministic, offline JSON “repo signals” scan that downstream rule-routing recipes can consume (linters, languages/frameworks, and existing local rule styles).

Changes:

  • Introduces a filesystem-only repository scanner (packages/cli/src/detect/scan.ts) to infer linters, languages/frameworks, and local rule style signals.
  • Adds a detect CLI command with --json output and registers it in the CLI entrypoint.
  • Adds Vitest integration tests for linter detection, language/framework inference, rule-style surfacing, and stable JSON shape.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/cli/test/detect.test.ts New integration tests that execute the built CLI and validate detect JSON signals.
packages/cli/src/schemas/detect.ts New Zod schema describing taskless detect --json output.
packages/cli/src/index.ts Registers the new detect subcommand.
packages/cli/src/detect/scan.ts Implements deterministic repo signal scanning for linters/languages/frameworks/rule styles.
packages/cli/src/commands/detect.ts Adds the detect CLI command and JSON/human output modes.
openspec/changes/local-rule-routing/tasks.md Adds/updates spec task tracking for the local-rule-routing change set.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/schemas/detect.ts Outdated
Comment thread packages/cli/src/schemas/detect.ts Outdated
Comment thread packages/cli/src/commands/detect.ts
Comment thread packages/cli/test/detect.test.ts
@thecodedrift
thecodedrift marked this pull request as ready for review June 11, 2026 23:48
thecodedrift and others added 2 commits June 11, 2026 17:36
* feat/local-rule-routing:
  docs(openspec): Clarify detect output schema is internal, not published
  docs(openspec): Address review feedback on local-rule-routing contract
Address review on PR #24:
- Guard package.json dependency parsing: a malformed field (array/string/
  null) no longer yields bogus dependency names via Object.keys.
- Match pyproject tool tables exactly: `[tool.ruff]` or a nested
  `[tool.ruff.lint]`, never a similarly-prefixed sibling like
  `[tool.ruff-lsp]`. Anchored at line start to avoid value matches.
- Rename the linter `configFiles` output field to `evidence`: it holds
  config paths AND non-path markers (pyproject tables, package deps), so
  the old name/contract was misleading.
- Drop the divergent unused detect errorSchema; the error path emits the
  standard `{ ok, code, message }` envelope via makeErrorEnvelope.
- Run detect tests with telemetry disabled (DO_NOT_TRACK), keeping the
  offline scan path hermetic, and add coverage for the table-prefix and
  malformed-manifest cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thecodedrift added a commit that referenced this pull request Jun 12, 2026
PR #25 review:
- remote.txt: stop advertising runtime rules / check.ts as current
  behavior, since runtime-rule support isn't shipped end-to-end yet.
  Reframe the recipe around gathering inputs and delegating to
  `rule create`; the service owns rule-type selection and today writes
  ast-grep rules under .taskless/rules/.
- static.txt: instruct writing the matching `id` field in the test file
  (alongside valid/invalid), as rule create --anonymous and the CLI's
  test-file writer do and ast-grep test filtering expects.
- route.txt: update the detect output example to the renamed `evidence`
  field (follows the detect rename on PR #24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thecodedrift
thecodedrift force-pushed the feat/local-rule-routing branch from 6923eb3 to 934cfb9 Compare June 12, 2026 04:21
* feat/local-rule-routing:
  ci(openspec): Skip the archive check on non-tip stacked PRs (#31)
  docs(openspec): Clarify detect output schema is internal, not published
  docs(openspec): Address review feedback on local-rule-routing contract
  docs(openspec): Propose local-rule-routing change
  chore(skill): Refine the stacked-PR archive-check guidance
  chore(skill): Scope iterate-pr archive check to the stack tip
  chore(config): Allow git-town in project settings

@thecodedrift thecodedrift left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comments included around how we think about this parse. At a high level, I think we can use a tool like ast-grep or glob to quickly scan for files that tell us "what" we see, and then a second parallel detailed scan can answer the next level question around what linters are in use. I just generally think this procedural design is better expressed through config which we did at the top and then gave up on halfway through.

The benefits of this mean that as we add more linters and languages, it's just config changes, not adding code to the detect file.

Comment thread packages/cli/src/detect/scan.ts
Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/detect/scan.ts
Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/detect/scan.ts Outdated
Comment thread packages/cli/src/detect/scan.ts
thecodedrift and others added 4 commits June 13, 2026 20:26
…eworks

Rework the detect scan from a root-only probe into a bounded, monorepo-aware
tree walk and restructure how signals are derived, addressing review feedback
on detect/scan.ts.

- Monorepo-aware discovery: a single fs.glob walk (curated IGNORED_DIRECTORIES
  + MAX_DIRECTORY_DEPTH cap) finds configs and manifests anywhere in the tree;
  evidence carries the path it was found at (e.g. packages/api/.eslintrc.json).
- languages -> linters: a linter is tagged with the language it serves, so its
  dependency evidence is read only from that language's manifest (node from
  package.json, Python from pyproject.toml/requirements.txt) instead of
  conflating ecosystems. Config-file presence stays unconditional per the spec.
- Real parsers: pyproject.toml via smol-toml (graceful on malformed input,
  dropping only its own signal); brittle table regex removed.
- Dropped the frameworks signal — the route recipe never consumed it.
- Fold rule-style detection into a named helper; readFileNode import alias.

Requires Node 22+ (Node 20 is EOL; fs.glob is the walker). Minor changeset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
detect recognized these languages but reported zero linters for them. Fill
the obvious gaps with config-file signals: golangci-lint (Go), Clippy (Rust),
and PHPStan / PHP_CodeSniffer / Psalm (PHP). Still tool-level signal, no rule
catalog — consistent with the D3 non-goal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat/local-rule-routing: (21 commits)
  refactor(cli): Uppercase the acronym in CLIConfig
  refactor(cli): Uppercase the acronym in CLIErrorEnvelope too
  fix(cli): Carry the CLIErrorCode rename into auth.ts
  docs(openspec): Uppercase CLIError/CLIErrorCode in the change contract
  docs(openspec): Uppercase CLIError/CLIErrorCode in the analytics spec
  refactor(cli): Uppercase the CLI acronym in CliError/CliErrorCode
  docs(openspec): Sync corrected check/help spec into the archived capability
  fix(cli): Resolve cli_run identity at invocation start, not end
  test(cli): Prove help_* removal once via a source scan, not per-test
  test(cli): Assert no legacy help_* event on served-topic and index paths
  refactor(cli): Single-pass check counts; robust cli_installed assertion
  fix(cli): Only emit cli_rule_created/improved when rules are written
  fix(cli): Resolve cli_run identity fresh; tighten cli_error + drop cli_version
  docs(openspec): Address review on the telemetry contract
  ci(openspec): Skip the archive check on non-tip stacked PRs
  chore(openspec): Archive restructure-cli-telemetry and sync specs
  feat(cli): Collapse help_* into cli_help { topic }; drop cli_info
  feat(cli): Concrete auth + lifecycle events; drop start/_completed pairs
  feat(cli): Emit concrete rule events; drop rule start/_completed pairs
  feat(cli): Emit cli_run once per invocation from the runner
  ...
Conform detect.ts to the cli_ taxonomy now on main: detect is read-only and
rides on cli_run, so the bespoke cli_detect capture and its getTelemetry import
go. Matches the tip's #39 resolution so the branch is green/consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thecodedrift
thecodedrift merged commit 3ef9846 into feat/local-rule-routing Jun 15, 2026
2 checks passed
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