feat: Phase 5F/5G/5H — graduation, conflicts, MCP engineering tools
The population move + the safety net + the universal consumer hookup,
all shipped together. This is where the engineering graph becomes
genuinely useful against the real 262-memory corpus.
5F: Memory → Entity graduation (THE population move)
- src/atocore/engineering/_graduation_prompt.py: stdlib-only shared
prompt module mirroring _llm_prompt.py pattern (container + host
use same system prompt, no drift)
- scripts/graduate_memories.py: host-side batch driver that asks
claude-p "does this memory describe a typed entity?" and creates
entity candidates with source_refs pointing back to the memory
- promote_entity() now scans source_refs for memory:* prefix; if
found, flips source memory to status='graduated' with
graduated_to_entity_id forward pointer + writes memory_audit row
- GET /admin/graduation/stats exposes graduation rate for dashboard
5G: Sync conflict detection on entity promote
- src/atocore/engineering/conflicts.py: detect_conflicts_for_entity()
runs on every active promote. V1 checks 3 slot kinds narrowly to
avoid false positives:
* component.material (multiple USES_MATERIAL edges)
* component.part_of (multiple PART_OF edges)
* requirement.name (duplicate active Requirements in same project)
- Conflicts + members persist via the tables built in 5A
- Fires a "warning" alert via Phase 4 framework
- Deduplicates: same (slot_kind, slot_key) won't get a new row
- resolve_conflict(action="dismiss|supersede_others|no_action"):
supersede_others marks non-winner members as status='superseded'
- GET /admin/conflicts + POST /admin/conflicts/{id}/resolve
5H: MCP + context pack integration
- scripts/atocore_mcp.py: 7 new engineering tools exposed to every
MCP-aware client (Claude Desktop, Claude Code, Cursor, Zed):
* atocore_engineering_map (Q-001/004 system tree)
* atocore_engineering_gaps (Q-006/009/011 killer queries — THE
director's question surfaced as a built-in tool)
* atocore_engineering_requirements_for_component (Q-005)
* atocore_engineering_decisions (Q-008)
* atocore_engineering_changes (Q-013 — reads entity audit log)
* atocore_engineering_impact (Q-016 BFS downstream)
* atocore_engineering_evidence (Q-017 inbound provenance)
- MCP tools total: 14 (7 memory/state/health + 7 engineering)
- context/builder.py _build_engineering_context now appends a compact
gaps summary ("Gaps: N orphan reqs, M risky decisions, K unsupported
claims") so every project-scoped LLM call sees "what we're missing"
Tests: 341 → 356 (15 new):
- 5F: graduation prompt parses positive/negative decisions, rejects
unknown entity types, tolerates markdown fences; promote_entity
marks source memory graduated with forward pointer; entity without
memory refs promotes cleanly
- 5G: component.material + component.part_of + requirement.name
conflicts detected; clean component triggers nothing; dedup works;
supersede_others resolution marks losers; dismiss leaves both
active; end-to-end promote triggers detection
- 5H: graduation user message includes project + type + content
No regressions across the 341 prior tests. The MCP server now answers
"which p05 requirements aren't satisfied?" directly from any Claude
session — no user prompt engineering, no context hacks.
Next to kick off from user: run graduation script on Dalidou to
populate the graph from 262 existing memories:
ssh papa@dalidou 'cd /srv/storage/atocore/app && PYTHONPATH=src \
python3 scripts/graduate_memories.py --project p05-interferometer --limit 30 --dry-run'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1327,6 +1327,62 @@ def api_list_entities(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/admin/conflicts")
|
||||
def api_list_conflicts(project: str | None = None) -> dict:
|
||||
"""Phase 5G: list open entity conflicts (optionally scoped to a project)."""
|
||||
from atocore.engineering.conflicts import list_open_conflicts
|
||||
conflicts = list_open_conflicts(project=project)
|
||||
return {"conflicts": conflicts, "count": len(conflicts)}
|
||||
|
||||
|
||||
class ConflictResolveRequest(BaseModel):
|
||||
action: str # dismiss|supersede_others|no_action
|
||||
winner_id: str | None = None
|
||||
|
||||
|
||||
@router.post("/admin/conflicts/{conflict_id}/resolve")
|
||||
def api_resolve_conflict(conflict_id: str, req: ConflictResolveRequest) -> dict:
|
||||
"""Resolve a conflict. Options: dismiss, supersede_others (needs winner_id), no_action."""
|
||||
from atocore.engineering.conflicts import resolve_conflict
|
||||
try:
|
||||
success = resolve_conflict(
|
||||
conflict_id=conflict_id,
|
||||
action=req.action,
|
||||
winner_id=req.winner_id,
|
||||
actor="api-http",
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail=f"Conflict not found or already resolved: {conflict_id}")
|
||||
return {"status": "resolved", "id": conflict_id, "action": req.action}
|
||||
|
||||
|
||||
@router.get("/admin/graduation/stats")
|
||||
def api_graduation_stats() -> dict:
|
||||
"""Phase 5F graduation stats for dashboard."""
|
||||
from atocore.models.database import get_connection
|
||||
|
||||
with get_connection() as conn:
|
||||
total_memories = int(conn.execute("SELECT COUNT(*) FROM memories WHERE status = 'active'").fetchone()[0])
|
||||
graduated = int(conn.execute("SELECT COUNT(*) FROM memories WHERE status = 'graduated'").fetchone()[0])
|
||||
entity_candidates_from_mem = int(conn.execute(
|
||||
"SELECT COUNT(*) FROM entities WHERE status = 'candidate' "
|
||||
"AND source_refs LIKE '%memory:%'"
|
||||
).fetchone()[0])
|
||||
active_entities = int(conn.execute("SELECT COUNT(*) FROM entities WHERE status = 'active'").fetchone()[0])
|
||||
|
||||
return {
|
||||
"active_memories": total_memories,
|
||||
"graduated_memories": graduated,
|
||||
"entity_candidates_from_memories": entity_candidates_from_mem,
|
||||
"active_entities": active_entities,
|
||||
"graduation_rate": (
|
||||
graduated / (total_memories + graduated) if (total_memories + graduated) > 0 else 0.0
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# --- Phase 5 Engineering V1: The 10 canonical queries ---
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user