CodeClone is a deterministic structural controller for Python. Full architecture, contracts, and the agent playbook live
in AGENTS.md.
Code is the implementation source of truth. If docs and code diverge, follow code for implementation decisions and report the divergence.
Default role: specs and validation only.
Do not edit production code unless the user explicitly permits edits for a specific task. “Реализуй” / “Implement” is explicit permission. “Проверь” / “Validate” is not.
When permitted to edit any tracked repository file — specs, tests, documentation, CI, coverage fixes, or docs-only
changes included — follow the change-control workflow. Task type never skips start. When CodeClone MCP is available,
read the bundled codeclone-change-control skill for the full pipeline (tool tiers, decision tables, profiles).
The protocol is mandatory. The derived verification profile controls which steps are required, but start is always
required before edits.
Profiles:
| Profile | Workflow |
|---|---|
| Python structural / governance config | analyze → start → get_relevant_memory → edit → analyze → memory write if required → finish(after_run_id=...) |
| Documentation-only / non-Python | analyze → start → get_relevant_memory → edit → memory write if required → finish(changed_files=[...]) |
| Blocked follow-up | queue behind foreign active intent, then promote before editing |
| Read-only / spec validation | no edit workflow unless repository files change |
Do not skip, replace, reorder, or approximate required steps. Optional/profile-dependent steps may be skipped only under their stated conditions. If a required MCP call fails or is unavailable, stop and report the blocker instead of continuing as a normal edit.
analyze_repository(root="<abs_path>")- Skip only when a valid recent run for the same absolute root already exists.
start_controlled_change(root="<abs_path>", scope={...}, intent="...")- Returns blast radius, budget, workspace state, and
intent_id. - If
status: "needs_analysis", runanalyze_repositoryfirst. - If
status: "queued", do not edit; wait for promotion. - If
concurrent_intentsis non-empty without queue, narrow scope or ask. - If start blocks only because declared scope is already dirty and this is resumed known WIP with no foreign
overlap, retry with
dirty_scope_policy="continue_own_wip"; finish must still prove scope.
- Returns blast radius, budget, workspace state, and
- Edit only after
status == "active"andedit_allowed == true. get_relevant_memory(root="<abs_path>", scope=... or intent_id=...)rootis required;intent_idalone fails MCP validation.
- Edit within declared scope only.
- Run
analyze_repository(root="<abs_path>")after edits for Python structural and governance config changes.- Required after-run must have a new
run_id; identical before/after runs fail Python structural verification withafter_run_not_new. - May be skipped for documentation-only and other non-Python patches when
finishcan verify from changed-file evidence.
- Required after-run must have a new
- Before finish, perform mandatory incident/complexity/decision memory check.
- Call
finish_controlled_change(intent_id=..., changed_files=[...], after_run_id=...).- Returns scope check, verification, receipt, and clears accepted intents.
auto_clear=trueby default; intent clears only on accepted outcomes.- If
status: "unverified", the intent stays active. Follownext_step, then callfinishagain on the sameintent_idwith missing evidence. - If
status: "violated", the intent stays active. Remove out-of-scope changes and retryfinish, or expand scope viastart_controlled_change. - If
user_action_required: true, stop and escalate.
finish reconciles evidence with the start-time dirty snapshot and the full git tree.
Blocking reasons:
- under-reported in-scope dirty →
finish_block_reason: missing_evidence - live foreign in-scope overlap →
foreign_dirty_overlap - unattributed out-of-scope dirt blocks only when
CODECLONE_STRICT_FINISHis truthy →own_unscoped_dirty
Advisory/non-blocking cases:
- new/modified/unknown unattributed out-of-scope dirt and unchanged preexisting unscoped dirty are advisory unless
strict finish applies; finish may return
accepted_with_external_changes - foreign active/stale dirty outside scope →
foreign_attributed_outside_scopeand is ignored - recoverable/dead-PID intents do not grant foreign attribution
If reason=workspace_hygiene, read finish_block_reason; do not bypass with atomic verify. Advisory hygiene fields
such as new_unattributed_unscoped_dirty may appear in external_changes or accepted_with_external_changes but are
not block reasons.
Engineering Memory is a local SQLite store of evidence-linked repository facts. MCP help:
help(topic="engineering_memory"). Published contract pages are TBD during docs-site migration; verify behavior against
code and tests.
Chat is not memory. Conversation text is ephemeral across context shrink, new sessions, and new MCP processes. Durable facts the next agent must know belong in Engineering Memory via MCP, not only in chat or compact summaries.
Default mcp_sync_policy=bootstrap_if_missing auto-creates the store from the latest MCP run on get_relevant_memory.
Explicit refresh: manage_engineering_memory(action="refresh_from_run").
CLI memory init remains for CI/offline. Human approval is still required for agent drafts through the VS Code Memory
view, not MCP and not codeclone memory approve.
After start_controlled_change returns edit_allowed: true:
- Call
get_relevant_memory(root="<abs_path>", scope=... or intent_id=...). - Read contract warnings, stale decisions, and
contradiction_notealerts. - Use
query_engineering_memory(mode=for_path)ormode=searchfor drill-down. - Do not ignore stale memory warnings.
- Do not treat
draft,inferred, or excluded stale records as established facts. - If memory contains a
contradiction_notefor scope, surface it to the user before editing.
- Never use project root as memory scope.
- Compress
record_candidatestatements to one durable fact: target ≤300 chars;validate_claimswarns above 500; hard limit 1000. - List responses default to compact previews.
- Treat
records[],experiences[], andtrajectories[]as separate evidence lanes:records= asserted knowledgetrajectories= episodic workflow evidenceexperiences= advisory patternscoverage= visibility metadata
- Scores are lane-local. Never compare
relevance_scoreacross lanes. for_pathand plain non-semantic search are unranked.subject_count/subjects_truncatedmeans more subjects exist, not that evidence disappeared.- Use
mode=getordetail_level=fullfor complete subjects, agent facets, trajectory contracts, steps, evidence ids, and payloads. patch_trail_summaryrides each trajectory and is never duplicated at the payload root.
Do not call finish_controlled_change after a non-trivial edit cycle until this check passes.
Write at least one durable MCP note before finish if any trigger fired:
| Trigger | Examples |
|---|---|
| Incident | verify/hygiene surprise, unverified/violated recovery, workaround, blocked step, foreign intent friction |
| Complexity | non-obvious root cause, multi-file debug, near do_not_touch, acted on stale/contradiction memory |
| Decision | tradeoff, integration quirk, “next agent must not repeat X” |
Skip only trivial edits: typo, one obvious line, nothing to relearn.
Write through MCP only:
manage_engineering_memory(
root="<abs_path>",
action=record_candidate,
record_type=risk_note | change_rationale,
statement="<what happened, what we learned, what to do next time>",
subject_path="<main repo-relative file you touched>"
)
Other allowed memory writes:
| Timing | Tool |
|---|---|
| During edit, stable observation | record_candidate |
| Before finish, batch notes | finish_controlled_change(..., propose_memory=true) |
| Optional pre-finish claim check | manage_engineering_memory(action=validate_claims, text=...) on claims_text |
| After accepted patch | finish(..., propose_memory=true) → memory_candidates |
Agents cannot call approve, reject, or archive via MCP. Ask the user to promote drafts in the CodeClone VS Code
Memory view.
Memory cannot authorize edits, expand scope, or override findings.
When start returns status: "queued":
start_controlled_change(on_conflict="queue")→status: "queued".- Wait for the foreign intent to clear.
manage_change_intent(action="promote", intent_id=...).- Edit only after promote returns
status: "active". - If
before_run_evicted, re-analyze and re-start.
Live foreign intent means stop, not kill. Never suggest killing a process without explicit user confirmation that the PID is abandoned.
When start_controlled_change / finish_controlled_change are unavailable, use the atomic path in the change-control
skill.
Rules:
- Prefer
start_controlled_change/finish_controlled_change. - Use atomic tools only for queue/promote/recover or when workflow tools are unavailable.
- Do not mix workflow and atomic verification paths in one edit cycle.
- Queue/promote/recover via
manage_change_intentmay be used alongside workflow tools because workflow tools do not expose those administrative transitions.
start_controlled_changedoes not run analysis; ensure a valid run exists before calling it.finish_controlled_changedoes not run analysis; for Python structural and governance config changes, runanalyze_repositoryafter editing and passafter_run_id.- MUST NOT edit files without declaring intent first, including
tests/**/*.py. - MUST NOT edit unless
start_controlled_changereturnedstatus == "active"andedit_allowed == true. - MUST NOT edit while intent is
queued; promote first. - MUST NOT silently expand scope. If files outside declared scope are needed, stop before editing them. Expand only
after user approval unless the user already explicitly allowed expansion. Call
start_controlled_changeagain with expanded scope and continue only when the expanded intent is active. - Do not edit extra files based on blast-radius context alone.
do_not_touchis a hard boundary.review_contextis context, not a ban.- Do not update baselines, analysis cache, or generated reports.
- When
finishor verify returnsnext_step, follow it. - CodeClone findings are the source of truth; do not reinterpret.
- If
finish_controlled_changereturnsstatus: "unverified"or"violated", do not claim verification. - Leaving an active or recoverable own intent behind is blocked cleanup, not completion.
- MUST NOT call
finish_controlled_changeafter a non-trivial cycle withoutrecord_candidateorpropose_memory=truewhen incident/complexity/decision triggers applied. - MUST NOT treat assistant chat text as Engineering Memory.
Run routine controller steps automatically. Queue blocked follow-up work automatically when it can wait; do not ask before queueing.
Ask the user only when:
- scope expansion is required and was not already explicitly allowed
- a
do_not_touchpath must be touched - live foreign intent overlaps and queue is not appropriate
- patch contract returned
violated - patch contract returned
unverifiedand the deterministicnext_stepcannot be executed - baseline, analysis cache, canonical reports, or generated state would be modified
- recovery or reset of another agent’s intent is needed
Routine controller work is automatic. Boundary decisions require the user.
Workflow status: "blocked" is not persisted registry lifecycle; clear abandoned blocked intents via
manage_change_intent(action="clear").
Do not say “done”, “implemented”, “validated”, “verified”, “ready”, or equivalents unless all conditions hold:
finish_controlled_changereturnedstatus: "accepted"or"accepted_with_external_changes"; or, in atomic fallback,manage_change_intent(action="check")returnedcleanorexpanded,check_patch_contract(mode="verify")returnedaccepted, andmanage_change_intent(action="clear")succeeded.scope_check.statusis"clean"or"expanded".intent_clearedistrue; or atomicmanage_change_intent(action="clear")succeeded.- If
claimsis present andclaims.validisfalse, report the warnings. - Claim validation was handled by
finish_controlled_changewhenreview_textwas provided andclaim_validation_recommendedwastrue; for atomic workflow, final summary claims passedvalidate_review_claimswithpatch_health_deltafrom verify unlessclaim_validation_recommendedwas explicitlyfalse.
If status is accepted_with_external_changes, report the external-change advisory instead of presenting the patch as
fully clean.
If any item cannot be completed, report BLOCKED or UNVERIFIED, include intent_id, and state the exact missing
step.
The controller derives verification profile from actual changed files. The agent does not choose or claim the profile.
finish_controlled_change computes it through check_patch_contract(mode="verify"); atomic workflow calls
check_patch_contract(mode="verify") directly.
| Profile | When | after_run required |
Structural checks |
|---|---|---|---|
python_structural |
any .py / .pyi touched |
yes | all |
governance_config |
config files only: pyproject.toml, CI, Dockerfile, etc. |
yes | not applicable |
documentation_only |
only docs files: .md, .rst, LICENSE, etc. |
no | not applicable |
non_python_patch |
other files, no Python/docs | no | not applicable |
state_artifact_change |
CodeClone state artifacts: codeclone.baseline.json, .codeclone/**, .cache/codeclone/** |
no; violated | not applicable |
Rules:
startis always required before edits; lighter profiles affect after-run/verify only.- If any Python source, governance configuration, baseline, cache, or generated state file was touched, lightweight path is not accepted.
- Documentation-only patches can verify without
after_run_idwhenchanged_filesordiff_refevidence is provided. - Other non-Python patches may verify without
after_run_id, but only with controller-reported limitations; do not present as full structural verification. - Receipts use “not applicable” for skipped structural checks, never “passed”.
- Claim Guard may reject or warn on claims that exceed the derived profile.
- For documentation-only patches, “no Python files touched” is allowed; “no structural regressions” requires structural evidence from an after-run.
novelty="known"is baseline-relative, not patch-relative. Do not infer that a patch did not introduce/reintroduce a finding from baseline novelty alone; patch-local regression claims require clean before-run to after-run evidence.
- Read-only tasks: analysis, validation, research
- User explicitly says analysis-only
- CodeClone MCP is unavailable and the task is read-only
- For repository edits that require change control when CodeClone MCP is unavailable, stop and report the blocker
Specs are disposable implementation briefs, not documentation. They are deleted after implementation and validation.
Invariants:
- One model per decision. Choose one approach and close alternatives; never leave incompatible paths in one section.
- Verify against code. Verify every function signature, data model, and behavior claim against current source before writing.
- No aspirational APIs. If a function does not exist yet, say so; do not describe it as existing.
- Decision table for state machines. Exhaustively map every input combination to exactly one output.
- Dependency direction explicit. List imports for each new file and what imports it; verify against
AGENTS.md§14.
Self-check before delivery:
- Conflicting approaches? Pick one.
- Code snippets match actual APIs? Read source.
- State transitions deterministic? Write decision table.
- Implementer can follow without ambiguity? If unclear, it is wrong.
When validating implementation against a spec:
- Read all implementation files, not just grep results.
- Cross-reference every spec requirement against code.
- Run relevant tests:
uv run pytest -q <test_files>. - Run
uv run pre-commit run --all-filesif the user asks to commit. - Check MCP tool visibility if a new tool was added.
- Report
conformant/improved/divergent/missingwith evidence.
# Always
uv run pre-commit run --all-files
# MCP changes
uv run pytest -q tests/test_mcp_service.py tests/test_mcp_server.py
# Full suite
uv run pytest -qSee AGENTS.md §3 for surface-specific commands.
- Never update golden snapshots merely to “fix” tests; snapshot updates require explicit user approval and a contract/schema change rationale.
- Never change fingerprint semantics without
BASELINE_FINGERPRINT_VERSIONreview. - Never make base
codeclonedepend on MCP runtime packages. - Never let MCP mutate baselines, source files, canonical reports, or analysis cache.
- Ephemeral coordination state (workspace intents) and audit trail under
.codeclone/are allowed only through controller and audit contracts. - Never iterate sets/dicts without sorting when output order matters.
- Never introduce
Anyin core/domain code without immediately narrowing it. - Never create
*.mdspecs insidedocs/; usespecs/. - Version constants live in
codeclone/contracts/__init__.py; always read from there, never copy from another doc.
When compacting, preserve only task-critical state.
- current task goal and explicit non-goals
- modified files and why each was modified
- passing/failing test commands and exact remaining failures
- user corrections and rejected approaches, including rejection reasons
- unresolved blockers and next safe action
- active intent:
intent_id, lifecycle status (active/queued/unverified/violated/recoverable), declared scope,edit_allowed, before/afterrun_ids - expected verification profile (
python_structural/governance_config/documentation_only/non_python_patch) and whetherafter_run_idis still required - finish outcome:
accepted/accepted_with_external_changes/BLOCKED/UNVERIFIED; if not accepted, exact missing step - foreign intents or queue position currently blocking work
- pending MCP memory writes: incident/complexity/decision triggers that fired but have not yet been recorded with
record_candidateorpropose_memory=true
Preserve whether the current task touches a versioned contract, especially codeclone/contracts/__init__.py or
BASELINE_FINGERPRINT_VERSION, and that it must not change without review. Do not preserve the full contract list; it
lives in code.
- full logs when failure summaries are enough
- repeated explanations
- obsolete failed attempts unless they explain a current constraint
Compact summaries are still ephemeral. Durable facts, including rejected approaches the next agent must not repeat, must be written to Engineering Memory through MCP.
feat(scope): short imperative description
Optional body with context.
Scopes: mcp, cli, core, baseline, cache, report, html, metrics, memory, observability, analytics,
docs, vscode, codex, claude-desktop, claude-code, cursor.
Prefixes: feat, fix, refactor, test, docs, chore.