Skip to content

RFC-0014 Atlas — Phase 0–1 Implementation Plan#

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Ship a local repository Atlas — schema-validated .atlas/ layout, deterministic Python-aware generation, validation, status, and minimal local query — via metagit atlas with no MCP, federation, or optional adapters.

Architecture: New src/metagit/core/atlas/ package (models, ids, paths, validation, extractors, store, service, query). Thin Click group metagit atlas. Canonical YAML under repo-local .atlas/; rebuildable JSON index under .atlas/index/ (gitignored). Follow RFC-0010 package shape and campaign YAML load/save patterns.

Tech Stack: Python 3, Pydantic, PyYAML, jsonschema, Click, stdlib ast (Python symbols), pytest. No tree-sitter or GitNexus in Phase 0–1.

Design: 2026-07-14-rfc-0014-atlas-design.md

Global constraints#

  • Implement on a feature branch (e.g. feat/rfc-0014-atlas), not on main.
  • Modality: CLI + docs (+ skill if useful); no MCP until Phase 2.
  • Core must run with zero optional adapters.
  • Never commit secrets; default path exclusions must drop .env and key material.
  • Generator never overwrites ontology/, intent/, mappings/, or overrides/ without an explicit user command.
  • Before editing existing symbols: GitNexus impact when available; always task qa:prepush then task gitnexus:analyze before hand-off.
  • 2-space indent; type hints; T | Exception service returns; imports at file top.

Out of scope (Phase 0–1)#

MCP tools, atlas context / impact / publish / pull / doctor / propose, mex/GitNexus adapters, federation, OpenAPI/protobuf extractors, Node full symbol parse, public HTTP API, SPA, org ontology packages.

File map (create)#

Path Responsibility
src/metagit/core/atlas/__init__.py Public exports
src/metagit/core/atlas/models.py Entity envelope, evidence, atlas.yaml, status/query results
src/metagit/core/atlas/ids.py ID parse/normalize/validate
src/metagit/core/atlas/paths.py .atlas/ layout path helpers
src/metagit/core/atlas/serialize.py Deterministic YAML dump/load
src/metagit/core/atlas/validate.py Graph + schema validation
src/metagit/core/atlas/store.py Read/write curated + generated files; atomic generated replace
src/metagit/core/atlas/extractors/__init__.py Extractor registry
src/metagit/core/atlas/extractors/inventory.py File/language/module inventory
src/metagit/core/atlas/extractors/python_ast.py Python symbol extraction via ast
src/metagit/core/atlas/extractors/tests_discovery.py Test file discovery
src/metagit/core/atlas/extractors/secrets.py Path exclusion helpers
src/metagit/core/atlas/index.py Rebuild JSON derived index
src/metagit/core/atlas/query.py list/get/traverse + minimal DSL
src/metagit/core/atlas/service.py AtlasService — init/generate/refresh/validate/status/query
src/metagit/cli/commands/atlas.py Click atlas_group
schemas/atlas/entity.schema.json JSON Schema for entity envelope
schemas/atlas/atlas-config.schema.json JSON Schema for atlas.yaml
tests/fixtures/atlas/python_toy/ Tiny Python repo fixture
tests/core/atlas/ Unit tests
tests/cli/commands/test_atlas_cli.py CLI tests
docs/reference/atlas.md Operator reference
.mex/patterns/atlas-local.md Recurring runbook

File map (modify)#

  • src/metagit/cli/main.py — register atlas_group
  • .gitignore — ignore .atlas/index/
  • scripts/modality-parity.ymlatlas_local feature
  • docs/agents.md, llms.txt, AGENTS.md, CHANGELOG.md, mkdocs.yml
  • .mex/ROUTER.md, .mex/patterns/INDEX.md
  • docs/superpowers/specs/2026-07-09-acl-rfc-series-index.md — related-RFC pointer only

Task 1: Models + IDs#

Files: - Create: src/metagit/core/atlas/__init__.py - Create: src/metagit/core/atlas/models.py - Create: src/metagit/core/atlas/ids.py - Test: tests/core/atlas/test_models.py - Test: tests/core/atlas/test_ids.py

  • [ ] Step 1: Write failing tests
#!/usr/bin/env python
"""Unit tests for Atlas ID helpers."""

from __future__ import annotations

import pytest

from metagit.core.atlas.ids import normalize_entity_id, parse_entity_id, validate_entity_id


