Skip to content

phase0-D: add capabilities + manifest drift gate#8

Merged
rafael5 merged 3 commits into
mainfrom
phase0-D
May 10, 2026
Merged

phase0-D: add capabilities + manifest drift gate#8
rafael5 merged 3 commits into
mainfrom
phase0-D

Conversation

@rafael5
Copy link
Copy Markdown
Collaborator

@rafael5 rafael5 commented May 10, 2026

Summary

  • Phase 0 / Track D of the AI-discoverability rollout (per .github/docs/phase0-plan.md). Ships m-cli's tier-1 per-repo contract: a hand-authored dist/repo.meta.json plus three machine-readable payloads it exposes.
  • New CLI surface, all driven by the existing in-process registries (no hand-curation):
    • m capabilities --jsondist/commands.json (argparse parser tree)
    • m lint --list-rules --jsondist/lint-rules.json (Rule registry + Profile reverse-index)
    • m fmt --list-rules --jsondist/fmt-rules.json (FmtRule registry + preset reverse-index)
  • make manifest regenerates all three artifacts; make check-manifest regenerates and asserts no drift, wired into CI as a separate step.
  • CLAUDE.mdAGENTS.md (with CLAUDE.md symlinked back); AGENTS.md gains the agent-quickstart sections required by the tier-1 contract (Setup / Test / Build / Verify / Guardrails). Existing detailed conventions sections preserved.
  • m_cli.cli.build_parser() factored out of main() so the capabilities walker introspects the same tree the dispatcher dispatches against (plugins included).
  • dist/ allowlisted in .gitignore for the four manifest files only; everything else under dist/ stays untracked.

Per the plan: exposes paths in dist/repo.meta.json are sibling-relative (commands.json, not dist/commands.json) — the validator and Track E's smoke test resolve relative to the manifest's own dir; the plan's literal template would have produced dist/dist/... 404s on the raw URL.

Test plan

  • pytest tests/ — 1362 passed, 1 skipped (no regressions; 44 of those tests are new for D3 / D6 / D7).
  • dist/repo.meta.json validates against the published Track A schema (OK: dist/repo.meta.json via validate-repo-meta.py).
  • All three exposed payloads parse as JSON; sizes: commands.json 732 lines, lint-rules.json 1407 lines, fmt-rules.json 117 lines.
  • m capabilities --json | python3 -m json.tool parses; m lint --list-rules --json | jq length returns the registered rule count; same for m fmt.
  • Reverse-index correctness: every rule's profiles (lint) / presets (fmt) list equals the set of profiles/presets whose selector returns it (covered by unit tests).
  • CI: make check-manifest step is new — first push will exercise it. Failure here would mean CI's uv sync produced a parser tree that drifts from what was committed.
  • After merge: tier-1 raw URLs reachable —
    • https://raw.githubusercontent.com/m-dev-tools/m-cli/main/dist/repo.meta.json
    • https://raw.githubusercontent.com/m-dev-tools/m-cli/main/dist/commands.json
    • https://raw.githubusercontent.com/m-dev-tools/m-cli/main/dist/lint-rules.json
    • https://raw.githubusercontent.com/m-dev-tools/m-cli/main/dist/fmt-rules.json

🤖 Generated with Claude Code

rafael5 and others added 3 commits May 10, 2026 18:48
Phase 0 / Track D of the AI-discoverability rollout
(.github/docs/phase0-plan.md). Ships the tier-1 per-repo contract for
m-cli: a hand-authored repo.meta.json plus three machine-readable
payloads it exposes, all derived from the in-process registries.

  * `m capabilities --json` — argparse parser tree → dist/commands.json
  * `m lint  --list-rules --json` — Rule registry + Profile reverse-index
                                     → dist/lint-rules.json
  * `m fmt   --list-rules --json` — FmtRule registry + preset reverse-index
                                     → dist/fmt-rules.json

The argparse / Rule / FmtRule registries remain the only source of
truth; nothing in dist/ is hand-curated. `make manifest` regenerates
all three artifacts; `make check-manifest` regenerates and asserts no
drift, wired into CI as a separate step.

Other changes:

  * `CLAUDE.md` → `AGENTS.md`, with `CLAUDE.md` symlinked back. AGENTS.md
    gains the agent-quickstart sections required by the tier-1 contract
    (Setup / Test / Build / Verify / Guardrails); the existing detailed
    conventions sections are preserved as deeper reference.
  * `m_cli.cli.build_parser()` extracted from `main()` so the
    capabilities walker introspects the same tree the dispatcher
    runs against (plugins included).
  * `dist/` allowlisted in .gitignore for the four manifest files only;
    everything else under `dist/` stays untracked.

Verified: 1362 tests pass, 1 skipped. dist/repo.meta.json validates
against the Phase 0 schema (track A) via validate-repo-meta.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `Manifest drift gate` step on PR #8 surfaced two real drift causes
between local generation and CI regeneration:

  1. Plugin-contributed subcommands. `corpus-stats` (from m-cli-extras,
     installed in the maintainer's local venv) was getting baked into
     dist/commands.json. CI doesn't install plugins, so its
     regeneration produced a different manifest.

     Fix: `build_capabilities(...)` now filters to built-in subcommands
     by default. The dispatcher stashes the built-in name set on the
     parser via `set_defaults(_m_cli_builtins=...)`; the walker reads
     it via `parser.get_default()`. New `include_plugins=True` kwarg
     for callers (e.g. ad-hoc inspection) that want everything.

  2. Positional `required` attribute. argparse derives `required` for
     positionals from `nargs` in a way that varies across CPython 3.12.x
     patch releases — observed False locally (3.12.13) but True in CI's
     setup-uv-resolved Python. Including `required` in the option dict
     broke the drift gate on every run.

     Fix: drop `required` from the emitted option dicts entirely. The
     field was redundant (positionals are required by convention,
     optionals are typically not) and unreliable.

Two new regression tests pin both fixes
(`test_plugin_contributed_subcommands_are_excluded_by_default`,
`test_options_omit_required_field`).

Verified: 1364 tests pass, 1 skipped. dist/commands.json drops from
732 to 630 lines after the two filters.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The org-level schema's `exposes.*` contract resolves paths relative
to the repo root (via .git ancestor walk, per .github/profile/
repo.meta.schema.json description text and the validator's fix in
.github commit e8be174). m-cli's manifest shipped with bare
filenames in commit 8908508, which would have left phase0-smoke's
URL-mode resolver looking for /commands.json at the repo root —
404.

Fix is mechanical:

  "commands":   "commands.json"      → "dist/commands.json"
  "lint_rules": "lint-rules.json"    → "dist/lint-rules.json"
  "fmt_rules":  "fmt-rules.json"     → "dist/fmt-rules.json"

Verified:
- .github's validate-repo-meta.py against this manifest: OK (walks
  to m-cli's .git, resolves dist/commands.json correctly).
- All three referenced files exist under dist/ and are current
  (make manifest produces zero diff against committed payloads).
- make check-manifest will now pass once this commit lands and
  regen produces no diff.

Brings m-cli in line with m-stdlib (dist/stdlib-manifest.json) and
m-standard (docs/integrated/grammar-surface.json), both of which
use repo-root-relative paths. Unblocks phase0-smoke's 3/3 dry-run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafael5 rafael5 merged commit 611040a into main May 10, 2026
1 check passed
@rafael5 rafael5 deleted the phase0-D branch May 10, 2026 23:53
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.

1 participant