Skip to content

dotgraph integration: contract Surfaces + awareness block + doctor/sync wiring (0.5.3)#49

Merged
dilawarabbas1 merged 3 commits into
mainfrom
dotagent/dotgraph-integration
May 25, 2026
Merged

dotgraph integration: contract Surfaces + awareness block + doctor/sync wiring (0.5.3)#49
dilawarabbas1 merged 3 commits into
mainfrom
dotagent/dotgraph-integration

Conversation

@dilawarabbas1

Copy link
Copy Markdown
Owner

Summary

One-way integration with dotgraph. dotagent stays the document/process layer; dotgraph stays the code-graph tool. Three additive surfaces, all gated on the presence of .dotgraph/graph.db. The boundary stays clean: dotagent never indexes code, never bundles dotgraph, never gates convergence on surfaces (the orchestrator does that).

Three commits

Commit Title
d81b4e3 Contract: add Surfaces section for dotgraph reconcile integration
af3880b Adapter render: inject Code-graph awareness block when .dotgraph/graph.db exists
67d6266 Doctor + sync: probe dotgraph state; refresh emitted docs pre-render (0.5.3)

What's new

1. Contract Surfaces schema

Every contract.md scaffold now carries a ## Surfaces section under a new <!-- anchor: surfaces --> HTML anchor. The fenced YAML block uses key names that match dotgraph's reconcile flags verbatim:

surfaces:
  code: [{id, why}]
  data:
    tables:        []   # → dotgraph --claimed-tables
    columns:       []
    redis_keys:    []   # → dotgraph --claimed-keys
    kafka_topics:  []   # → dotgraph --claimed-topics
    collections:   []
  callers_to_update: [{id, reason}]
  tests_to_update: []

Backward-compatible — old contracts without the section still validate. contract score --json emits new surfaces_enumerated + surfaces_present observability fields (no rubric weight; downstream gates decide).

2. Adapter "Code-graph awareness" block

When .dotgraph/graph.db exists, every rendered adapter (CLAUDE.md, .cursorrules, copilot-instructions.md, AGENTS.md) gains a rules-of-engagement section listing the 5 dotgraph MCP tools (context_pack, impact, reconcile, find_refs, search) and the 5 emitted doc snapshots. Filesystem check only — render stays deterministic. NO MCP config snippet (that's the consumer's job).

3. Doctor + sync wiring

dotagent doctor --format json gains a dotgraph envelope with installed, version, db_present, dirty_files, stale, etc. dotagent sync runs dotgraph emit-docs --target all as a pre-step when conditions are met. Opt out with --skip-dotgraph. All failures fail-soft.

Spec verification matrix

All 8 spec verification items + 4 bonus checks passed end-to-end:

  • ✓ Scaffold contains Surfaces with all 11 expected subsections
  • ✓ Round-trip parser preserves values; count adjusts on modify
  • score --json emits surfaces fields in populated/placeholder/stripped cases (9/0/0)
  • ✓ Adapter render toggles block on db presence (3 enabled adapters carry it; opencode disabled by default)
  • ✓ Doctor JSON returns dotgraph field in all 4 sub-cases (no PATH / PATH no db / PATH+db / text mode)
  • ✓ Sync runs emit-docs before reindex; doc mtime > sync start; all 5 targets written
  • --skip-dotgraph suppresses pre-step
  • ✓ Surfaces YAML byte-identical across 3 contract rounds; only neg-log appended
  • ✓ Backward compat: removing Surfaces adds zero new validation errors
  • ✓ Rubric unchanged (11 signals, max=33, same bands)
  • ✓ Render is deterministic between back-to-back invocations
  • ✓ Bare .dotgraph/ dir without graph.db produces NO block

Two deviations resolved without help

Spec claim Reality Resolution
dotgraph status --json returns last_indexed Real fields: files, dirty_files, nodes, edges, unresolved, db. No timestamp. Doctor emits last_indexed: null; stale derived from dirty_files > 0 alone. Documented in _compute_stale.
--claimed-callers flag on dotgraph reconcile Real flags don't include it. YAML key callers_to_update: preserved; consumed via MCP tool dict input, not CLI. Documented in template.