def test_validate_accepts_capability_id() -> None:
  assert validate_entity_id("capability:payment.capture") == "capability:payment.capture"


def test_validate_rejects_spaces() -> None:
  with pytest.raises(ValueError):
    validate_entity_id("capability:bad id")


def test_parse_splits_kind_and_local() -> None:
  kind, local = parse_entity_id("invariant:refund.idempotent")
  assert kind == "invariant"
  assert local == "refund.idempotent"


def test_normalize_strips_and_lowercases_kind() -> None:
  assert normalize_entity_id("Capability:Payment.Capture") == "capability:Payment.Capture"
#!/usr/bin/env python
"""Unit tests for Atlas models."""

from __future__ import annotations

import pytest
from pydantic import ValidationError

from metagit.core.atlas.models import AtlasConfig, EntityEnvelope, EvidenceItem


def test_entity_envelope_requires_id_and_kind() -> None:
  row = EntityEnvelope.model_validate(
    {
      "apiVersion": "atlas.metagit.dev/v1alpha1",
      "kind": "Capability",
      "metadata": {
        "id": "capability:refund.issue",
        "name": "Issue Refund",
        "lifecycle": "active",
        "classification": "internal",
        "provenance": {"source": "curated", "updatedAt": "2026-07-14T00:00:00Z"},
      },
      "spec": {"purpose": "Refund funds"},
    }
  )
  assert row.metadata.id == "capability:refund.issue"
  with pytest.raises(ValidationError):
    EntityEnvelope.model_validate(
      {
        "apiVersion": "atlas.metagit.dev/v1alpha1",
        "kind": "Capability",
        "metadata": {
          "id": "bad id",
          "name": "X",
          "lifecycle": "active",
          "classification": "internal",
          "provenance": {"source": "curated", "updatedAt": "2026-07-14T00:00:00Z"},
        },
        "spec": {},
      }
    )


def test_evidence_confidence_bounds() -> None:
  ok = EvidenceItem(
    id="evidence:symbol:a",
    kind="symbol",
    locator="src/a.py#f",
    revision="abc",
    extractor="python-ast@1.0.0",
    observedAt="2026-07-14T00:00:00Z",
    confidence=1.0,
  )
  assert ok.confidence == 1.0
  with pytest.raises(ValidationError):
    EvidenceItem(
      id="evidence:symbol:a",
      kind="symbol",
      locator="src/a.py#f",
      revision="abc",
      extractor="python-ast@1.0.0",
      observedAt="2026-07-14T00:00:00Z",
      confidence=1.5,
    )


def test_atlas_config_defaults() -> None:
  cfg = AtlasConfig(repository="github.com/acme/toy", formatVersion="v1alpha1")
  assert cfg.commitGenerated is True
  assert cfg.apiVersion == "atlas.metagit.dev/v1alpha1"
  • [ ] Step 2: Run tests to verify they fail

Run: uv run pytest tests/core/atlas/test_ids.py tests/core/atlas/test_models.py -v
Expected: FAIL with import or collection errors for missing modules.

  • [ ] Step 3: Implement minimal models + ids
#!/usr/bin/env python
"""Atlas entity ID helpers."""

from __future__ import annotations

import re

_ID_RE = re.compile(r"^([a-z][a-z0-9_]*):([A-Za-z0-9_.\-]+)$")


def validate_entity_id(value: str) -> str:
  stripped = value.strip()
  if not _ID_RE.match(stripped):
    raise ValueError(
      f"invalid entity id {value!r}; expected kind:local (e.g. capability:payment.capture)"
    )
  return stripped


def parse_entity_id(value: str) -> tuple[str, str]:
  normalized = validate_entity_id(value)
  kind, local = normalized.split(":", 1)
  return kind, local


def normalize_entity_id(value: str) -> str:
  stripped = value.strip()
  if ":" not in stripped:
    raise ValueError(f"invalid entity id {value!r}")
  kind, local = stripped.split(":", 1)
  return validate_entity_id(f"{kind.lower()}:{local}")

In models.py, define (at minimum):

  • Literals: Lifecycle, Classification, ProvenanceSource, FreshnessState
  • Provenance, EvidenceItem, EntityMetadata, EntityEnvelope
  • AtlasConfig with apiVersion, repository, formatVersion, commitGenerated: bool = True, optional sources freshness map
  • Result envelopes: AtlasStatusResult, AtlasValidateResult, AtlasQueryResult with ok: bool

