From f4dfb8468141113a4a55758647566ada2066f5ea Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 2 Jul 2026 21:49:13 -0400 Subject: [PATCH] =?UTF-8?q?feat(directives):=20harvest=207=20directives=20?= =?UTF-8?q?from=20python.md=20=C2=A717.1-17.7=20(banned=20patterns=20+=20b?= =?UTF-8?q?oundary=20exception)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conductor/directives/ban_any_type/v1.md | 22 ++++++++++++ conductor/directives/ban_dict_any/v1.md | 28 +++++++++++++++ .../ban_dict_get_on_known_fields/v1.md | 21 +++++++++++ .../directives/ban_getattr_dispatch/v1.md | 21 +++++++++++ .../directives/ban_hasattr_dispatch/v1.md | 36 +++++++++++++++++++ .../directives/ban_optional_returns/v1.md | 31 ++++++++++++++++ .../directives/boundary_layer_exception/v1.md | 13 +++++++ 7 files changed, 172 insertions(+) create mode 100644 conductor/directives/ban_any_type/v1.md create mode 100644 conductor/directives/ban_dict_any/v1.md create mode 100644 conductor/directives/ban_dict_get_on_known_fields/v1.md create mode 100644 conductor/directives/ban_getattr_dispatch/v1.md create mode 100644 conductor/directives/ban_hasattr_dispatch/v1.md create mode 100644 conductor/directives/ban_optional_returns/v1.md create mode 100644 conductor/directives/boundary_layer_exception/v1.md diff --git a/conductor/directives/ban_any_type/v1.md b/conductor/directives/ban_any_type/v1.md new file mode 100644 index 00000000..597d095f --- /dev/null +++ b/conductor/directives/ban_any_type/v1.md @@ -0,0 +1,22 @@ +# ban_any_type — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.2 (lines 267-277). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:267-277` + +--- + +### 17.2 Banned: `Any` + +```python +# BANNED: +def _to_typed_tool_call(tc: Any) -> ToolCall: + return ToolCall(id=getattr(tc, "id", "") or "", ...) + +# CORRECT: +def _parse_wire_tool_call(wire: dict[str, Any]) -> ToolCall: + """Boundary: parse MCP wire dict to typed ToolCall.""" + return ToolCall.from_dict(wire) +``` \ No newline at end of file diff --git a/conductor/directives/ban_dict_any/v1.md b/conductor/directives/ban_dict_any/v1.md new file mode 100644 index 00000000..78c84647 --- /dev/null +++ b/conductor/directives/ban_dict_any/v1.md @@ -0,0 +1,28 @@ +# ban_dict_any — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.1 (lines 247-265). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:247-265` + +--- + +### 17.1 Banned: `dict[str, Any]` + +```python +# BANNED: +def process(event: dict[str, Any]) -> None: + if event.get("kind") == "tool_call": + +# BANNED: +flat: dict[str, Any] = project_manager.flat_config(...) + +# CORRECT: +def process(event: CommsLogEntry) -> None: + if event.kind == "tool_call": + +# CORRECT (boundary only): +def _parse_wire(raw: str) -> Metadata: + return Metadata.from_dict(tomllib.loads(raw)) +``` \ No newline at end of file diff --git a/conductor/directives/ban_dict_get_on_known_fields/v1.md b/conductor/directives/ban_dict_get_on_known_fields/v1.md new file mode 100644 index 00000000..6ecfabb0 --- /dev/null +++ b/conductor/directives/ban_dict_get_on_known_fields/v1.md @@ -0,0 +1,21 @@ +# ban_dict_get_on_known_fields — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.6 (lines 340-350). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:340-350` + +--- + +### 17.6 Banned: `.get('field', default)` on a `dict[str, Any]` + +```python +# BANNED: +tier = entry.get('source_tier', 'main') +model = entry.get('model', 'unknown') + +# CORRECT (direct attribute access on the typed dataclass): +tier = entry.source_tier +model = entry.model +``` \ No newline at end of file diff --git a/conductor/directives/ban_getattr_dispatch/v1.md b/conductor/directives/ban_getattr_dispatch/v1.md new file mode 100644 index 00000000..dbae5a05 --- /dev/null +++ b/conductor/directives/ban_getattr_dispatch/v1.md @@ -0,0 +1,21 @@ +# ban_getattr_dispatch — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.5 (lines 328-338). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:328-338` + +--- + +### 17.5 Banned: `getattr(x, 'field', default)` for type dispatch + +```python +# BANNED: +tool_id = getattr(tc, "id", "") or "" +tool_name = getattr(tc.function, "name", "") or "" + +# CORRECT: +tool_id = tc.id +tool_name = tc.function.name +``` \ No newline at end of file diff --git a/conductor/directives/ban_hasattr_dispatch/v1.md b/conductor/directives/ban_hasattr_dispatch/v1.md new file mode 100644 index 00000000..8ee288a7 --- /dev/null +++ b/conductor/directives/ban_hasattr_dispatch/v1.md @@ -0,0 +1,36 @@ +# ban_hasattr_dispatch — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.4 (lines 300-326). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:300-326` + +--- + +### 17.4 Banned: `hasattr()` for entity type dispatch + +```python +# BANNED: +def handle_event(self, event: Metadata) -> None: + if hasattr(event, 'tool_calls'): + # tool call path + elif hasattr(event, 'source_tier'): + # mma path + elif hasattr(event, 'path'): + # file path + +# CORRECT (typed Union dispatch): +def handle_event(self, event: CommsLogEntry | FileItem | HistoryMessage) -> None: + if isinstance(event, CommsLogEntry): + # mma path + elif isinstance(event, FileItem): + # file path + elif isinstance(event, HistoryMessage): + # tool call path + +# CORRECT (preferred — refactor so no dispatch is needed): +def _handle_comms_entry(self, event: CommsLogEntry) -> None: ... +def _handle_file_item(self, event: FileItem) -> None: ... +def _handle_history(self, event: HistoryMessage) -> None: ... +``` \ No newline at end of file diff --git a/conductor/directives/ban_optional_returns/v1.md b/conductor/directives/ban_optional_returns/v1.md new file mode 100644 index 00000000..8fb8aa0f --- /dev/null +++ b/conductor/directives/ban_optional_returns/v1.md @@ -0,0 +1,31 @@ +# ban_optional_returns — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.3 (lines 279-298). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:279-298` + +--- + +### 17.3 Banned: `Optional[T]` returns + +```python +# BANNED: +def find_ticket(self, id: str) -> Optional[Ticket]: + for t in self.active_tickets: + if t.id == id: return t + return None # ← silent failure; consumer has to None-check + +# CORRECT (Result pattern): +def find_ticket(self, id: str) -> Result[Ticket]: + for t in self.active_tickets: + if t.id == id: return Result(data=t) + return Result(data=NIL_TICKET, errors=[ErrorInfo(...)]) # drain point handles + +# CORRECT (NIL_T sentinel — preferred when consumer just reads fields): +def find_ticket(self, id: str) -> Ticket: + for t in self.active_tickets: + if t.id == id: return t + return NIL_TICKET # zero-initialized frozen dataclass; safe to read fields +``` \ No newline at end of file diff --git a/conductor/directives/boundary_layer_exception/v1.md b/conductor/directives/boundary_layer_exception/v1.md new file mode 100644 index 00000000..a528cf2b --- /dev/null +++ b/conductor/directives/boundary_layer_exception/v1.md @@ -0,0 +1,13 @@ +# boundary_layer_exception — v1 + +**Why this iteration:** Lifted verbatim from `conductor/code_styleguides/python.md` §17.7 (lines 352-354). +This is the baseline encoding — the imperative-ban style currently in production. +Future variants will test alternative encodings (rationale-first, before/after, tabular) against this baseline. + +**Source:** `conductor/code_styleguides/python.md:352-354` + +--- + +### 17.7 The one exception: the boundary layer + +The ONLY place these patterns are allowed is at the literal wire boundary — the function that calls `tomllib.load()`, `json.loads()`, or a vendor SDK's response parser. The boundary is 2-3 functions per file. Every consumer IMMEDIATELY converts to a typed dataclass via `from_dict()`. \ No newline at end of file