Test plan

  • tests/project/test_contract_surfaces.py (new) — 13 tests
  • tests/render/test_code_graph_awareness.py (new) — 13 tests
  • tests/test_dotgraph_integration.py (new) — 18 tests (PATH-shim mocked dotgraph for full lifecycle)
  • tests/project/test_contract_score_json.py — extended to accept new key-set
  • Full suite: 792 passed, 2 skipped

Docs

  • CHANGELOG.md — v0.5.3 entry
  • README.md — single-paragraph integration note (per spec)

Version

0.5.2 → 0.5.3


Generated by Claude Code

claude added 3 commits May 25, 2026 10:33
Every fresh contract.md scaffolded by `init_contract` now carries a
canonical `## Surfaces` section under the `<!-- anchor: surfaces -->`
HTML anchor. The section is a fenced YAML block whose key names match
dotgraph's `reconcile` flags verbatim so a consumer can pipe straight
through without remapping:

  dotgraph reconcile \
    --paths $(git diff --name-only) \
    --claimed-tables $(yq '.surfaces.data.tables[]' contract.md) \
    --claimed-keys   $(yq '.surfaces.data.redis_keys[]' contract.md) \
    --claimed-topics $(yq '.surfaces.data.kafka_topics[]' contract.md)

The locked schema:

  surfaces:
    code:                       # list of {id, why}
    data:
      tables:        []         # mirrors --claimed-tables
      columns:       []
      redis_keys:    []         # mirrors --claimed-keys
      kafka_topics:  []         # mirrors --claimed-topics
      collections:   []
    callers_to_update:          # list of {id, reason}
    tests_to_update:            # list of paths / node ids

The anchor sits BEFORE `<!-- anchor: negotiation-log -->` so Surfaces
is part of the content hash and round-trips through `contract round`
unchanged. Convergence semantics work correctly: same Surfaces between
rounds → same hash; different Surfaces → different hash.

### Public surfaces

- `extract_surfaces_block(text) -> str` — raw YAML body or "".
- `parse_surfaces(text) -> dict` — normalized parse against
  SURFACES_SCHEMA. Tolerant: missing top-level keys default to empty
  arrays; malformed YAML returns empty schema; missing `surfaces:`
  root returns empty schema.
- `count_surfaces(text) -> int` — total enumerated items, placeholders
  excluded (scaffold's `<dotgraph_node_id>`, `<short reason>`, etc.
  drop from the count so a fresh init reports 0).
- `SURFACES_SCHEMA` — the canonical shape consumers can rely on.

### Score JSON additions

`dotagent project contract score --json` now emits two additional
top-level fields:

  "surfaces_enumerated": <int>   # count of real (non-placeholder) items
  "surfaces_present":    <bool>  # true iff enumerated > 0

The rubric (S1..S11, max 33) is unchanged — Surfaces is observability
only. Downstream gates (e.g. dotgraph reconcile, Coda's doc-maintenance
stage) decide enforcement. This matches the spec: "the goal is
observability, not enforcement."

### Backward compatibility

The `surfaces` anchor is NOT added to `SECTION_ANCHORS` (the validator's
required-anchor tuple). It lives in a new `OPTIONAL_SECTION_ANCHORS`
tuple documenting intent. Old contracts written before this PR continue
to validate, score, round-trip, and freeze — they just report
`surfaces_enumerated: 0`.

### Deviations from spec to flag

The spec mentioned a `callers` field that matches a dotgraph CLI flag.
Inspection of dotgraph/cli.py shows `dotgraph reconcile` accepts
`--paths`, `--symbols`, `--claimed-tables`, `--claimed-keys`,
`--claimed-topics`, `--base`, `--include-intra-file` — NO
`--claimed-callers`. The YAML keeps the spec's `callers_to_update:`
shape verbatim because it's consumed via the MCP `reconcile` tool's
dict input, not the CLI. Documented in the schema comments.

### Tests

- tests/project/test_contract_surfaces.py (new) — 13 tests:
  · scaffold emits anchor + fenced YAML with all top-level keys
  · parse_full_yaml round-trips real values
  · parse returns empty schema when absent
  · parse tolerates missing top-level keys (only `code` declared)
  · parse tolerates malformed YAML
  · parse tolerates missing `surfaces:` root
  · count excludes placeholder entries (fresh scaffold == 0)
  · count tallies real entries (full YAML == 10)
  · count handles mixed placeholder + real
  · score --json includes surfaces fields (placeholder case)
  · score --json reflects real surfaces when populated
  · old contract without Surfaces still validates
  · content-hash changes with Surfaces edits, ignores neg-log

