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 onmain. - 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
.envand key material. - Generator never overwrites
ontology/,intent/,mappings/, oroverrides/without an explicit user command. - Before editing existing symbols: GitNexus
impactwhen available; alwaystask qa:prepushthentask gitnexus:analyzebefore hand-off. - 2-space indent; type hints;
T | Exceptionservice 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— registeratlas_group.gitignore— ignore.atlas/index/scripts/modality-parity.yml—atlas_localfeaturedocs/agents.md,llms.txt,AGENTS.md,CHANGELOG.md,mkdocs.yml.mex/ROUTER.md,.mex/patterns/INDEX.mddocs/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,EntityEnvelopeAtlasConfigwithapiVersion,repository,formatVersion,commitGenerated: bool = True, optionalsourcesfreshness map- Result envelopes:
AtlasStatusResult,AtlasValidateResult,AtlasQueryResultwithok: 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).
ValidationIssuemodel: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.dependsOnwhen present - invalid classification/lifecycle already handled by Pydantic; re-check IDs
-
Optional:
validate_config_dict(raw: dict) -> list[ValidationIssue]usingjsonschema.validateagainstatlas-config.schema.json(resolve schema path relative to package or reposchemas/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_toycopied intotmp_pathor 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
.pywithast, 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 withtest_. -
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 replacegenerated/atomically (os.replace/ directory swap).load_curated_entities: read ontology + intent YAML lists intoEntityEnvelopelist.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_toyfixture
Cover:
initthengenerate→ inventory/symbols/verifications exist;.envabsent from inventory.- Second
generatewithout source changes → byte-identical generated YAML (deterministic). - Curated capability + invariant in ontology files →
validateok; dangling ref → not ok. - Touch
refunds.pythenrefresh(["src/toy/refunds.py"])→ status/result reports invalidation reason containing that path; symbols still includeRefundService.issue. - Service methods return
Exceptioninstances on hard failures (missing root), not raise (match project convention), or raise only at CLI boundary — pick one and stay consistent withSemanticGraphService(prefer returnT | 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:
- Load policy exclusions + config
- Run inventory / python_ast / tests_discovery
- Write generated manifests with content hashes
- Rebuild index
- Update freshness on
atlas.yamlsources (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
AtlasQueryreading 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 . --jsonatlas generate --path . --jsonatlas validate --path . --jsonatlas status --path . --jsonatlas query 'Capability[id="capability:refund.issue"]' --path . --jsonatlas 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_groupstyle;raise_if_error/emit_jsonfromacl_commonwhere 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
.gitignoreentry
.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.mdwith<!-- 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 initcreates valid layout without modifying source. - [ ]
metagit atlas generatedeterministic on unchangedpython_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.
- [ ]
.envexcluded from generated inventory. - [ ] No MCP / federation / adapter code required for green tests.
- [ ] Modality
atlas_localpassestask 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?