Use Field(ge=0.0, le=1.0) for confidence. Validate metadata.id via validate_entity_id in a field validator.

__init__.py re-exports AtlasConfig, EntityEnvelope, EvidenceItem.

  • [ ] Step 4: Run tests to verify they pass

Run: uv run pytest tests/core/atlas/test_ids.py tests/core/atlas/test_models.py -v
Expected: PASS

  • [ ] Step 5: Commit
git add src/metagit/core/atlas tests/core/atlas
git commit -m "$(cat <<'EOF'
feat(atlas): add RFC-0014 models and entity ID helpers

EOF
)"

Task 2: Paths + deterministic serialize#

Files: - Create: src/metagit/core/atlas/paths.py - Create: src/metagit/core/atlas/serialize.py - Test: tests/core/atlas/test_paths.py - Test: tests/core/atlas/test_serialize.py

  • [ ] Step 1: Write failing tests
#!/usr/bin/env python
"""Unit tests for Atlas path helpers."""

from __future__ import annotations

from pathlib import Path

from metagit.core.atlas.paths import (
  ATLAS_DIRNAME,
  atlas_root,
  capabilities_file,
  generated_dir,
  index_dir,
  inventory_file,
)


def test_layout_under_repo_root(tmp_path: Path) -> None:
  root = atlas_root(tmp_path)
  assert root == tmp_path / ATLAS_DIRNAME
  assert capabilities_file(tmp_path) == root / "ontology" / "capabilities.yaml"
  assert inventory_file(tmp_path) == root / "generated" / "inventory.yaml"
  assert index_dir(tmp_path) == root / "index"
  assert generated_dir(tmp_path).name == "generated"
#!/usr/bin/env python
"""Unit tests for deterministic Atlas YAML serialization."""

from __future__ import annotations

from metagit.core.atlas.serialize import dump_yaml, load_yaml


def test_dump_is_stable_for_same_mapping() -> None:
  payload = {"b": 2, "a": {"z": 1, "y": 2}}
  first = dump_yaml(payload)
  second = dump_yaml(payload)
  assert first == second
  assert "a:" in first
  loaded = load_yaml(first)
  assert loaded["b"] == 2
  • [ ] Step 2: Run tests — expect FAIL

Run: uv run pytest tests/core/atlas/test_paths.py tests/core/atlas/test_serialize.py -v

  • [ ] Step 3: Implement

paths.py: constants + functions returning Path for every top-level layout entry listed in the design (atlas.yaml, ontology/, intent/, generated/, mappings/, overrides/, policy/, index/). Use os.path.join only if matching project style; prefer pathlib consistently within this package once chosen — match RFC-0010 paths.py style (pathlib.Path).

serialize.py:

#!/usr/bin/env python
"""Deterministic YAML helpers for Atlas artifacts."""

from __future__ import annotations

from typing import Any

import yaml


def dump_yaml(data: Any) -> str:
  return yaml.safe_dump(
    data,
    sort_keys=True,
    default_flow_style=False,
    allow_unicode=True,
  )


def load_yaml(text: str) -> Any:
  return yaml.safe_load(text)
  • [ ] Step 4: Run tests — expect PASS

  • [ ] Step 5: Commit

git add src/metagit/core/atlas/paths.py src/metagit/core/atlas/serialize.py tests/core/atlas/test_paths.py tests/core/atlas/test_serialize.py
git commit -m "$(cat <<'EOF'
feat(atlas): add layout paths and deterministic YAML helpers

EOF
)"

Task 3: JSON Schema artifacts + validator#

Files: - Create: schemas/atlas/entity.schema.json - Create: schemas/atlas/atlas-config.schema.json - Create: src/metagit/core/atlas/validate.py - Test: tests/core/atlas/test_validate.py

  • [ ] Step 1: Write failing tests
#!/usr/bin/env python
"""Unit tests for Atlas validation."""

from __future__ import annotations

from metagit.core.atlas.models import EntityEnvelope
from metagit.core.atlas.validate import ValidationIssue, validate_entities


def test_dangling_invariant_ref_is_error() -> None:
  cap = EntityEnvelope.model_validate(
    {
      "apiVersion": "atlas.metagit.dev/v1alpha1",
      "kind": "Capability",
      "metadata": {
        "id": "capability:refund.issue",
        "name": "Issue Refund",
        "lifecycle": "active",
        "classification": "internal",
        "provenance": {"source": "curated", "updatedAt": "2026-07-14T00:00:00Z"},
      },
      "spec": {"invariants": ["invariant:missing"]},
    }
  )
  issues = validate_entities([cap])
  assert any(i.code == "dangling_ref" for i in issues)


