Private
Public Access
feat(directives): scavenge from intent_dsl_survey + handoffs/: 5 directives
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# dsl_uses_first_class_spans_for_errors
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/intent_dsl_survey_20260612/spec.md:138` (Section 3.3 "AI-fuzzing tolerance rules" — "line/offset independence (parser uses token positions, not raw line numbers)"). The CoSy modulo-indexing parallel + the `{ }` recovery anchors are the same principle: stable token-level references, not source-level line numbers.
|
||||
**Source:** `conductor/tracks/intent_dsl_survey_20260612/spec.md:138`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from intent_dsl_survey track)
|
||||
@@ -0,0 +1,17 @@
|
||||
# DSL error messages and tolerance regions reference first-class token spans — not raw line numbers
|
||||
|
||||
## The span primitive
|
||||
|
||||
When the DSL parser reports an error or defines a parse-tolerance region, it uses a `Span { start: int, end: int, token_index: int }` — a position in the token stream — not a raw `line:column` from the source. The parser is line/offset-independent.
|
||||
|
||||
Per `conductor/tracks/intent_dsl_survey_20260612/spec.md:138`: "AI-fuzzing tolerance rules: CoSy-style modulo indexing, structured recovery anchors via `{ }`, **line/offset independence (parser uses token positions, not raw line numbers)**."
|
||||
|
||||
## Why
|
||||
|
||||
LLM-emitted DSL is not human-authored source code. The LLM may shift an expression by 5 lines between attempts, emit fuzzy whitespace, or duplicate a token. Line numbers are unstable for AI-emitted code; token positions (relative to the start of the token stream) are stable. A parser that uses raw line numbers throws confusing errors ("error on line 47" when the model emitted 30 lines) and breaks recovery on the next retry.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- `tests/test_dsl_parser.py` asserts that an error in a multi-line DSL expression reports a `Span` with `token_index`, not a `line:column`.
|
||||
- A fuzzy-tolerance region (`fuzzy { ... }` or `arena { ... }`) is defined by a `Span`; if the model's output shifts the region's tokens but keeps their order, the region is still valid.
|
||||
- Error messages include the surrounding tokens ("near `sum -> filter -> ...`"), not just the line.
|
||||
@@ -0,0 +1,9 @@
|
||||
# intent_signal_postfix_not_xml
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/intent_dsl_survey_20260612/spec.md:92-93` (Cluster 3 — "nagent's tag protocol — mentioned but explicitly rejected (no XML angle brackets, no JSON blobs)") + `conductor/tracks/intent_dsl_survey_20260612/spec.md:50` (Non-Goals: "ignore its record formats as they problably will be less xml/json based as I don't like them"). The rejection is explicit and user-stated, not an oversight.
|
||||
**Source:** `conductor/tracks/intent_dsl_survey_20260612/spec.md:50 + 92-93`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from intent_dsl_survey track)
|
||||
@@ -0,0 +1,25 @@
|
||||
# Intent DSL signals use non-XML postfix notation — XML angle brackets and JSON record blobs are explicitly rejected
|
||||
|
||||
## The signal format
|
||||
|
||||
The project's intent DSL (per `conductor/tracks/intent_dsl_survey_20260612/`) uses postfix and infix symbols for signals, not XML or JSON record formats. Examples:
|
||||
|
||||
- `->` (between verbs) — pipeline flow: output of left → input of right
|
||||
- `<-` (after a verb) — input binding: the right side is the producer
|
||||
- `[ ]` — basic block (Onat's compilation unit, no branching semantics)
|
||||
- `arena { }` — arena-scoped block (tape-drive region)
|
||||
- `try { ... } recover { ... }` — error envelope returning `Result[T]`
|
||||
|
||||
Per `conductor/tracks/intent_dsl_survey_20260612/spec.md:92-93`: "nagent's tag protocol — mentioned but explicitly rejected (no XML angle brackets, no JSON blobs)."
|
||||
|
||||
Per `conductor/tracks/intent_dsl_survey_20260612/spec.md:50`: "Not adopting XML/JSON record formats. Per the user: 'ignore its record formats as they problably will be less xml/json based as I don't like them.'"
|
||||
|
||||
## Why
|
||||
|
||||
XML angle brackets (`<nagent-shell>...</nagent-shell>`) and JSON record blobs (`{"name": "read_file", "args": {...}}`) require string-escaping discipline the LLM is bad at. The non-XML postfix/infix notation survives AI-fuzzing: indentation drift, line-offset drift, and verb-name drift do not break the parser because the symbols are short, distinct, and the parser uses token positions, not line numbers.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- New DSL primitives use postfix/infix symbols, not XML tags.
|
||||
- A new "tag-like" protocol MUST NOT be introduced in this project without checking against the intent_dsl_survey spec.
|
||||
- The bridge script (translating DSL to function calls) parses symbols, not XML/JSON.
|
||||
@@ -0,0 +1,9 @@
|
||||
# pipeline_immediate_mode_no_object
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/intent_dsl_survey_20260612/spec.md:189` (Section 5 Claim 2 — "the DSL's pipeline is immediate-mode in pipeline composition. Each `->`-delimited stage is a method invocation, not a Pipeline object."). The O'Donnell IMGUI parallel — "widgets are method invocations, not objects" — is the load-bearing argument; the project inherits the immediate-mode philosophy at the pipeline level.
|
||||
**Source:** `conductor/tracks/intent_dsl_survey_20260612/spec.md:189`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from intent_dsl_survey track)
|
||||
@@ -0,0 +1,24 @@
|
||||
# DSL pipelines are immediate-mode — there is no Pipeline object to query, name, or pass across executions
|
||||
|
||||
## The pipeline contract
|
||||
|
||||
A `->`-delimited chain of DSL verbs exists ONLY while the program is executing. Once execution ends, the pipeline's state is gone. There is no Pipeline object you can:
|
||||
|
||||
- Query for the current value
|
||||
- Name and pass to another verb
|
||||
- Re-use across executions
|
||||
- Hold in a variable
|
||||
|
||||
The only way to "name" a chain is to wrap it in a function: `func my_pipeline(x: T) -> U { ... }`.
|
||||
|
||||
Per `conductor/tracks/intent_dsl_survey_20260612/spec.md:189`: "the DSL's pipeline is *immediate-mode in pipeline composition*. Each `->`-delimited stage is a method invocation, not a Pipeline object. The pipeline exists *only* while the DSL program is being executed; once execution ends, the pipeline's state is gone. ... the `->` chain has no 'pipeline object' you can query, name, or pass around."
|
||||
|
||||
## Why
|
||||
|
||||
A retained Pipeline object would require the parser/VM to track state across executions, which (a) doubles the cognitive load on the parser, (b) makes pipelines harder to reason about, and (c) defeats the immediate-mode execution model the DSL inherits from O'Donnell's IMGUI / "widgets are method invocations, not objects."
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- The DSL parser does NOT construct an intermediate AST object that is retained after parse; it emits operations directly (per Onat's xchg model).
|
||||
- A pipeline value is consumed by the next verb in the chain or the function exits; there is no "the pipeline" as a first-class value.
|
||||
- Re-using a chain requires wrapping it in a function, not naming the chain itself.
|
||||
@@ -0,0 +1,9 @@
|
||||
# run_full_tier_after_phase_refactor
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52` (the "Lesson for the follow-up track" — run the FULL tier after a public-dataclass-signature refactor) + `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:113-114` (the explicit "This is the same lesson as §2.1: targeted tests don't surface call-site regressions in other files. Run the broader tier"). The post-mortem captured 13 test failures from two signature changes that targeted tests missed.
|
||||
**Source:** `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52 + 113-114`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from `docs/handoffs/`)
|
||||
@@ -0,0 +1,27 @@
|
||||
# After any Phase-style refactor, run the FULL affected test tier — targeted tests are insufficient to surface caller regressions
|
||||
|
||||
## The regression protocol
|
||||
|
||||
When a Phase-style refactor changes a public signature (function args, dataclass fields, class constructor), targeted regression tests are a *convenience subset*. They exercise the new path the agent was working on. They do NOT exercise every caller in the codebase.
|
||||
|
||||
The full affected tier (`tier-1-unit-core`, `tier-2-mock-app-core`, `tier-3-live_gui`) is the only verification that catches caller regressions in unrelated files.
|
||||
|
||||
Per `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52`: "after every Phase-2-style refactor that changes a public dataclass signature, run the FULL `tier-1-unit-core` tier (not just the targeted tests). The targeted test suite I picked was a convenience subset; the broader tier surfaces construction sites the targeted tests don't hit."
|
||||
|
||||
Per `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:113-114`: "This is the same lesson as §2.1: targeted tests don't surface call-site regressions in other files. Run the broader tier."
|
||||
|
||||
## Why (the post-mortem)
|
||||
|
||||
The `any_type_componentization_20260621` track:
|
||||
- Phase 2 changed `NormalizedResponse` from a 6-field dataclass to a 4-field dataclass with nested `UsageStats`.
|
||||
- The targeted tests (`test_ai_client_result.py`, `test_ai_client_no_top_level_sdk_imports.py`) passed.
|
||||
- The actual callers of the OLD signature in `tests/test_ai_client_tool_loop*.py`, `tests/test_ai_client_cli.py`, `tests/test_openai_compatible.py`, `tests/test_auto_whitelist.py` were never run.
|
||||
- 10 test failures surfaced 7-10 days later when the user ran `run_tests_batched.py`.
|
||||
|
||||
The same pattern repeated with `WebSocketServer.broadcast()`: targeted `tests/test_websocket_server.py` passed; the caller in `src/app_controller.py:_run_pending_tasks_once_result` was never exercised in the targeted run.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- Phase N commits are NOT complete until `uv run python scripts/run_tests_batched.py --tier <affected_tier(s)>` runs FULLY (no `--stop-on-failure`) and exits 0.
|
||||
- Targeted test files (the ones the agent wrote) are a subset; they pass for the wrong reasons.
|
||||
- The follow-up protocol after a Phase-style refactor: grep for callers in `src/` + `tests/`, migrate them, run the full tier, commit the migration + the green tier output.
|
||||
@@ -0,0 +1,9 @@
|
||||
# search_all_call_sites_after_signature_change
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52` (the "Lesson for the follow-up track" — run the FULL tier after a public-dataclass-signature refactor) + `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:114-119` (the "search for all callers" instruction). The post-mortem shows two real regressions from the `any_type_componentization_20260621` track: the `NormalizedResponse` field-shape change (10 test failures) and the `WebSocketServer.broadcast()` signature change (`worker[queue_fallback] error` spam).
|
||||
**Source:** `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52 + 114-119`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from `docs/handoffs/`)
|
||||
@@ -0,0 +1,22 @@
|
||||
# After a public API or dataclass signature change, search for ALL callers in src/ and tests/ and migrate them in the same atomic commit
|
||||
|
||||
## The post-mortem rule
|
||||
|
||||
When a refactor changes a public API — a function signature, a dataclass field shape, a class constructor — the migration is incomplete until every caller has been updated. Targeted regression tests pass with stale callers because they only exercise the new path; the broken callers surface later when the broader test tier or live_gui session hits them.
|
||||
|
||||
Per `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:51-52`: "after every Phase-2-style refactor that changes a public dataclass signature, run the FULL `tier-1-unit-core` tier (not just the targeted tests). The targeted test suite I picked was a convenience subset; the broader tier surfaces construction sites the targeted tests don't hit."
|
||||
|
||||
Per `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md:114-119`: "Tier 1 should plan to fix this in the follow-up track. Search for all `broadcast(channel` calls in `src/` ... The fix is mechanical: replace `broadcast(channel, payload)` with `broadcast(WebSocketMessage(channel=, payload=))`."
|
||||
|
||||
## Why (post-mortem from `any_type_componentization_20260621`)
|
||||
|
||||
The track changed `NormalizedResponse` from a 6-field dataclass to a 4-field dataclass with a nested `UsageStats`. The Phase 2 commit landed; the targeted regression tests passed. Three callers in `src/ai_client.py` (`_send_gemini_cli`, `_send_grok`, `_send_minimax`) still constructed `NormalizedResponse` with the old kwargs. The broken callers surfaced in a later batch run (`tier-1-unit-core`), 7-10 days after the change.
|
||||
|
||||
The same lesson applied to a separate `WebSocketServer.broadcast(channel, payload) → broadcast(WebSocketMessage)` refactor: the targeted test passed; `src/app_controller.py:_run_pending_tasks_once_result` and `src/events.py` still used the old signature, producing `worker[queue_fallback] error: WebSocketServer.broadcast() takes 2 positional arguments but 3 were given` spam in `tier-2-mock-app-core`.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- After any Phase-style refactor, the same atomic commit MUST include a grep + migration of every caller in `src/` and `tests/`.
|
||||
- The grep pattern is the OLD signature as a substring; the migration replaces each callsite.
|
||||
- The follow-up regression run uses the FULL affected tier (not targeted tests), and that run is gated by the same commit.
|
||||
- A signature change with a "deferred to followup track" note is a sign the refactor was incomplete.
|
||||
Reference in New Issue
Block a user