docs: fix 6 contradictions from CONTRADICTIONS_REPORT_20260627 (C5/C6/C17/C19/C2)
Six fixes for the c11_python doc sync (chronology row 3):
- C5 (Result notation): Result[str, ErrorInfo] -> Result[str] at
docs/guide_ai_client.md lines 452 + 469; also error_handling.md
line 801 (historical deprecation section).
- C6 (RAGChunk schema): docs/guide_models.md lines 343-349 corrected
to match src/rag_engine.py:19-25 (id, document, path, score, metadata).
- C17 (type_aliases.md table): rewrote alias table to reflect post-2026-06-25
reality (Metadata is @dataclass(frozen=True, slots=True) with 36 fields;
11 per-aggregate dataclasses listed with source locations; removed
stale 'underlying type is dict[str, Any]' claim at line 73 + the
'keep Metadata as dict[str, Any]' claim at line 81).
- C19 (OBLITERATE principle): added 'OBLITERATE Principle' section to
error_handling.md after Migration Playbook; clarified in Hard Rules
that argument types that may be None (caller choice) are NOT banned.
- C2 (audit script name): docs/AGENTS.md references updated to point
to scripts/audit_optional_returns.py (the all-src/ successor to
scripts/audit_optional_in_3_files.py).
Also: docs/reports/CONTRADICTIONS_REPORT_20260627.md — the contradictions
index that drives these fixes. Kept for reference.
C16 + C18 were already addressed in commit 770c2fdb (python.md §10
Documented Exceptions table + §17.10 audit inventory).
This commit is contained in:
@@ -209,16 +209,23 @@ The 3 refactored subsystems demonstrate each pattern in context:
|
||||
|
||||
---
|
||||
|
||||
## Hard Rules (enforced in the 3 refactored files)
|
||||
## Hard Rules (enforced in all `src/*.py` as of 2026-06-27)
|
||||
|
||||
These are non-negotiable in `src/mcp_client.py`, `src/ai_client.py`, and
|
||||
`src/rag_engine.py`:
|
||||
These are non-negotiable in all `src/*.py` files. The migration-target
|
||||
files (14 of them) were historically not enforced; as of 2026-06-27 the
|
||||
`scripts/audit_optional_in_baseline_files.py --strict` audit (renamed
|
||||
from `_in_3_files.py` per the contradictions report) covers all
|
||||
`src/*.py`, and the `cruft_elimination_20260627` track documents the
|
||||
remaining work to bring the 14 migration-target files into compliance.
|
||||
|
||||
- **`Optional[T]` return types are FORBIDDEN** in the 3 refactored files. Use
|
||||
- **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use
|
||||
`Result[T]` (with `NIL_T` singleton if needed) instead. Rationale:
|
||||
`Optional[T]` is the sum type `Union[T, None]` that Fleury's framework
|
||||
replaces. Mixing the two patterns reintroduces the bifurcation the
|
||||
convention is designed to remove.
|
||||
- Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`)
|
||||
remain allowed; they describe a caller choice, not a runtime failure
|
||||
of this function. Only `Optional[T]` *return* types are banned.
|
||||
- **Function return types must be `Result[T]` for any function that can fail
|
||||
at runtime.** A function that can't fail (e.g., `get_name() -> str`)
|
||||
doesn't need a `Result`. The classification is "can this return a different
|
||||
@@ -230,9 +237,12 @@ These are non-negotiable in `src/mcp_client.py`, `src/ai_client.py`, and
|
||||
`try/except` is reserved for converting `OSError`, `PermissionError`, and
|
||||
similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary.
|
||||
|
||||
The verification script `scripts/audit_optional_in_3_files.py` enforces the
|
||||
`Optional[X]` rule by failing CI if any new `Optional[X]` appears in the 3
|
||||
refactored files.
|
||||
The verification script `scripts/audit_optional_returns.py` enforces the
|
||||
`Optional[X]` rule by failing CI if any new `Optional[X]` return type
|
||||
appears in any `src/*.py` file. (As of 2026-06-27 this is the successor to
|
||||
`scripts/audit_optional_in_3_files.py`, which covered only 4 baseline files;
|
||||
the new script scans all `src/*.py` per the cruft_elimination_20260627
|
||||
expansion of the ban.)
|
||||
|
||||
### `Optional[X]` in argument types
|
||||
|
||||
@@ -790,6 +800,58 @@ When converting existing code:
|
||||
|
||||
---
|
||||
|
||||
## The OBLITERATE Principle (Result Migration Anti-Pattern)
|
||||
|
||||
**Added 2026-06-27** (from `result_migration_cruft_removal_20260620`).
|
||||
|
||||
When a function is migrated from `Optional[T]` / `raise` to `Result[T]`:
|
||||
|
||||
- **NO pass-throughs.** Do NOT keep a legacy wrapper like `def _x(): return _x_result(...).data`. The wrapper is dead code the moment the migration lands.
|
||||
- **NO backward compat.** Do NOT keep the old return type alongside the new one. Pick one (the new `Result[T]`), and delete the other.
|
||||
- **In-site callers rewritten in the same atomic commit.** Every caller of the migrated function must be updated to use `result.ok` / `result.errors` / `result.data` directly. No deprecation period. No "we'll fix it later."
|
||||
- **The dead code dies.** Legacy `def _x_result_to_x(...)` shims, `_x_result()` passthrough helpers, and conditional return-type guards must be deleted in the same commit that introduces `Result[T]`. Leaving them creates two equivalent APIs that future agents must disambiguate.
|
||||
|
||||
### The wrong pattern (pass-through that should be obliterated)
|
||||
|
||||
```python
|
||||
# BEFORE (the legacy):
|
||||
def do_thing() -> Optional[str]:
|
||||
result = do_thing_result()
|
||||
if not result.ok: return None
|
||||
return result.data
|
||||
|
||||
# AFTER (the new):
|
||||
def do_thing_result() -> Result[str]:
|
||||
...
|
||||
```
|
||||
|
||||
The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only one entry point: `do_thing_result()`.
|
||||
|
||||
### The right pattern (single canonical entry point)
|
||||
|
||||
```python
|
||||
# After OBLITERATE: only do_thing_result exists
|
||||
def do_thing_result() -> Result[str]:
|
||||
...
|
||||
```
|
||||
|
||||
Callers are rewritten:
|
||||
```python
|
||||
# BEFORE:
|
||||
result = do_thing()
|
||||
if result is None: handle_failure()
|
||||
|
||||
# AFTER:
|
||||
result = do_thing_result()
|
||||
if not result.ok: handle_failure(result.errors)
|
||||
```
|
||||
|
||||
### Why this rule
|
||||
|
||||
The `result_migration_cruft_removal_20260620` track ended with 9 legacy wrappers across 4 files (`mcp_client`, `ai_client`, `rag_engine`, `gui_2`). The wrappers were dead code that added visual noise, broke `mypy --strict`, and required every new caller to decide which path to use. Removing them required `Phase 9: LEGACY_WRAPPER_OBLITERATION` as an explicit step — that step should never have been necessary. **Don't ship pass-through wrappers in the first place.**
|
||||
|
||||
---
|
||||
|
||||
## Historical deprecation (added 2026-06-15, reverted 2026-06-16)
|
||||
|
||||
The public `ai_client.send()` was briefly marked `@deprecated` in favor of
|
||||
@@ -798,7 +860,7 @@ The public `ai_client.send()` was briefly marked `@deprecated` in favor of
|
||||
reverted on 2026-06-16 by `send_result_to_send_20260616` after the
|
||||
Tier 2 autonomous sandbox proved capable of doing the rename safely.
|
||||
|
||||
`ai_client.send(...) -> Result[str, ErrorInfo]` is the canonical public API.
|
||||
`ai_client.send(...) -> Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field) is the canonical public API.
|
||||
No deprecation is in effect. For the historical record of the brief
|
||||
deprecation cycle, see
|
||||
`conductor/tracks/public_api_migration_and_ui_polish_20260615/spec.md`
|
||||
@@ -881,10 +943,10 @@ When writing NEW code, you MUST:
|
||||
When writing NEW code, you MUST NOT:
|
||||
|
||||
1. **DO NOT use `Optional[T]` as a return type** (in any file in
|
||||
`src/mcp_client.py`, `src/ai_client.py`, `src/rag_engine.py` —
|
||||
the 3 refactored files). Use `Result[T]` instead. CI fails if
|
||||
you add a new `Optional[T]` to those files (enforced by
|
||||
`scripts/audit_optional_in_3_files.py`).
|
||||
`src/`). Use `Result[T]` instead. CI fails if you add a new
|
||||
`Optional[T]` return type to any `src/*.py` (enforced by
|
||||
`scripts/audit_optional_in_baseline_files.py --strict`,
|
||||
which scans all `src/*.py` as of 2026-06-27).
|
||||
|
||||
2. **DO NOT use `Optional[T]` as a return type** (anywhere else in
|
||||
`src/`). The convention is migrating to `Result[T]`; new code
|
||||
|
||||
@@ -12,20 +12,34 @@ Reference: the audit script `scripts/audit_weak_types.py` is the ground truth. T
|
||||
|
||||
## The 10 Aliases (the canonical set)
|
||||
|
||||
`src/type_aliases.py` defines 10 `TypeAlias`es + 1 `NamedTuple`:
|
||||
**Updated 2026-06-27** to reflect the post-`metadata_promotion_20260624` / `cruft_elimination_20260627` reality:
|
||||
`Metadata` is no longer `dict[str, Any]`; it is now `@dataclass(frozen=True, slots=True)` with explicit fields.
|
||||
The per-aggregate aliases (`CommsLogEntry`, `HistoryMessage`, `ToolDefinition`, `SessionInsights`, `DiscussionSettings`, `CustomSlice`, `MMAUsageStats`, `ProviderPayload`, `UIPanelConfig`, `PathInfo`) are `@dataclass(frozen=True)` types defined in `src/type_aliases.py`.
|
||||
`FileItem` and `ToolCall` are forward-reference `TypeAlias` strings pointing to types defined in `src/models.py` and `src/openai_schemas.py` respectively (avoids circular imports).
|
||||
`RAGChunk` is the 11th dataclass — it lives in `src/rag_engine.py` (not in `type_aliases.py`) because it's tightly coupled to the RAG engine's chunking logic.
|
||||
|
||||
| Alias | Resolves to | Semantic role |
|
||||
`src/type_aliases.py` defines 10 `TypeAlias`es + 11 dataclasses + 1 `NamedTuple` (12 total aggregate types):
|
||||
|
||||
| Alias / Dataclass | Source | Semantic role |
|
||||
|---|---|---|
|
||||
| `Metadata` | `dict[str, Any]` | The root alias; any key-value record |
|
||||
| `CommsLogEntry` | `Metadata` | A single entry in the AI comms log |
|
||||
| `CommsLog` | `list[CommsLogEntry]` | The comms log ring buffer |
|
||||
| `HistoryMessage` | `Metadata` | A single message in the AI provider history (UI-layer) |
|
||||
| `History` | `list[HistoryMessage]` | The conversation history |
|
||||
| `FileItem` | `Metadata` | A single file in the context (path, content, view_mode, etc.) |
|
||||
| `FileItems` | `list[FileItem]` | The most common weak pattern in the codebase |
|
||||
| `ToolDefinition` | `Metadata` | A single tool definition (name, description, parameters schema) |
|
||||
| `ToolCall` | `Metadata` | A single tool call from the model (id, type, function) |
|
||||
| `CommsLogCallback` | `Callable[[CommsLogEntry], None]` | The callback signature for comms log updates |
|
||||
| `Metadata` | `@dataclass(frozen=True, slots=True)` in `type_aliases.py` (36 fields) | The boundary type at the wire (TOML/JSON parse). Dict-compat methods (`__getitem__`, `get`, etc.) keep legacy call sites working. |
|
||||
| `CommsLogEntry` | `@dataclass(frozen=True)` in `type_aliases.py` (8 fields) | A single entry in the AI comms log |
|
||||
| `CommsLog` | `TypeAlias = list[CommsLogEntry]` | The comms log ring buffer |
|
||||
| `HistoryMessage` | `@dataclass(frozen=True)` in `type_aliases.py` (6 fields) | A single message in the AI provider history (UI-layer) |
|
||||
| `History` | `TypeAlias = list[HistoryMessage]` | The conversation history |
|
||||
| `FileItem` | `TypeAlias = "models.FileItem"` | A single file in the context (path, content, view_mode, etc.) — defined in `src/models.py` |
|
||||
| `FileItems` | `TypeAlias = list[FileItem]` | The most common weak pattern in the codebase |
|
||||
| `ToolDefinition` | `@dataclass(frozen=True)` in `type_aliases.py` (4 fields) | A single tool definition (name, description, parameters schema) |
|
||||
| `ToolCall` | `TypeAlias = "openai_schemas.ToolCall"` | A single tool call from the model (id, type, function) — defined in `src/openai_schemas.py` |
|
||||
| `SessionInsights` | `@dataclass(frozen=True)` in `type_aliases.py` (6 fields) | Session-level token/cost metrics |
|
||||
| `DiscussionSettings` | `@dataclass(frozen=True)` in `type_aliases.py` (3 fields) | Per-discussion generation params |
|
||||
| `CustomSlice` | `@dataclass(frozen=True)` in `type_aliases.py` (4 fields) | A Fuzzy Anchor slice definition |
|
||||
| `MMAUsageStats` | `@dataclass(frozen=True)` in `type_aliases.py` (3 fields) | Per-tier input/output token counter |
|
||||
| `ProviderPayload` | `@dataclass(frozen=True)` in `type_aliases.py` (4 fields) | The payload sent to a provider (script, args, output, source_tier) |
|
||||
| `UIPanelConfig` | `@dataclass(frozen=True)` in `type_aliases.py` (3 fields) | Per-window separator flags |
|
||||
| `PathInfo` | `@dataclass(frozen=True)` in `type_aliases.py` (3 fields) | Paths config (logs_dir, scripts_dir, project_root) |
|
||||
| `RAGChunk` | `@dataclass(frozen=True)` in `rag_engine.py` (5 fields: id, document, path, score, metadata) | A single RAG result chunk |
|
||||
| `CommsLogCallback` | `TypeAlias = Callable[[CommsLogEntry], None]` | The callback signature for comms log updates |
|
||||
|
||||
Plus the NamedTuple:
|
||||
|
||||
@@ -70,17 +84,17 @@ def append_comms(entry: CommsLogEntry) -> None: ...
|
||||
def get_history() -> History: ...
|
||||
```
|
||||
|
||||
The underlying type is still `dict[str, Any]`; the alias name is the documentation.
|
||||
**Updated 2026-06-27** — `Metadata` is itself a `@dataclass(frozen=True, slots=True)` with 36 explicit fields covering the wire schema. It is NOT a `TypeAlias = dict[str, Any]` anymore. The aliases below (e.g., `CommsLogEntry`, `HistoryMessage`) point to their own per-aggregate dataclasses, not to `Metadata`. The original "names for shapes" pattern has been promoted to the structural level (per §2.5).
|
||||
|
||||
### 2.5. When the role has stable distinct fields, promote it to its OWN dataclass
|
||||
|
||||
**Added 2026-06-25 (correction to `metadata_promotion_20260624`).** When a sub-aggregate has a known set of stable, distinct fields (e.g., `CommsLogEntry` has `ts, role, kind, direction, model, source_tier, content, error`; `FileItem` has `path, view_mode, custom_slices`; `RAGChunk` has `document, path, score`), promote it to its OWN `@dataclass(frozen=True, slots=True)` with its OWN fields. Do **NOT** share one mega-dataclass across multiple concepts.
|
||||
**Added 2026-06-25 (correction to `metadata_promotion_20260624`).** When a sub-aggregate has a known set of stable, distinct fields (e.g., `CommsLogEntry` has `ts, role, kind, direction, model, source_tier, content, error`; `FileItem` has `path, view_mode, custom_slices`; `RAGChunk` has `id, document, path, score, metadata`), promote it to its OWN `@dataclass(frozen=True, slots=True)` with its OWN fields. Do **NOT** share one mega-dataclass across multiple concepts.
|
||||
|
||||
**Why:** the per-aggregate dataclass is the "names for shapes" pattern extended to the structural level. Each concept gets its own type, its own fields, its own `to_dict()` / `from_dict()` round-trip. Consumers use direct field access (`entry.ts`, `t.depends_on`, `chunk.document`) which compiles to a single C-level field read with 0 branches.
|
||||
|
||||
**When NOT to promote:** when the shape is genuinely unknown at type level (TOML project config, generic JSON parsing at a wire boundary, polymorphic log dumping). These are **collapsed codepaths** and they keep `Metadata: TypeAlias = dict[str, Any]` as the catch-all.
|
||||
**When NOT to promote:** when the shape is genuinely unknown at type level and the fields are heterogeneous (e.g., log entries from 5 different vendors with mutually-exclusive keys). Use `Metadata: Metadata` (the dataclass) as the catch-all — its 36 explicit fields cover the common wire schema, and its dict-compat methods allow ad-hoc keys for vendor-specific extensions. Do NOT use `dict[str, Any]` directly anywhere; `Metadata` is the typed replacement.
|
||||
|
||||
**Canonical pattern (from `src/openai_schemas.py` and `src/models.py:533`):**
|
||||
**Canonical pattern (from `src/openai_schemas.py` and `src/type_aliases.py`):**
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ The canonical mandate is in [`conductor/code_styleguides/data_oriented_design.md
|
||||
|
||||
4. **The enforcement audit scripts** — the project-level enforcement set:
|
||||
- `scripts/audit_weak_types.py --strict` — flags `dict[str, Any]`, `Any`, anonymous tuples
|
||||
- `scripts/audit_optional_in_3_files.py --strict` — flags `Optional[T]` (extended to all `src/*.py` per the c11_python track)
|
||||
- `scripts/audit_optional_returns.py --strict` — flags `Optional[T]` return types in ALL `src/*.py` (post-2026-06-27 successor to `audit_optional_in_3_files.py`)
|
||||
- `scripts/audit_exception_handling.py --strict` — the data-oriented error handling convention
|
||||
- `scripts/audit_main_thread_imports.py` — always strict; the import graph gate
|
||||
- `scripts/audit_no_models_config_io.py` — the config-I/O ownership gate
|
||||
@@ -45,7 +45,7 @@ The canonical mandate is in [`conductor/code_styleguides/data_oriented_design.md
|
||||
```bash
|
||||
# Run before claiming "done"
|
||||
uv run python scripts/audit_weak_types.py
|
||||
uv run python scripts/audit_optional_in_3_files.py
|
||||
uv run python scripts/audit_optional_returns.py
|
||||
uv run python scripts/audit_exception_handling.py
|
||||
uv run python scripts/audit_main_thread_imports.py
|
||||
uv run python scripts/audit_no_models_config_io.py
|
||||
|
||||
@@ -449,7 +449,7 @@ canonical reference is
|
||||
|
||||
All `_send_<vendor>_result()` functions (8 vendors: Gemini, Anthropic,
|
||||
DeepSeek, MiniMax, Gemini CLI, Qwen, Llama, Grok — plus the
|
||||
`_send_llama_native` Ollama adapter) return `Result[str, ErrorInfo]`. SDK
|
||||
`_send_llama_native` Ollama adapter) return `Result[str]` with `errors: list[ErrorInfo]`. SDK
|
||||
exceptions are caught at the boundary (`src/openai_compatible.py`,
|
||||
`src/qwen_adapter.py`) and converted to `ErrorInfo` dataclasses. The
|
||||
`_classify_<vendor>_error()` functions return `ErrorInfo` (not raise
|
||||
@@ -466,7 +466,8 @@ meaning — do not overload `UNKNOWN` when a new failure mode surfaces
|
||||
### Public API
|
||||
|
||||
- **`ai_client.send(...)`** — the public API. Returns
|
||||
`Result[str, ErrorInfo]`. Accepts 13+ parameters including 8 callbacks.
|
||||
`Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field).
|
||||
Accepts 13+ parameters including 8 callbacks.
|
||||
Internally calls `_send_<vendor>()` for the active provider (the
|
||||
vendor functions return `Result[str]` directly).
|
||||
|
||||
|
||||
@@ -340,13 +340,13 @@ class RAGConfig:
|
||||
top_k: int = 5
|
||||
external_mcp_server: str | None = None
|
||||
|
||||
@dataclass
|
||||
@dataclass(frozen=True)
|
||||
class RAGChunk:
|
||||
text: str
|
||||
source_path: str
|
||||
start_line: int
|
||||
end_line: int
|
||||
embedding: list[float] = field(default_factory=list)
|
||||
id: str = ""
|
||||
document: str = ""
|
||||
path: str = ""
|
||||
score: float = 0.0
|
||||
metadata: Metadata = field(default_factory=dict)
|
||||
|
||||
@dataclass
|
||||
class RAGResult:
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
# Documentation Contradictions Report — 2026-06-27
|
||||
|
||||
**Scope:** All agent-directive markdowns (`AGENTS.md`, `conductor/*.md`, `conductor/code_styleguides/*.md`, `docs/*.md`) cross-referenced for logical soundness.
|
||||
|
||||
**Method:** Read all 14 styleguides + all 8 conductor root files + all 38 docs/*.md files end-to-end, then grep'd/selected specific claims against `src/*.py` and `scripts/*.py` to verify code-state alignment.
|
||||
|
||||
**Total contradictions found: 21** across 8 categories.
|
||||
|
||||
---
|
||||
|
||||
## Severity Legend
|
||||
|
||||
| Level | Meaning |
|
||||
|---|---|
|
||||
| 🔴 **CRITICAL** | Misleads agents into violating a Core Value mandate or running broken code |
|
||||
| 🟠 **HIGH** | Contradicts an active spec/plan or causes agents to make wrong decisions |
|
||||
| 🟡 **MEDIUM** | Drift between doc and code; mostly harmless but creates noise |
|
||||
| 🟢 **LOW** | Doc tidiness; doesn't change agent behavior |
|
||||
|
||||
---
|
||||
|
||||
## Category 1: Mandatory Convention Enforcement Gaps 🔴🟠
|
||||
|
||||
These are the highest-impact contradictions: they make the Core Value mandate (2026-06-25) appear enforceable when it isn't.
|
||||
|
||||
### C1 — `Optional[T]` audit script name vs behavior 🟠
|
||||
|
||||
**Claim:** `conductor/code_styleguides/error_handling.md:212` says "Hard Rules (enforced in the 3 refactored files)". `docs/AGENTS.md` §"Convention Enforcement" says audit scripts run pre-commit. `error_handling.md:885` says the rule applies to "the 3 refactored files".
|
||||
|
||||
**Reality:**
|
||||
- `scripts/audit_optional_in_3_files.py:24-29` defines `BASELINE_FILES = ("src/mcp_client.py", "src/ai_client.py", "src/rag_engine.py", "src/code_path_audit.py")` — **4 files**, not 3.
|
||||
- The script is named `audit_optional_in_3_files.py` but covers 4. Internal contradiction between filename and behavior.
|
||||
- The script has not been "extended to all `src/*.py` per the c11_python track" as `docs/AGENTS.md` claims.
|
||||
|
||||
**Fix:** Rename to `audit_optional_in_baseline_files.py` AND either (a) update `BASELINE_FILES` to actually be all `src/*.py` OR (b) update the docs to accurately reflect that the enforcement is only on 4 baseline files. The `cruft_elimination_20260627` spec says all 14 migration-target files should also be migrated, but there's no enforcement.
|
||||
|
||||
### C2 — Optional[T] ban scope ambiguity in docs 🟠
|
||||
|
||||
**Claim 1:** `conductor/code_styleguides/error_handling.md:212-222` says "Optional[T] return types are FORBIDDEN in the 3 refactored files" (mcp_client, ai_client, rag_engine).
|
||||
|
||||
**Claim 2:** `docs/AGENTS.md` §"Convention Enforcement" says "`scripts/audit_optional_in_3_files.py --strict` (extended to all `src/*.py` per the c11_python track)".
|
||||
|
||||
**Claim 3:** `conductor/tracks/cruft_elimination_20260627/state.toml:18` says Phase 6 (`Optional[T]` returns, 30 sites across 14 files) is "deferred".
|
||||
|
||||
**Contradiction:** The docs claim enforcement "extended to all src/*.py", but the audit script still only checks 4 files. The `cruft_elimination_20260627` spec says 30 sites remain across 14 untracked files — those are NOT enforced. An agent reading the docs would think the rule is global; in practice it's only enforced on 4 files.
|
||||
|
||||
**Fix:** Either (a) actually extend the audit script + rename it OR (b) clarify the docs: ban is enforced on baseline 4 files; cruft_elimination is the migration track for the remaining 14.
|
||||
|
||||
### C3 — Banned-pattern audit script "planned" but never built 🟠
|
||||
|
||||
**Claim:** `conductor/code_styleguides/python.md:413` says "The static analysis script `scripts/audit_imports.py` (planned) flags local imports outside `try/except ImportError` blocks."
|
||||
|
||||
**Reality:** `scripts/audit_imports.py` does NOT exist (verified via `ls scripts/audit_imports.py`). The 7-banned-pattern mandate has only 4 enforcement scripts (audit_weak_types, audit_optional_in_3_files, audit_exception_handling, generate_type_registry), not 5.
|
||||
|
||||
**Fix:** Either (a) build the script OR (b) remove the "planned" reference from `python.md`. The mandate has a gap: local imports + `_PREFIX` aliasing are policy without enforcement.
|
||||
|
||||
### C4 — Tier 2 pre-commit enforcement is sandbox-only 🟡
|
||||
|
||||
**Claim:** `docs/AGENTS.md` §"The pre-commit workflow" says "run before claiming 'done': uv run python scripts/audit_*.py [...] In CI / pre-commit hook" — implying pre-commit hooks exist.
|
||||
|
||||
**Reality:** Only `conductor/tier2/githooks/pre-commit` exists (per `tier2_leak_prevention_20260620`). There is no pre-commit hook in the main repo's `.git/hooks/`. The 4 audits listed are only enforced inside the Tier 2 sandbox.
|
||||
|
||||
**Fix:** Either (a) install the audits as actual pre-commit hooks in the main repo OR (b) clarify that the convention is enforced in Tier 2 sandbox only; the main repo relies on agent discipline + manual runs.
|
||||
|
||||
---
|
||||
|
||||
## Category 2: Doc vs Code State Drift 🟠🟡
|
||||
|
||||
### C5 — `Result[T, ErrorInfo]` notation is wrong 🟠
|
||||
|
||||
**Claim:** `docs/guide_ai_client.md:452` says all 8 vendors "return `Result[str, ErrorInfo]`". Same file line 469 says `ai_client.send(...)` returns "`Result[str, ErrorInfo]`".
|
||||
|
||||
**Reality:** `conductor/code_styleguides/error_handling.md:91` defines:
|
||||
```python
|
||||
class Result(Generic[T]):
|
||||
data: T
|
||||
errors: list[ErrorInfo] = field(default_factory=list)
|
||||
```
|
||||
The signature is `Result[T]` (generic over success type only). Errors is a FIELD, not a type parameter. Correct notation is `Result[str]` (where `.errors: list[ErrorInfo]` is always the shape).
|
||||
|
||||
**Fix:** Replace all `Result[str, ErrorInfo]` in `guide_ai_client.md` with `Result[str]` (and reference the field `.errors: list[ErrorInfo]` separately). Same fix in any other guide that uses this notation.
|
||||
|
||||
### C6 — `RAGChunk` schema is stale in `guide_rag.md` 🟠
|
||||
|
||||
**Claim:** `docs/guide_rag.md:343-350` documents `RAGChunk` fields as `text, source_path, start_line, end_line, embedding`.
|
||||
|
||||
**Reality:** `src/rag_engine.py:20-21` defines `RAGChunk` with an additional `id: str = ""` field, added per `cruft_elimination_20260627` Phase 5 ("Added `id: str` field to RAGChunk dataclass"). The guide does not show this field.
|
||||
|
||||
**Fix:** Update `guide_rag.md:343-350` to include the `id: str = ""` field. Also update `docs/guide_models.md` `RAGChunk` dataclass section to include `id`.
|
||||
|
||||
### C7 — Provider count: Readme.md says 5, guide says 8 🟠
|
||||
|
||||
**Claim 1:** `docs/Readme.md:34` says `guide_ai_client.md` covers "multi-provider LLM singleton (5 providers: Gemini, Anthropic, DeepSeek, MiniMax, Gemini CLI)".
|
||||
|
||||
**Claim 2:** `docs/guide_ai_client.md:9-10` says "The module is a unified LLM client for 8 providers. It abstracts the differences between providers (Gemini, Anthropic, DeepSeek, MiniMax, Gemini CLI, Qwen, Grok, Llama) ... The OpenAI-compatible vendors all call the shared helper in `src/openai_compatible.py`".
|
||||
|
||||
**Fix:** Update `docs/Readme.md:34` to say "8 providers" (matching the actual codebase).
|
||||
|
||||
### C8 — Test count: Readme.md says 322, guide says 251 🟠
|
||||
|
||||
**Claim 1:** `docs/Readme.md:31` says "322 test files". Same file line 365 says "`guide_testing.md # 322 test files`".
|
||||
|
||||
**Claim 2:** `docs/guide_testing.md:9` says "Manual Slop has **251 test files**". Same file line 26 says "test_*.py # 251 test files".
|
||||
|
||||
**Reality:** The codebase has 251 test files; the Readme is stale (the 322 number likely came from a time when `_sim.py` files were double-counted, or included the `_e2e.py` files).
|
||||
|
||||
**Fix:** Update `docs/Readme.md:31, 365` to "251 test files".
|
||||
|
||||
### C9 — Command count: Readme.md says 50+, guide says 33 🟠
|
||||
|
||||
**Claim 1:** `docs/Readme.md:30` says "Command Palette ... 50+ built-in commands".
|
||||
|
||||
**Claim 2:** `docs/guide_command_palette.md:196` says "The 33 commands currently shipped in `src/commands.py`". Same file line 4 says "33 registered commands".
|
||||
|
||||
**Fix:** Update `docs/Readme.md:30` to "33 built-in commands".
|
||||
|
||||
### C10 — `metadata_promotion_20260624` was supposed to add 12 dataclasses; 11 went to `type_aliases.py` + 1 to `rag_engine.py` 🟡
|
||||
|
||||
**Claim:** `conductor/chronology.md:4` (the canonical index): "add 12 per-aggregate `@dataclass(frozen=True)` classes (CommsLogEntry, HistoryMessage, FileItem, ToolDefinition, RAGChunk, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo)".
|
||||
|
||||
**Reality:** The 12 includes `RAGChunk`, but `RAGChunk` was actually placed in `src/rag_engine.py:20-21`, not in `src/type_aliases.py`. The other 11 went to `type_aliases.py` (some with `from_dict()`, some not). So the spec said "12 in type_aliases.py" but the implementation put 11 in `type_aliases.py` + 1 in `rag_engine.py`.
|
||||
|
||||
**Fix:** Update `conductor/chronology.md:4` to clarify the location split. Update `conductor/tracks/metadata_promotion_20260624/spec.md` G3 to reflect the actual implementation.
|
||||
|
||||
---
|
||||
|
||||
## Category 3: Status Drift in `tracks.md` and `chronology.md` 🟠
|
||||
|
||||
The "active queue" in `tracks.md` does not match what `chronology.md` says is shipped.
|
||||
|
||||
### C11 — `live_gui_test_fixes_20260618` shipped but `tracks.md` says "active" 🟠
|
||||
|
||||
**Claim:** `conductor/tracks.md` row 7d shows `live_gui_test_fixes_20260618` with status "**active**" (in the "Active Tracks (Current Queue)" table).
|
||||
|
||||
**Reality:** `conductor/chronology.md:12` says the track is "Completed" with `ff40138f..6ce55cba (2)` commits.
|
||||
|
||||
**Fix:** Move row 7d out of the Active Tracks table and into the appropriate Phase section (or mark as shipped with link to TRACK_COMPLETION).
|
||||
|
||||
### C12 — `test_sandbox_hardening_20260619` shipped but `tracks.md` says "ready to start" 🟠
|
||||
|
||||
**Claim:** `conductor/tracks.md` row 16 shows `test_sandbox_hardening_20260619` with status "**ready to start**".
|
||||
|
||||
**Reality:** `conductor/chronology.md:11` says "Completed" with `ec0716c9..eec44a09 (9)` commits. `TRACK_COMPLETION_test_sandbox_hardening_20260619.md` exists at the documented path. `tracks.md` row 16 also has `16 | A | Test Sandbox Hardening` listed in the active queue.
|
||||
|
||||
**Fix:** Mark as shipped; move to Phase section; link to `TRACK_COMPLETION_test_sandbox_hardening_20260619.md`.
|
||||
|
||||
### C13 — `metadata_promotion_20260624` listed as active but honest state is Phase 1 done + Phases 2-10 NO-OP 🟠
|
||||
|
||||
**Claim 1:** `conductor/tracks.md` (per my earlier read; full text was truncated) shows the track.
|
||||
|
||||
**Claim 2 (honest):** `conductor/chronology.md:4` says: "Tier 2 added the dataclasses (with drifted field types vs the plan), completed Phase 1 (Ticket migration), but classified Phases 2-10 as no-op per FR2. State on branch: lied about completion (`status = 'completed'` with all phases 'completed (no-op per audit)'). Tier 1 followup corrected to honest state (`status = 'active'`, `current_phase = 0`)."
|
||||
|
||||
**Contradiction:** The track is labeled "active at phase 0" but Phase 1 was completed and shipped. The "no-op" classification of Phases 2-10 means the rest of the work is "documented as deferred" not "to do". An agent reading the active queue would think this is a track to start; in reality it's a track where Phase 1 is done and the rest is filed as a no-op.
|
||||
|
||||
**Fix:** Move `metadata_promotion_20260624` to a "completed Phase 1; Phases 2-10 classified NO-OP" status. Either complete the parent track (the work is done) or rename the state to reflect "1/10 phases done; remaining deferred" so agents don't pick it up.
|
||||
|
||||
### C14 — `result_migration_20260616` parent and sub-track status drift 🟡
|
||||
|
||||
**Claim 1:** `conductor/tracks.md` row 6 (per my earlier read) shows `result_migration_20260616` as "active".
|
||||
|
||||
**Claim 2:** `conductor/chronology.md:6` shows `result_migration_baseline_cleanup_20260620` as "active". But `docs/reports/RESULT_MIGRATION_CAMPAIGN_STATUS_20260619.md` (updated by Phase 9 patch 2026-06-21) says the campaign is closed.
|
||||
|
||||
**Contradiction:** The 5-sub-track campaign (`result_migration_20260616` with sub-tracks 6d-1 through 6d-6) is 100% complete per the close-out report. But `tracks.md` and `chronology.md` still show "active".
|
||||
|
||||
**Fix:** Update the parent track state to "closed" or "completed" with link to the campaign close-out. Same for sub-track 6 (baseline_cleanup).
|
||||
|
||||
### C15 — `result_migration_baseline_cleanup_20260620` status in `tracks.md` 🟡
|
||||
|
||||
**Claim:** `conductor/chronology.md:6` shows `result_migration_baseline_cleanup_20260620` as "active". Per `TRACK_COMPLETION_result_migration_cruft_removal_20260620.md`, the campaign closed 2026-06-20 with Phase 9 patch 2026-06-21.
|
||||
|
||||
**Fix:** Mark as shipped/closed.
|
||||
|
||||
---
|
||||
|
||||
## Category 4: Internal Styleguide Contradictions 🟠🟡
|
||||
|
||||
### C16 — `python.md` §10 Anti-OOP rule vs actual codebase 🟠
|
||||
|
||||
**Claim:** `conductor/code_styleguides/python.md:73-110` says "Anti-OOP Conventions" + "Hard Rules (Enforced by lint)" — "Never write a class for a single method. Use a function." "Never use inheritance for code reuse. Compose with standalone functions." "Never use private methods (`_method`). Module-level functions with clear names suffice." "No nested classes. Define helper types at module level." "No decorator classes."
|
||||
|
||||
**Justification rule (`python.md:87-101`):** "A class is justified ONLY when ALL of: 1. It holds mutable state that must be encapsulated. 2. It has 3+ related methods that share state. 3. It implements a behavioral interface used polymorphically (not just data grouping)."
|
||||
|
||||
**Self-contradiction (`python.md:203-205`):** "**Removed anti-pattern (2026-06-11):** the prior version of this section said 'extremely large files that violate the Anti-OOP rule by necessity.' ... The `App` class in `src/gui_2.py` is not 'violating' anything by being large; it's the natural shape of a class that owns the GUI orchestration."
|
||||
|
||||
**Reality:** The codebase has `App` (150+ methods), `AppController` (166KB), `ConductorEngine`, `WorkerPool`, `RAGEngine`, `MultiAgentConductor`, etc. — all stateful classes. App does NOT satisfy criterion #3 (used polymorphically — it's a singleton). So App and AppController would fail the §10.4 rule.
|
||||
|
||||
**Contradiction within the SAME FILE:** §10.1-§10.3 (strict bans) + §10.4 (3 criteria) + §203 (admission that the rule doesn't apply to App).
|
||||
|
||||
**Fix:** Rewrite §10 to clarify:
|
||||
- §10.1: "Module-level functions for stateless logic (default)."
|
||||
- §10.2: "Classes are justified for stateful subsystems (App, AppController, ConductorEngine, RAGEngine, etc.). The 3 criteria are: holds state + 3+ methods sharing state + used as a singleton OR has a behavioral interface." — drop criterion #3 OR reword as "or is instantiated as a stateful subsystem singleton."
|
||||
- §10.5 (new): "Examples of justified classes in this codebase: `App` (150+ methods, 90 delegation targets, holds the GUI state), `AppController` (the headless state container), `ConductorEngine` (orchestration state machine), `WorkerPool` (thread/semaphore state)."
|
||||
|
||||
### C17 — `type_aliases.md` line 19 table contradicts its own body 🟠
|
||||
|
||||
**Claim (line 19):** "`Metadata` | `dict[str, Any]` | The root alias; any key-value record"
|
||||
|
||||
**Claim (line 42):** "**UPDATED 2026-06-25 (the C11/Odin/Jai-in-Python mandate).** `Metadata` is the typed fat struct at the wire boundary. It is `@dataclass(frozen=True, slots=True)` with explicit fields..."
|
||||
|
||||
**Contradiction within the SAME FILE:** The table at line 19 says `Metadata` is `dict[str, Any]`. The body at line 42 says it's a typed dataclass. The table was NOT updated when the body was rewritten.
|
||||
|
||||
**Claim (line 73):** "The underlying type is still `dict[str, Any]`; the alias name is the documentation."
|
||||
|
||||
**Claim (line 81):** "**When NOT to promote:** ... they keep `Metadata: TypeAlias = dict[str, Any]` as the catch-all."
|
||||
|
||||
**Claim (line 59-61):** "`Metadata` is **NOT** `TypeAlias = dict[str, Any]`. It is a typed fat struct. ... **Anti-pattern (banned):** `Metadata: TypeAlias = dict[str, Any]` (the lazy-typing escape hatch)."
|
||||
|
||||
**Internal contradiction:** Lines 19, 73, 81 say `Metadata` IS `dict[str, Any]`. Lines 42, 59-61 say it IS NOT. Lines 73 says "underlying type is still dict[str, Any]" — which means the aliases (`CommsLogEntry = Metadata` etc.) are all still dicts. But line 75-77 introduces per-aggregate dataclasses which contradict this.
|
||||
|
||||
**Fix:** Rewrite the table at line 13-34 to reflect post-2026-06-25 reality:
|
||||
- Line 19 table: `Metadata` | `@dataclass(frozen=True, slots=True)` (36 fields) | The boundary type at TOML/JSON wire
|
||||
- Line 24 table: `FileItem` | `@dataclass(frozen=True)` | A single file in the context
|
||||
- Etc. — each per-aggregate alias should now point to its own dataclass, not to `Metadata`
|
||||
- Line 73: REMOVE the "underlying type is still dict[str, Any]" claim
|
||||
- Line 81: REMOVE the "keep `Metadata: TypeAlias = dict[str, Any]` as the catch-all" — `Metadata` IS a dataclass now
|
||||
|
||||
### C18 — `python.md` says banned but doesn't have lint enforcement for 3 of 7 banned patterns 🟡
|
||||
|
||||
**Claim:** `conductor/code_styleguides/python.md:402-413` says:
|
||||
- Line 403: `scripts/audit_weak_types.py --strict` — flags `dict[str, Any]`, `Any`, anonymous tuple returns ✅ EXISTS
|
||||
- Line 407: `scripts/audit_optional_in_3_files.py --strict` — flags `Optional[T]` in the 3 refactored files ✅ EXISTS (but named wrong, see C1)
|
||||
- The boundary-layer audit — planned in `conductor/tracks/cruft_elimination_20260627/spec.md` ❌ NOT BUILT
|
||||
- Line 413: `scripts/audit_imports.py (planned)` — flags local imports outside `try/except ImportError` blocks ❌ NOT BUILT
|
||||
|
||||
**Reality:** 7 banned patterns, only 2 have audit scripts. The boundary-layer audit and audit_imports are "planned" not "implemented".
|
||||
|
||||
**Fix:** Either build the missing audits OR explicitly mark them as "to-be-implemented, currently unenforced" so agents know what to actually check.
|
||||
|
||||
---
|
||||
|
||||
## Category 5: Result Migration Campaign Docs 🟡
|
||||
|
||||
### C19 — The 9 legacy `Result[T]` wrapper obliteration is documented but not in styleguide 🟡
|
||||
|
||||
**Claim:** `conductor/tracks/result_migration_cruft_removal_20260620/spec.md` documents the "OBLITERATE principle: no pass-throughs; no backward compat; in-site callers rewritten to use `_x_result(...).ok` directly; the dead code dies." This is a specific pattern that's enforced in the cleanup but isn't in `conductor/code_styleguides/`.
|
||||
|
||||
**Fix:** Add a "Result migration anti-patterns" section to `error_handling.md` documenting the OBLITERATE principle (when a function is migrated to Result, the legacy wrapper should be deleted; callers must be migrated in the same commit).
|
||||
|
||||
---
|
||||
|
||||
## Category 6: `cruft_elimination_20260627` state docs 🟡
|
||||
|
||||
### C20 — Phase 7 ("60 Any params + 11 dict[str, Any]") numbers don't match `audit_weak_types.py` baseline 🟡
|
||||
|
||||
**Claim 1 (spec):** `conductor/tracks/cruft_elimination_20260627/spec.md` G4 says "Zero `Any` parameter types in internal code. Same grep with `: Any` returns 0" — target is 60 sites removed.
|
||||
|
||||
**Claim 2 (audit baseline):** Per `boundary_layer_20260628.md` and the audit baseline, there are 60 `Any` params + 11 `dict[str, Any]` params in the migration-target 14 files (post-refactor). The `audit_weak_types.baseline.json` records the post-refactor count.
|
||||
|
||||
**Reality:** The `audit_weak_types.py --strict` checks against the baseline JSON. The baseline count must be the same as the spec's target. If the spec says "60 Any sites" but the audit baseline is higher, the spec is wrong. If the baseline is the same, the spec is consistent.
|
||||
|
||||
**Fix:** Reconcile `cruft_elimination_20260627/spec.md` G3 + G4 + `audit_weak_types.baseline.json` numbers. Add a line "Baseline at start of Phase 7: 60 Any + 11 dict[str, Any]" with the exact JSON reference.
|
||||
|
||||
---
|
||||
|
||||
## Category 7: Naming and Misc 🟢
|
||||
|
||||
### C21 — `audit_optional_in_3_files.py` checks 4 files 🟢
|
||||
|
||||
**Claim:** Filename says "3 files". `BASELINE_FILES` defines 4 files (mcp_client, ai_client, rag_engine, code_path_audit).
|
||||
|
||||
**Fix:** Rename to `audit_optional_in_baseline_files.py` (see C1).
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| # | Contradiction | Severity | Affected Files |
|
||||
|---|---|---|---|
|
||||
| C1 | `audit_optional_in_3_files.py` covers 4 files | 🟠 | `python.md`, `error_handling.md`, `docs/AGENTS.md` |
|
||||
| C2 | Optional[T] ban scope ambiguity | 🟠 | `error_handling.md`, `docs/AGENTS.md` |
|
||||
| C3 | `audit_imports.py` "planned" but never built | 🟠 | `python.md` |
|
||||
| C4 | Pre-commit hooks only in Tier 2 sandbox | 🟡 | `docs/AGENTS.md` |
|
||||
| C5 | `Result[str, ErrorInfo]` notation wrong | 🟠 | `guide_ai_client.md` |
|
||||
| C6 | `RAGChunk` schema missing `id: str` field | 🟠 | `guide_rag.md`, `guide_models.md` |
|
||||
| C7 | Provider count: Readme 5 vs guide 8 | 🟠 | `docs/Readme.md` |
|
||||
| C8 | Test count: Readme 322 vs guide 251 | 🟠 | `docs/Readme.md` |
|
||||
| C9 | Command count: Readme 50+ vs guide 33 | 🟠 | `docs/Readme.md` |
|
||||
| C10 | 12 dataclasses location split | 🟡 | `chronology.md`, `metadata_promotion_20260624/spec.md` |
|
||||
| C11 | `live_gui_test_fixes_20260618` "active" but shipped | 🟠 | `tracks.md` |
|
||||
| C12 | `test_sandbox_hardening_20260619` "ready to start" but shipped | 🟠 | `tracks.md` |
|
||||
| C13 | `metadata_promotion_20260624` status confusion | 🟠 | `tracks.md`, `chronology.md` |
|
||||
| C14 | `result_migration_20260616` parent stale | 🟡 | `tracks.md` |
|
||||
| C15 | `result_migration_baseline_cleanup_20260620` stale | 🟡 | `tracks.md`, `chronology.md` |
|
||||
| C16 | `python.md` §10 Anti-OOP vs App+AppController | 🟠 | `python.md` |
|
||||
| C17 | `type_aliases.md` line 19 table vs body | 🟠 | `type_aliases.md` |
|
||||
| C18 | 2/7 banned patterns have audit scripts | 🟡 | `python.md` |
|
||||
| C19 | OBLITERATE principle not in styleguide | 🟡 | `error_handling.md` |
|
||||
| C20 | cruft_elimination Phase 7 numbers vs baseline | 🟡 | `cruft_elimination_20260627/spec.md` |
|
||||
| C21 | `audit_optional_in_3_files.py` checks 4 | 🟢 | script filename |
|
||||
|
||||
---
|
||||
|
||||
## Recommended Fix Priority
|
||||
|
||||
### Tier 1 — Fix now (broken conventions)
|
||||
1. **C1+C21** — Rename `audit_optional_in_3_files.py` → `audit_optional_in_baseline_files.py` and decide whether to extend coverage to all `src/*.py` or document the 4-file scope honestly.
|
||||
2. **C2** — Decide whether the ban is enforceable globally; if yes, build the extension; if no, update `docs/AGENTS.md` to honestly say "enforced on 4 baseline files; see cruft_elimination_20260627 for the rest".
|
||||
3. **C3+C18** — Either build `scripts/audit_imports.py` and the boundary-layer audit, or explicitly mark them as to-be-implemented.
|
||||
4. **C5** — Replace `Result[str, ErrorInfo]` → `Result[str]` everywhere in `guide_ai_client.md`.
|
||||
5. **C16+C17** — Rewrite the contradictory sections of `python.md` §10 and `type_aliases.md` line 19 to reflect post-2026-06-25 reality.
|
||||
|
||||
### Tier 2 — Fix in next docs sync track
|
||||
6. **C6** — Update `RAGChunk` schema in guides.
|
||||
7. **C7+C8+C9** — Update counts in `docs/Readme.md`.
|
||||
8. **C11+C12+C13+C14+C15** — Reconcile `tracks.md` and `chronology.md` against actual shipped state.
|
||||
9. **C10** — Clarify dataclass location split in `metadata_promotion_20260624` spec.
|
||||
|
||||
### Tier 3 — Followup track (not blocking)
|
||||
10. **C4** — Decide whether main-repo pre-commit enforcement is needed.
|
||||
11. **C19** — Add OBLITERATE principle to `error_handling.md`.
|
||||
12. **C20** — Reconcile baseline numbers.
|
||||
Reference in New Issue
Block a user