def test_valid_pair_passes() -> None:
  inv = EntityEnvelope.model_validate(
    {
      "apiVersion": "atlas.metagit.dev/v1alpha1",
      "kind": "Invariant",
      "metadata": {
        "id": "invariant:refund.idempotent",
        "name": "Refund idempotent",
        "lifecycle": "active",
        "classification": "internal",
        "provenance": {"source": "curated", "updatedAt": "2026-07-14T00:00:00Z"},
      },
      "spec": {"statement": "Same key => one effect"},
    }
  )
  cap = EntityEnvelope.model_validate(
    {
      "apiVersion": "atlas.metagit.dev/v1alpha1",
      "kind": "Capability",
      "metadata": {
        "id": "capability:refund.issue",
        "name": "Issue Refund",
        "lifecycle": "active",
        "classification": "internal",
        "provenance": {"source": "curated", "updatedAt": "2026-07-14T00:00:00Z"},
      },
      "spec": {"invariants": ["invariant:refund.idempotent"]},
    }
  )
  issues = validate_entities([cap, inv])
  assert issues == []
  • [ ] Step 2: Run — expect FAIL

  • [ ] Step 3: Implement schemas + validate_entities

  • Emit JSON Schema files that mirror the Pydantic envelope (hand-written is fine; keep required fields aligned with models).

  • ValidationIssue model: code: str, message: str, entity_id: str | None = None.
  • validate_entities(entities: list[EntityEnvelope]) -> list[ValidationIssue]:
  • duplicate IDs
  • dangling refs in spec.invariants, spec.contracts, spec.dependsOn when present
  • invalid classification/lifecycle already handled by Pydantic; re-check IDs
  • Optional: validate_config_dict(raw: dict) -> list[ValidationIssue] using jsonschema.validate against atlas-config.schema.json (resolve schema path relative to package or repo schemas/atlas/).

  • [ ] Step 4: Run — expect PASS

  • [ ] Step 5: Commit

git add schemas/atlas src/metagit/core/atlas/validate.py tests/core/atlas/test_validate.py
git commit -m "$(cat <<'EOF'
feat(atlas): add JSON schemas and entity reference validation

EOF
)"

Task 4: Python toy fixture + secret exclusion helpers#

Files: - Create: tests/fixtures/atlas/python_toy/ (minimal package + tests + .env secret fixture) - Create: src/metagit/core/atlas/extractors/__init__.py - Create: src/metagit/core/atlas/extractors/secrets.py - Test: tests/core/atlas/test_secrets.py

  • [ ] Step 1: Create fixture tree
tests/fixtures/atlas/python_toy/
  README.md
  pyproject.toml          # name = python-toy
  src/toy/__init__.py
  src/toy/refunds.py      # class RefundService with def issue
  tests/test_refunds.py   # def test_issue_idempotent
  .env                    # SECRET_KEY=super-secret-do-not-index

refunds.py should contain a real RefundService.issue function/method for later symbol extraction.

  • [ ] Step 2: Write failing exclusion tests
#!/usr/bin/env python
"""Unit tests for Atlas secret path exclusions."""

from __future__ import annotations

from pathlib import Path

from metagit.core.atlas.extractors.secrets import is_excluded_path


def test_env_files_excluded(tmp_path: Path) -> None:
  assert is_excluded_path(tmp_path / ".env")
  assert is_excluded_path(tmp_path / "secrets" / "token.json")
  assert not is_excluded_path(tmp_path / "src" / "toy" / "refunds.py")
  • [ ] Step 3: Implement is_excluded_path

