feat(cli): Add deterministic detect command for rule routing - #24
Conversation
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>
There was a problem hiding this comment.
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
detectCLI command with--jsonoutput 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.
* 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>
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>
6923eb3 to
934cfb9
Compare
* 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
left a comment
There was a problem hiding this comment.
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.
…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>
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,
detectreports 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 theexistingrecipe (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