- tests/project/test_contract_score_json.py — updated to accept the new
  surfaces_* keys in the JSON receipt schema. The pre-existing key-set
  assertion now includes them.

Full suite: 761 passed, 2 skipped.
…ph.db` exists

When dotagent renders adapter files (CLAUDE.md, .cursorrules,
copilot-instructions.md, AGENTS.md) and detects `.dotgraph/graph.db`
at the project root, a deterministic "Code-graph awareness (dotgraph)"
section is injected into the rules-of-engagement segment.

### Behaviour

- `.dotgraph/graph.db` present → block emitted into every adapter.
- Absent → block omitted entirely. No empty header, no "not installed"
  stub. Section simply doesn't exist.
- Filesystem check ONLY — does not shell out to `dotgraph` during
  render. Renders must stay deterministic and side-effect-free.
- Check is on the database FILE, not the `.dotgraph/` directory — a
  bare directory without the db is treated as absent.

### Block content

Lists the 5 dotgraph MCP tools (unprefixed; runtime adds namespace):

  · context_pack(task_description)  — call FIRST before grep/read
  · impact(symbol, depth=2)          — blast radius for symbol
  · reconcile(changed_paths, claimed) — diff stated vs structural
  · find_refs(needle)                — content-FTS safety net
  · search(query, kind)              — name/signature lookup

Plus pointers to the static graph snapshots dotgraph emit-docs
maintains:

  · docs/dependency-map.md
  · docs/db-impact-map.md
  · docs/redis-key-registry.md
  · docs/kafka-topics.md
  · docs/endpoints.md

Plus an instruction to populate the contract's Surfaces section honestly
using `impact()` output (closes the loop with deliverable 1).

### NOT included (spec)

- MCP server config snippet (the `command: dotgraph`, `args: [...]`
  block). That's a consumer concern. Coda generates per-spawn; Cursor
  users run `dotgraph mcp-config --tool cursor`.
- Tool-runtime prefixes (e.g. `mcp__dotgraph__context_pack`). Tool
  names are unprefixed; runtime adds its own namespace.

### Placement

Both render paths emit the block in the rules-of-engagement segment:

- v3 manifest (default in v0.5.0+): after HARD_POLICY, before the
  MUST READ + navigation manifest sections.
- v1 compendium: after `_render_rules`, before bug-registry.

Sister adapters (.cursorrules, copilot, AGENTS.md) inherit the same
body — the block lands in every one of them.

### Public surface

- `render/workflow.py::CODE_GRAPH_AWARENESS_BLOCK` — the template
  constant. Locked text; modify only with intention.
- `render/workflow.py::code_graph_awareness_block(repo_root) -> str`
  — helper that returns the block string when the db file exists,
  empty string otherwise.

### Tests

tests/render/test_code_graph_awareness.py (13 tests):

  helper directly:
    - returns empty when db absent
    - returns block when db present
    - returns empty when dir exists but db file missing

  v3 manifest renderer:
    - includes block when db present (verifies all 5 tool names + 5
      static doc paths appear)
    - omits block when db absent
    - block appears in rules-of-engagement segment (after HARD POLICY,
      before "Where to find what" navigation)
    - byte-diff is exactly the block (toggling db adds only the block,
      removes nothing)

  v1 compendium renderer:
    - includes block when db present
    - omits block when db absent

  sister-adapter parity:
    - all 4 adapters (claude/cursor/copilot/opencode) carry the block

  content invariants:
    - block does NOT embed an MCP server config snippet (spec)
    - block references all 5 MCP tool names
    - block references all 5 static doc snapshots

Full suite: 774 passed, 2 skipped.
…(0.5.3)

`dotagent doctor` reports dotgraph's freshness; `dotagent sync` runs
`dotgraph emit-docs --target all` as a pre-step when `.dotgraph/graph.db`
is present so the adapter render sees fresh graph-derived docs.

### `dotagent doctor --format json`

JSON output gained a top-level `dotgraph` envelope; the existing
`diagnoses` list moved into `payload.diagnoses` (the previous JSON
shape was a bare list — that envelope is the documented change).

  {
    "diagnoses": [...],
    "dotgraph": {
      "installed":    true,
      "version":      "0.1.8",
      "db_present":   true,
      "db_path":      ".dotgraph/graph.db",
      "dirty_files":  0,
      "last_indexed": null,
      "stale":        false,
      "error":        null,
      "files":        42,
      "nodes":        7361,
      "edges":        12000,
      "unresolved":   3
    }
  }

Failure modes:
- dotgraph not on PATH → installed=false, every other field null.
- dotgraph crash on `status` → installed=true, db_present=true,
  error=<stderr tail>, stale=true (conservative default).
- bad JSON from `status` → same as crash (error captured).

Text mode gains a one-line summary:
  dotgraph: 0.1.8 · 7361 nodes · clean
  dotgraph: 0.1.8 · 9 dirty file(s)
  dotgraph: 0.1.8 · no graph at .dotgraph/graph.db (run: dotgraph index .)
  dotgraph: not installed

Staleness heuristic (documented in `dotgraph_probe.py`):
  · `dirty_files > 0`                        → stale
  · `last_indexed` older than 24h AND any
    source file mtime > last_indexed         → stale
  · no `last_indexed` from status JSON       → stale iff dirty_files>0
  · everything else                          → fresh

### `dotagent sync` pre-step

When `.dotgraph/graph.db` exists at the project root AND dotgraph is on
PATH, sync runs `dotgraph emit-docs --target all` BEFORE the source
reindex + adapter render. Best-effort:
- non-zero exit → warning logged, sync proceeds.
- missing binary → warning logged, sync proceeds.
- timeout (>120s) → warning logged, sync proceeds.
The adapter render then reads whatever `docs/*.md` is on disk — fresh
if emit-docs succeeded, stale otherwise.

New flag `--skip-dotgraph` opts out of the pre-step (CI envs without
dotgraph available, or runs that want to be hermetic).

### New module

`src/dotagent/dotgraph_probe.py` owns all IPC with the dotgraph CLI:
- `probe(repo_root) -> DotgraphInfo` — read-only state snapshot.
- `emit_docs(repo_root) -> (ok, msg)` — runs `dotgraph emit-docs`.

Both functions are best-effort and NEVER raise. All failure modes
land as structured fields on the returned dataclass / tuple.

Subprocess concerns isolated here for trivial PATH-shim mocking in
tests.

### Deviations from spec (resolved without help)

- `dotgraph status --json` does NOT include `last_indexed`. Real
  fields: `files`, `dirty_files`, `nodes`, `edges`, `unresolved`, `db`.
  We emit `last_indexed: null` and derive `stale` from `dirty_files`
  when the timestamp is unavailable. Documented in the heuristic
  comment.
- `dotgraph reconcile` CLI has no `--claimed-callers` flag. The
  `callers_to_update:` YAML key in the Surfaces section is consumed
  by the MCP `reconcile` tool's dict input, not the CLI flag set.

### Tests (+18)

tests/test_dotgraph_integration.py uses PATH-shimming to install a
fake `dotgraph` binary that emulates real surfaces. Coverage:

  probe:
    - dotgraph not on PATH → installed=False, fields null
    - installed but no db → installed=True, db_present=False
    - installed + db + clean → all fields populated, stale=False
    - dirty_files > 0 → stale=True
    - status crash → error captured, stale=True
    - bad JSON from status → error captured

  emit_docs:
    - not installed → ok=False, "not on PATH"
    - success → writes the requested docs/* files
    - non-zero exit → ok=False, error message

  doctor --format json:
    - envelope has both `diagnoses` and `dotgraph`
    - dotgraph fields present + correct values
    - dotgraph.installed=False when binary missing

  doctor text mode:
    - emits one-line dotgraph summary when clean
    - says "not installed" when binary missing
    - shows dirty file count when stale

  sync:
    - runs emit-docs when db present (writes verified on disk)
    - skips emit-docs when db absent
    - --skip-dotgraph suppresses emit-docs even with db present
    - proceeds when emit-docs fails (logs but doesn't break)

### README + CHANGELOG

Single-paragraph integration note per spec, plus a 0.5.3 CHANGELOG entry
covering all three deliverables.

Full suite: 792 passed, 2 skipped.

Version: 0.5.2 → 0.5.3.
@dilawarabbas1 dilawarabbas1 merged commit 4f4fdd2 into main May 25, 2026
0 of 3 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