Default name/glob matchers: .env, .env.*, *.pem, *.key, **/credentials*, **/secrets/**, .atlas/index/**. Pure path logic; no file reads.

  • [ ] Step 4: Tests PASS; commit
git add tests/fixtures/atlas src/metagit/core/atlas/extractors tests/core/atlas/test_secrets.py
git commit -m "$(cat <<'EOF'
feat(atlas): add python toy fixture and secret path exclusions

EOF
)"

Task 5: Inventory + Python AST + test discovery extractors#

Files: - Create: src/metagit/core/atlas/extractors/inventory.py - Create: src/metagit/core/atlas/extractors/python_ast.py - Create: src/metagit/core/atlas/extractors/tests_discovery.py - Test: tests/core/atlas/test_extractors.py

  • [ ] Step 1: Write failing tests (use tests/fixtures/atlas/python_toy copied into tmp_path or referenced read-only)
#!/usr/bin/env python
"""Unit tests for Atlas extractors."""

from __future__ import annotations

import shutil
from pathlib import Path

import pytest

from metagit.core.atlas.extractors.inventory import build_inventory
from metagit.core.atlas.extractors.python_ast import extract_python_symbols
from metagit.core.atlas.extractors.tests_discovery import discover_tests

FIXTURE = Path(__file__).resolve().parents[2] / "fixtures" / "atlas" / "python_toy"


@pytest.fixture()
def toy_repo(tmp_path: Path) -> Path:
  dest = tmp_path / "python_toy"
  shutil.copytree(FIXTURE, dest)
  return dest


def test_inventory_lists_python_and_skips_env(toy_repo: Path) -> None:
  inv = build_inventory(toy_repo, revision="deadbeef")
  paths = {item["path"] for item in inv["files"]}
  assert "src/toy/refunds.py" in paths
  assert ".env" not in paths
  assert inv["provenance"]["extractor"].startswith("inventory@")


def test_python_symbols_include_refund_service(toy_repo: Path) -> None:
  symbols = extract_python_symbols(toy_repo, revision="deadbeef")
  locators = {s["locator"] for s in symbols}
  assert any("RefundService.issue" in loc for loc in locators)
  assert all("confidence" in s for s in symbols)


def test_discover_tests_finds_idempotent(toy_repo: Path) -> None:
  tests = discover_tests(toy_repo, revision="deadbeef")
  assert any("test_issue_idempotent" in t.get("locator", t.get("id", "")) for t in tests)
  • [ ] Step 2: Run — expect FAIL

  • [ ] Step 3: Implement extractors

  • Walk files with pathlib.rglob, skip excluded paths and common noise (.git, node_modules, __pycache__, .venv).

  • Inventory record: path, language hint by suffix, size optional; wrap list in dict with provenance.
  • Python: parse .py with ast, emit evidence-shaped dicts for modules/classes/functions; extractor: python-ast@1.0.0; confidence: 1.0.
  • Tests: match test_*.py / *_test.py; record function names starting with test_.
  • All outputs must be deterministic when file set unchanged (sort by path/locator).

  • [ ] Step 4: Run — expect PASS; commit

git add src/metagit/core/atlas/extractors tests/core/atlas/test_extractors.py
git commit -m "$(cat <<'EOF'
feat(atlas): add inventory, python-ast, and test discovery extractors

EOF
)"

Task 6: Store + atomic generate + index rebuild#

Files: - Create: src/metagit/core/atlas/store.py - Create: src/metagit/core/atlas/index.py - Test: tests/core/atlas/test_store.py

  • [ ] Step 1: Write failing tests
#!/usr/bin/env python
"""Unit tests for Atlas store and index."""

from __future__ import annotations

from pathlib import Path

from metagit.core.atlas.models import AtlasConfig
from metagit.core.atlas.paths import atlas_yaml_path, index_entities_file, inventory_file
from metagit.core.atlas.store import AtlasStore


def test_init_layout_and_atomic_generated(tmp_path: Path) -> None:
  store = AtlasStore(tmp_path)
  cfg = AtlasConfig(repository="local/python-toy", formatVersion="v1alpha1")
  assert store.init_layout(cfg) is None
  assert atlas_yaml_path(tmp_path).is_file()
  assert store.write_generated({"inventory.yaml": {"files": [], "provenance": {"extractor": "inventory@1.0.0"}}}) is None
  assert inventory_file(tmp_path).is_file()
  # curated path must still exist and not be wiped
  assert (tmp_path / ".atlas" / "ontology").is_dir()


def test_index_rebuild_writes_json(tmp_path: Path) -> None:
  store = AtlasStore(tmp_path)
  store.init_layout(AtlasConfig(repository="local/x", formatVersion="v1alpha1"))
  store.write_generated({"inventory.yaml": {"files": [{"path": "a.py"}], "provenance": {"extractor": "inventory@1.0.0"}}})
  assert store.rebuild_index() is None
  assert index_entities_file(tmp_path).is_file()

Add index_entities_file / atlas_yaml_path to paths.py if not already present.

  • [ ] Step 2: Implement store

  • init_layout: create directories + default empty YAML stubs for ontology/intent/mappings/overrides/policy + README + atlas.yaml via serialize.

  • write_generated: write to temp dir under .atlas/.tmp-generated-*, validate YAML loads, then replace generated/ atomically (os.replace / directory swap).
  • load_curated_entities: read ontology + intent YAML lists into EntityEnvelope list.
  • rebuild_index: flatten known entities + generated symbol/inventory summaries into .atlas/index/entities.json (sorted).

Never delete curated dirs in write_generated.

  • [ ] Step 3: Tests PASS; commit
git add src/metagit/core/atlas/store.py src/metagit/core/atlas/index.py src/metagit/core/atlas/paths.py tests/core/atlas/test_store.py
git commit -m "$(cat <<'EOF'
feat(atlas): add store with atomic generated writes and JSON index

EOF
)"

Task 7: AtlasService — init, generate, refresh, validate, status#

Files: - Create: src/metagit/core/atlas/service.py - Test: tests/core/atlas/test_service.py

  • [ ] Step 1: Write failing tests using copied python_toy fixture

Cover:

  1. init then generate → inventory/symbols/verifications exist; .env absent from inventory.
  2. Second generate without source changes → byte-identical generated YAML (deterministic).
  3. Curated capability + invariant in ontology files → validate ok; dangling ref → not ok.
  4. Touch refunds.py then refresh(["src/toy/refunds.py"]) → status/result reports invalidation reason containing that path; symbols still include RefundService.issue.
  5. Service methods return Exception instances on hard failures (missing root), not raise (match project convention), or raise only at CLI boundary — pick one and stay consistent with SemanticGraphService (prefer return T | Exception).
  • [ ] Step 2: Implement AtlasService
class AtlasService:
  def __init__(self, repo_root: str | Path) -> None: ...
  def init(self, *, repository: str | None = None, generate: bool = False) -> AtlasStatusResult | Exception: ...
  def generate(self) -> AtlasStatusResult | Exception: ...
  def refresh(self, paths: list[str] | None = None) -> AtlasStatusResult | Exception: ...
  def validate(self) -> AtlasValidateResult | Exception: ...
  def status(self) -> AtlasStatusResult | Exception: ...

Generation pipeline:

  1. Load policy exclusions + config
  2. Run inventory / python_ast / tests_discovery
  3. Write generated manifests with content hashes
  4. Rebuild index
  5. Update freshness on atlas.yaml sources (fresh / stale / …)

Refresh: if paths provided, re-run extractors filtered to those paths + dependents (Phase 1: re-extract touched Python files + full test rediscovery is acceptable if documented); always record invalidationReason in status payload.

Resolve git revision via git rev-parse HEAD when available; else "unknown".

  • [ ] Step 3: Tests PASS; commit
git add src/metagit/core/atlas/service.py tests/core/atlas/test_service.py
git commit -m "$(cat <<'EOF'
feat(atlas): add AtlasService init/generate/refresh/validate/status

EOF
)"

Task 8: Query layer (get / list / traverse + minimal DSL)#

Files: - Create: src/metagit/core/atlas/query.py - Test: tests/core/atlas/test_query.py

  • [ ] Step 1: Write failing tests

Seed curated capability mapped to symbol evidence via .atlas/mappings/semantic-to-evidence.yaml (store helper may load mappings). Assert:

def test_traverse_capability_to_evidence(toy_with_atlas: Path) -> None:
  from metagit.core.atlas.query import AtlasQuery
  from metagit.core.atlas.service import AtlasService

  svc = AtlasService(toy_with_atlas)
  assert not isinstance(svc.generate(), Exception)
  q = AtlasQuery(toy_with_atlas)
  result = q.traverse("capability:refund.issue", relations=["maps_to", "implements", "verified_by"])
  assert result.ok
  assert len(result.nodes) >= 1

Also test JSON-style filter: q.list_entities(kind="Capability") and DSL parse for Capability[id="capability:refund.issue"] → same entity.

  • [ ] Step 2: Implement AtlasQuery reading index + curated YAML; no network.

  • [ ] Step 3: PASS; commit

git add src/metagit/core/atlas/query.py tests/core/atlas/test_query.py
git commit -m "$(cat <<'EOF'
feat(atlas): add local query get/list/traverse and minimal DSL

EOF
)"

Task 9: CLI group#

Files: - Create: src/metagit/cli/commands/atlas.py - Modify: src/metagit/cli/main.py - Test: tests/cli/commands/test_atlas_cli.py

  • [ ] Step 1: Write failing CLI tests with CliRunner + copied fixture

Commands:

  • atlas init --path . --json
  • atlas generate --path . --json
  • atlas validate --path . --json
  • atlas status --path . --json
  • atlas query 'Capability[id="capability:refund.issue"]' --path . --json
  • atlas refresh src/toy/refunds.py --path . --json

Use --path for target repo root (default .). Do not require workspace .metagit.yml for local atlas ops.

  • [ ] Step 2: Implement Click group mirroring semantic_group style; raise_if_error / emit_json from acl_common where appropriate.

Wire in main.py:

from metagit.cli.commands.atlas import atlas_group
...
cli.add_command(atlas_group)
  • [ ] Step 3: PASS; commit
git add src/metagit/cli/commands/atlas.py src/metagit/cli/main.py tests/cli/commands/test_atlas_cli.py
git commit -m "$(cat <<'EOF'
feat(atlas): add metagit atlas CLI group for local MVP commands

EOF
)"

Task 10: Docs, modality, gitignore, closeout#

Files: - Create: docs/reference/atlas.md - Create: .mex/patterns/atlas-local.md - Modify: .gitignore, scripts/modality-parity.yml, mkdocs.yml, docs/agents.md, llms.txt, AGENTS.md, CHANGELOG.md, .mex/ROUTER.md, .mex/patterns/INDEX.md, ACL series index related pointer - Optionally: src/metagit/data/skills/metagit-cli/SKILL.md atlas section + modality anchor

  • [ ] Step 1: Add .gitignore entry
.atlas/index/
  • [ ] Step 2: Add modality feature atlas_local
  - id: atlas_local
    description: Repository-local Atlas semantic layer (init/generate/validate/query)
    service: metagit.core.atlas.service.AtlasService
    reference_doc: docs/reference/atlas.md
    surfaces:
      cli:
        markers:
          - path: src/metagit/cli/commands/atlas.py
            contains: '@atlas_group.command("generate")'
          - path: src/metagit/cli/main.py
            contains: atlas_group
      documentation:
        markers:
          - path: docs/reference/atlas.md
            contains: "modality:atlas_local"
          - path: docs/agents.md
            contains: "modality:atlas_local"
  • [ ] Step 3: Write docs/reference/atlas.md with <!-- modality:atlas_local -->, layout summary, command examples, boundary vs semantic KG / GitNexus, Phase 2+ deferred list.

  • [ ] Step 4: Update agent indexes + CHANGELOG feat: entry + ROUTER “Not yet built” → working bullet for Phase 0–1.

  • [ ] Step 5: Run QA + GitNexus

task qa:prepush
task gitnexus:analyze

Expected: green prepush; analyze completes.

  • [ ] Step 6: Commit
git add .gitignore scripts/modality-parity.yml docs AGENTS.md llms.txt CHANGELOG.md .mex mkdocs.yml src/metagit/data/skills
git commit -m "$(cat <<'EOF'
docs(atlas): add atlas reference, modality parity, and agent index links

EOF
)"

Acceptance checklist (Phase 0–1)#

  • [ ] metagit atlas init creates valid layout without modifying source.
  • [ ] metagit atlas generate deterministic on unchanged python_toy.
  • [ ] Generated entities include provenance, revision, timestamp, confidence.
  • [ ] Curated capability + invariant validate; dangling refs fail.
  • [ ] Query traverses capability → evidence/verification.
  • [ ] Refresh on changed file reports invalidation reason.
  • [ ] .env excluded from generated inventory.
  • [ ] No MCP / federation / adapter code required for green tests.
  • [ ] Modality atlas_local passes task qa:prepush.

Spec coverage (self-review)#

Design requirement Task
Schemas, IDs, layout, serialization 1–3
Secret exclusions 4
Inventory + Python symbols + tests 5
Atomic generated writes + index 6
init/generate/refresh/validate/status 7
Local query DSL + JSON filters 8
CLI 9
Docs/modality/gitignore 10
MCP, federation, adapters, context Out of scope (Phase 2–4)

Execution handoff#

After this plan is approved and the design review is signed off:

1. Subagent-Driven (recommended) — fresh subagent per task, review between tasks
2. Inline Execution — execute in this session with executing-plans checkpoints

Which approach?