diff --git a/docs/specs/002-wishful-context/implementation-plan.md b/docs/specs/002-wishful-context/implementation-plan.md index 2ff2acf..20717da 100644 --- a/docs/specs/002-wishful-context/implementation-plan.md +++ b/docs/specs/002-wishful-context/implementation-plan.md @@ -18,6 +18,15 @@ *GATE: Read all files before starting implementation.* +**Current Status (2026-06-03 refresh)**: +- `wishful.evolve()` is implemented and exported. +- `wishful.context` is still unimplemented. +- 002 should finish with registered context available to `evolve()`, + `explore()`, and `wishful.static.*` / `wishful.dynamic.*` imports. +- This repo uses `AGENTS.md` as the live agent architecture/instructions file; + there is no `CLAUDE.md`. +- Always run project Python commands through `uv run`. + **Specification**: - `docs/specs/002-wishful-context/product-requirements.md` - PRD - `docs/specs/002-wishful-context/solution-design.md` - SDD @@ -25,18 +34,31 @@ **Reference Implementation**: - `src/wishful/types/registry.py` - Pattern to mirror exactly - `src/wishful/types/__init__.py` - Export pattern +- `src/wishful/evolve/evolver.py` - Public evolve loop and mutation call site +- `src/wishful/evolve/mutation.py` - Prompt context builder to extend +- `src/wishful/explore/explorer.py` - String-path variant generation +- `src/wishful/core/loader.py` - Static/dynamic generation +- `src/wishful/config.py` - Settings and environment variables +- `src/wishful/cache/manager.py` - Static cache behavior **Key Design Decisions**: 1. Mirror `@wishful.type` pattern exactly 2. Use `for_=` parameter name 3. Global registry only (no scopes) -4. List targets register for each with priority order +4. List targets register the same provider for each target; priority for a target is registration order +5. Registered context applies to evolve, explore, static, and dynamic by default +6. Static imports stay cache-first by default; context-change invalidation is opt-in **Commands**: ```bash uv run pytest tests/test_context.py -v # Context tests uv run pytest tests/test_evolve.py -v # Verify evolve still works -uv run mypy src/wishful/context/ # Type check +uv run pytest tests/test_explore.py -v # Verify explore context integration +uv run pytest tests/test_import_hook.py -v # Verify static import behavior +uv run pytest tests/test_namespaces.py -v # Verify dynamic behavior +uv run pytest tests/test_config.py -v # Verify settings +uv run mypy src/wishful/context/ src/wishful/evolve/ # Type check +uv run python -c "from wishful import context, get_context_for; print('OK')" ``` --- @@ -61,6 +83,7 @@ uv run mypy src/wishful/context/ # Type check - [ ] T1.2.7 Test: `test_clear_registry` - [ ] T1.2.8 Test: `test_multiple_contexts_same_target` - [ ] T1.2.9 Test: `test_docstring_extraction` + - [ ] T1.2.10 Test: `test_rejects_callable_instance_target_without_stable_key` - [ ] T1.3 Implement `[activity: component-development]` - [ ] T1.3.1 Create `src/wishful/context/__init__.py` with exports @@ -71,31 +94,55 @@ uv run mypy src/wishful/context/ # Type check - [ ] `context(for_=)` decorator - [ ] `get_context_for(target)` function - [ ] `clear_context_registry()` function + - [ ] T1.3.3 Create `src/wishful/context/formatting.py` with: + - [ ] `build_context_block(targets, surface, base_context=None)` + - [ ] target expansion for exact vs module fallback + - [ ] max-entry limiting + - [ ] surface enable/disable checks - [ ] T1.4 Validate `[activity: run-tests]` - [ ] T1.4.1 Run `uv run pytest tests/test_context.py -v` - [ ] T1.4.2 Run `uv run mypy src/wishful/context/` - - [ ] T1.4.3 Verify import: `python -c "from wishful.context import context, get_context_for"` + - [ ] T1.4.3 Verify import: `uv run python -c "from wishful.context import context, get_context_for"` --- -### Phase 2: Public API Export +### Phase 2: Public API and Settings -- [ ] T2 Phase 2: Wishful Package Integration `[component: wishful]` +- [ ] T2 Phase 2: Wishful Package Integration and Settings `[component: wishful]` - [ ] T2.1 Prime Context - [ ] T2.1.1 Read `src/wishful/__init__.py` for export pattern + - [ ] T2.1.2 Read `src/wishful/config.py` for settings pattern - [ ] T2.2 Write Tests `[activity: test-execution]` - [ ] T2.2.1 Test: `test_context_importable_from_wishful` - [ ] T2.2.2 Test: `test_get_context_for_importable_from_wishful` + - [ ] T2.2.3 Test: context settings default values + - [ ] T2.2.4 Test: `configure()` updates context settings + - [ ] T2.2.5 Test: environment variables populate context settings + - [ ] T2.2.6 Test: `reset_defaults()` restores context settings - [ ] T2.3 Implement `[activity: component-development]` - [ ] T2.3.1 Modify `src/wishful/__init__.py` to export `context`, `get_context_for` + - [ ] T2.3.2 Add settings: + - [ ] `context_enabled: bool = True` + - [ ] `context_surfaces: tuple[str, ...] = ("evolve", "explore", "static", "dynamic")` + - [ ] `context_lookup: str = "exact_then_module"` + - [ ] `context_static_cache_policy: str = "cache_first"` + - [ ] `context_max_entries: int = 8` + - [ ] T2.3.3 Add env vars: + - [ ] `WISHFUL_CONTEXT` + - [ ] `WISHFUL_CONTEXT_SURFACES` + - [ ] `WISHFUL_CONTEXT_LOOKUP` + - [ ] `WISHFUL_CONTEXT_STATIC_CACHE_POLICY` + - [ ] `WISHFUL_CONTEXT_MAX_ENTRIES` + - [ ] T2.3.4 Update `configure()`, `Settings.copy()`, and `reset_defaults()` - [ ] T2.4 Validate `[activity: run-tests]` - - [ ] T2.4.1 Run `python -c "from wishful import context, get_context_for; print('OK')"` - - [ ] T2.4.2 Run full test suite: `uv run pytest tests/ -v` + - [ ] T2.4.1 Run `uv run python -c "from wishful import context, get_context_for; print('OK')"` + - [ ] T2.4.2 Run `uv run pytest tests/test_config.py -v` + - [ ] T2.4.3 Run full test suite: `uv run pytest tests/ -v` --- @@ -105,17 +152,21 @@ uv run mypy src/wishful/context/ # Type check - [ ] T3.1 Prime Context - [ ] T3.1.1 Read `src/wishful/evolve/mutation.py` current implementation - - [ ] T3.1.2 Read SDD Integration section `[ref: SDD; lines: 192-227]` + - [ ] T3.1.2 Read `src/wishful/evolve/evolver.py` current implementation + - [ ] T3.1.3 Read SDD Integration section - [ ] T3.2 Write Tests `[activity: test-execution]` - [ ] T3.2.1 Test: `test_build_evolution_context_includes_registered_context` - [ ] T3.2.2 Test: `test_build_evolution_context_no_context_registered` - [ ] T3.2.3 Test: `test_context_docstring_included_in_prompt` + - [ ] T3.2.4 Test: public `evolve()` passes the original target function into mutation context lookup - [ ] T3.3 Implement `[activity: component-development]` - [ ] T3.3.1 Add `target_function: Callable | None = None` param to `_build_evolution_context()` - [ ] T3.3.2 Add context lookup and formatting logic - - [ ] T3.3.3 Update `mutate_with_llm()` to pass target function + - [ ] T3.3.3 Add `target_function: Callable | None = None` param to `mutate_with_llm()` + - [ ] T3.3.4 Update `mutate_with_llm()` to pass target function to `_build_evolution_context()` + - [ ] T3.3.5 Update `evolve()` to call `mutate_with_llm(..., target_function=fn)` with the original target function - [ ] T3.4 Validate `[activity: run-tests]` - [ ] T3.4.1 Run `uv run pytest tests/test_evolve.py -v` @@ -124,47 +175,115 @@ uv run mypy src/wishful/context/ # Type check --- -### Phase 4: Documentation & Spec Updates +### Phase 4: Explore Integration + +- [ ] T4 Phase 4: Explore Integration `[component: explore]` + + - [ ] T4.1 Prime Context + - [ ] T4.1.1 Read `src/wishful/explore/explorer.py` + - [ ] T4.1.2 Read SDD Explore Integration section + + - [ ] T4.2 Write Tests `[activity: test-execution]` + - [ ] T4.2.1 Test: `explore()` includes exact target registered context + - [ ] T4.2.2 Test: module-level context is included when `context_lookup="exact_then_module"` + - [ ] T4.2.3 Test: module-level context is excluded when `context_lookup="exact"` + - [ ] T4.2.4 Test: explore skips context when `"explore"` is not in `context_surfaces` + - [ ] T4.2.5 Test: `test` and `benchmark` callables are not automatically embedded + + - [ ] T4.3 Implement `[activity: component-development]` + - [ ] T4.3.1 Build exact explore target from `module_name` and `function_name` + - [ ] T4.3.2 Pass `build_context_block(..., surface="explore")` result to `agenerate_module_code(...)` + - [ ] T4.3.3 Preserve winner caching behavior + + - [ ] T4.4 Validate `[activity: run-tests]` + - [ ] T4.4.1 Run `uv run pytest tests/test_explore.py -v` + - [ ] T4.4.2 Run `uv run pytest tests/test_context.py -v` + +--- + +### Phase 5: Static/Dynamic Import Integration + +- [ ] T5 Phase 5: Static/Dynamic Import Integration `[component: import-hook]` + + - [ ] T5.1 Prime Context + - [ ] T5.1.1 Read `src/wishful/core/loader.py` + - [ ] T5.1.2 Read `src/wishful/core/discovery.py` + - [ ] T5.1.3 Read `src/wishful/cache/manager.py` + - [ ] T5.1.4 Read SDD Static/Dynamic Integration section + + - [ ] T5.2 Write Tests `[activity: test-execution]` + - [ ] T5.2.1 Test: static cache miss includes registered exact function context + - [ ] T5.2.2 Test: static cache miss merges registered context with discovered import-site context + - [ ] T5.2.3 Test: existing static cache wins when `context_static_cache_policy="cache_first"` + - [ ] T5.2.4 Test: static cache regenerates on context fingerprint change when `context_static_cache_policy="invalidate_on_change"` + - [ ] T5.2.5 Test: static skips registered context when `context_static_cache_policy="ignore"` + - [ ] T5.2.6 Test: dynamic generation includes registered context on each regeneration + - [ ] T5.2.7 Test: multiple requested symbols include symbol-specific context + - [ ] T5.2.8 Test: module-level context is included/excluded according to `context_lookup` + + - [ ] T5.3 Implement `[activity: component-development]` + - [ ] T5.3.1 Add loader helper to build context targets from `fullname` and requested functions + - [ ] T5.3.2 Merge registered context into `context.context` before `generate_module_code(...)` + - [ ] T5.3.3 Preserve static `cache_first` path before generation + - [ ] T5.3.4 Add optional context fingerprint metadata for `invalidate_on_change` + - [ ] T5.3.5 Ensure dynamic mode includes registered context on proxy/runtime regeneration + + - [ ] T5.4 Validate `[activity: run-tests]` + - [ ] T5.4.1 Run `uv run pytest tests/test_import_hook.py -v` + - [ ] T5.4.2 Run `uv run pytest tests/test_namespaces.py -v` + - [ ] T5.4.3 Run `uv run pytest tests/test_context.py -v` + +--- + +### Phase 6: Documentation & Spec Updates -- [ ] T4 Phase 4: Update 001-wishful-evolve Documentation `[component: docs]` +- [ ] T6 Phase 6: Documentation, Examples, and Spec Updates `[component: docs]` - - [ ] T4.1 Update Evolve Implementation Plan - - [ ] T4.1.1 Add note about `@wishful.context` integration in `docs/specs/001-wishful-evolve/implementation-plan.md` - - [ ] T4.1.2 Update Phase 3 (Core Evolver) to pass `target_function` to mutation + - [ ] T6.1 Update Evolve Implementation Plan + - [ ] T6.1.1 Add note about `@wishful.context` integration in `docs/specs/001-wishful-evolve/implementation-plan.md` + - [ ] T6.1.2 Update Phase 3 (Core Evolver) to pass `target_function` to mutation - - [ ] T4.2 Update Evolve Design Docs - - [ ] T4.2.1 Add context integration section to `docs/specs/001-wishful-evolve/implementation-plan.md` - - [ ] T4.2.2 Add context follow-up notes to `docs/specs/003-wishful-code-search-workbench/concept-plan.md` + - [ ] T6.2 Update Workbench Direction Docs + - [ ] T6.2.1 Add context integration section to `docs/specs/003-wishful-code-search-workbench/concept-plan.md` + - [ ] T6.2.2 Document which tricky behavior is configurable - - [ ] T4.3 Update CLAUDE.md - - [ ] T4.3.1 Add `wishful.context` to architecture section - - [ ] T4.3.2 Update session history + - [ ] T6.3 Update AGENTS.md + - [ ] T6.3.1 Add `wishful.context` to architecture section + - [ ] T6.3.2 Update repository layout and test list if needed - - [ ] T4.4 Create Tryout Scripts - - [ ] T4.4.1 Create `examples/15_context_basic.py` - - [ ] T4.4.2 Create `examples/16_context_with_evolve.py` - - [ ] T4.4.3 Document context examples in `README.md` + - [ ] T6.4 Create Examples + - [ ] T6.4.1 Create `examples/15_context_basic.py` + - [ ] T6.4.2 Create `examples/16_context_with_evolve.py` + - [ ] T6.4.3 Create `examples/17_context_with_explore.py` + - [ ] T6.4.4 Create `examples/18_context_with_static_import.py` + - [ ] T6.4.5 Document context examples and settings in `README.md` --- -### Phase 5: Final Validation +### Phase 7: Final Validation -- [ ] T5 Integration & End-to-End Validation +- [ ] T7 Integration & End-to-End Validation - - [ ] T5.1 All unit tests passing: `uv run pytest tests/test_context.py -v` - - [ ] T5.2 All evolve tests passing: `uv run pytest tests/test_evolve.py -v` - - [ ] T5.3 Full test suite passing: `uv run pytest tests/ -v` - - [ ] T5.4 Type checking passes: `uv run mypy src/wishful/context/ src/wishful/evolve/` - - [ ] T5.5 Import verification: `from wishful import context, get_context_for` - - [ ] T5.6 Test coverage ≥90% for new code - - [ ] T5.7 Tryout scripts run successfully - - [ ] T5.8 PRD acceptance criteria verified: + - [ ] T7.1 All unit tests passing: `uv run pytest tests/test_context.py -v` + - [ ] T7.2 All evolve tests passing: `uv run pytest tests/test_evolve.py -v` + - [ ] T7.3 All explore tests passing: `uv run pytest tests/test_explore.py -v` + - [ ] T7.4 Import-hook tests passing: `uv run pytest tests/test_import_hook.py tests/test_namespaces.py -v` + - [ ] T7.5 Full test suite passing: `uv run pytest tests/ -v` + - [ ] T7.6 Type checking passes: `uv run mypy src/wishful/context/ src/wishful/evolve/` + - [ ] T7.7 Import verification: `uv run python -c "from wishful import context, get_context_for; print('OK')"` + - [ ] T7.8 Test coverage ≥90% for new code + - [ ] T7.9 Tryout scripts run successfully + - [ ] T7.10 PRD acceptance criteria verified: - [ ] `@wishful.context(for_=target)` works - [ ] Supports function references - [ ] Supports string paths - [ ] Supports list of targets - [ ] `get_context_for()` returns source + docstring - [ ] Integrates with `_build_evolution_context()` + - [ ] Integrates with `explore()` + - [ ] Integrates with static import generation + - [ ] Integrates with dynamic import generation + - [ ] Context settings work through `configure()` and env vars --- @@ -174,7 +293,11 @@ uv run mypy src/wishful/context/ # Type check - [ ] Test coverage ≥90% for `src/wishful/context/` - [ ] `wishful.context` importable from main package - [ ] Works with evolve's `_build_evolution_context()` -- [ ] Documentation updated (CLAUDE.md, evolve docs) +- [ ] Works with explore variant generation +- [ ] Works with static and dynamic import generation +- [ ] Static cache remains cache-first by default +- [ ] Context settings documented and tested +- [ ] Documentation updated (AGENTS.md, README, evolve/workbench docs) - [ ] Tryout scripts demonstrate usage - [ ] Type hints complete, mypy passes @@ -185,5 +308,8 @@ uv run mypy src/wishful/context/ # Type check | Risk | Mitigation | |------|------------| | Circular import with evolve | Lazy import in `_build_evolution_context()` | -| Source extraction fails | Fallback to `repr()` with warning | +| Source extraction fails | Store a descriptive stub string with provider name/module so prompts remain readable | | Target resolution ambiguous | Clear error messages, documentation | +| Context bloats prompts | `context_max_entries` and exact-only lookup setting | +| Static cache surprises users | Default `cache_first`, explicit `invalidate_on_change` opt-in | +| Context changes do not affect existing cached static modules | Document default clearly and offer `regenerate()` or `invalidate_on_change` | diff --git a/docs/specs/002-wishful-context/product-requirements.md b/docs/specs/002-wishful-context/product-requirements.md index e9b46bc..29c2387 100644 --- a/docs/specs/002-wishful-context/product-requirements.md +++ b/docs/specs/002-wishful-context/product-requirements.md @@ -17,15 +17,27 @@ ### Vision Enable developers to declaratively attach contextual information (fitness functions, constraints, examples, hints) to wishful-generated functions, making LLM generation smarter and more targeted. +### Current Status +This spec was refreshed on 2026-06-03 after the public `wishful.evolve()` loop merged. +`wishful.context` is still unimplemented. The next implementation should make +registered context available across the three generation surfaces: + +- `wishful.evolve()` +- `wishful.explore()` +- `wishful.static.*` and `wishful.dynamic.*` imports + ### Problem Statement When wishful generates or evolves code, the LLM lacks crucial context: - **evolve()**: LLM sees fitness *scores* but not *what's being measured* - **explore()**: LLM generates variants without knowing constraints or goals -- **static**: Generation happens without domain-specific hints or examples +- **static/dynamic imports**: Generation happens without durable domain-specific hints or examples beyond nearby import-site code Without this context, the LLM is essentially guessing what "good" means. -**Evidence**: During evolve() Phase 2 implementation, we discovered that `_build_evolution_context()` passes history with scores (90, 55, 25) but never explains WHY those scores differ. The LLM must reverse-engineer the fitness function from code patterns. +**Evidence**: The merged evolve spine passes prior attempts and fitness scores to +`_build_evolution_context()`, but it still does not include the fitness function, +constraints, examples, or acceptance rationale. The LLM must reverse-engineer why +one variant scored better than another from source patterns alone. ### Value Proposition `@wishful.context` provides a declarative, composable way to give the LLM exactly the context it needs - making generation smarter without changing the core wishful API. @@ -42,32 +54,61 @@ Without this context, the LLM is essentially guessing what "good" means. - [ ] `@wishful.context(for_=target)` decorator registers context - [ ] Supports function references: `@wishful.context(for_=sort)` - [ ] Supports string paths: `@wishful.context(for_="module.func")` - - [ ] Works with functions, classes, and any callable + - [ ] Works with functions and classes that have stable module-qualified names #### Feature 2: Multiple Targets - **User Story:** As a developer, I want one context to serve multiple functions so that I don't repeat myself. - **Acceptance Criteria:** - [ ] `@wishful.context(for_=[sort, search])` registers for both targets - - [ ] Order in list determines priority (first = highest) + - [ ] Each target keeps context entries in registration order #### Feature 3: Context Registry - **User Story:** As wishful internals, I need to look up all context for a given target. - **Acceptance Criteria:** - [ ] `get_context_for(target)` returns list of context sources - [ ] Returns source code of decorated items - - [ ] Respects priority ordering + - [ ] Respects registration-order priority - [ ] Returns empty list if no context registered -#### Feature 4: Integration Point -- **User Story:** As wishful.evolve (and future features), I need to include context in LLM prompts. +#### Feature 4: Evolve Integration +- **User Story:** As `wishful.evolve`, I need to include registered context in mutation prompts so the LLM understands what the fitness signal means. - **Acceptance Criteria:** - [ ] Context can be retrieved and formatted for LLM consumption - [ ] Works with `_build_evolution_context()` in evolve module + - [ ] `evolve()` passes the target function through to mutation so context lookup can use the original callable - [ ] Pattern matches existing `@wishful.type` infrastructure +#### Feature 5: Explore Integration +- **User Story:** As `wishful.explore`, I need registered context for the function path being explored so variants are generated against known constraints and goals. +- **Acceptance Criteria:** + - [ ] Context registered for the exact explore target path is included in `agenerate_module_code(...)` + - [ ] Module-level context can be included by default when configured + - [ ] Existing `test` and `benchmark` callables remain execution-only unless explicitly registered as context providers + - [ ] Winning variant caching behavior remains explicit and documented + +#### Feature 6: Static/Dynamic Import Integration +- **User Story:** As a user importing `wishful.static.*` or `wishful.dynamic.*`, I need registered context to augment import-site context during generation. +- **Acceptance Criteria:** + - [ ] Registered context is merged with existing discovered import-site context + - [ ] Context can apply to exact function targets and module-level targets + - [ ] Multiple requested symbols receive their own exact context where available + - [ ] Static imports preserve cache-first behavior by default + - [ ] Dynamic imports include registered context on every regeneration by default + +#### Feature 7: Context Settings +- **User Story:** As an advanced user, I need to control when and how context is applied so I can choose between stability, freshness, and prompt size. +- **Acceptance Criteria:** + - [ ] `wishful.configure(...)` exposes context behavior settings + - [ ] Environment variables expose the same settings for scripts and CI + - [ ] Users can enable/disable context globally + - [ ] Users can choose which surfaces receive registered context + - [ ] Users can choose exact-only vs exact-plus-module lookup + - [ ] Users can choose static cache policy for context changes + - [ ] Users can cap the number of context entries included in a prompt + ### Should Have Features -#### Feature 5: Docstring Extraction +#### Feature 8: Docstring Extraction - **User Story:** As a developer, I want my context function's docstring to be included as human-readable explanation. - **Acceptance Criteria:** - [ ] Docstrings are extracted and included in context @@ -79,6 +120,8 @@ Without this context, the LLM is essentially guessing what "good" means. - **Context validation** - No runtime checking that context is "correct" - **Auto-discovery** - No scanning modules for context, must be explicit - **Priority override** - No way to re-order after registration +- **Callable-instance targets** - Function and class targets are supported first; arbitrary callable objects are out of scope until there is a clear stable-key rule +- **Executing context providers for structured data** - v1 includes source and docstrings only; evaluating providers comes later --- @@ -111,12 +154,41 @@ contexts = wishful.context.get_context_for(sort) - Rule 1: Context is registered at decoration time (import time) - Rule 2: Multiple contexts for same target are allowed - Rule 3: Order of registration = priority order -- Rule 4: String targets resolved at lookup time, not registration +- Rule 4: String targets are stored as stable keys; callable lookup normalizes to the same key format +- Rule 5: Registered context is enabled by default across evolve, explore, static, and dynamic generation +- Rule 6: Static cache remains cache-first by default; changing context does not invalidate an existing static cache unless configured **Edge Cases:** -- Target doesn't exist yet (string path) → Register anyway, resolve later +- Target doesn't exist yet (string path) → Register anyway; later callable lookup matches if it normalizes to the same path - No context for target → Return empty list (not an error) - Same context decorated twice → Register twice (user's choice) +- Existing static cache with newly registered context → cache wins by default; user can call `wishful.regenerate(...)` or enable context-change invalidation + +### Default Behavior and Controls + +Defaults should make context useful without surprising users: + +- `context_enabled=True` +- `context_surfaces=("evolve", "explore", "static", "dynamic")` +- `context_lookup="exact_then_module"` +- `context_static_cache_policy="cache_first"` +- `context_max_entries=8` + +Control surface: + +- `wishful.configure(context_enabled=False)` disables registered context everywhere. +- `wishful.configure(context_surfaces=("evolve", "explore"))` limits where context is used. +- `wishful.configure(context_lookup="exact")` disables module-level fallback. +- `wishful.configure(context_static_cache_policy="invalidate_on_change")` lets context changes regenerate static cache entries. +- `wishful.configure(context_max_entries=3)` caps prompt expansion. + +Equivalent environment variables should exist for scripts: + +- `WISHFUL_CONTEXT` +- `WISHFUL_CONTEXT_SURFACES` +- `WISHFUL_CONTEXT_LOOKUP` +- `WISHFUL_CONTEXT_STATIC_CACHE_POLICY` +- `WISHFUL_CONTEXT_MAX_ENTRIES` --- @@ -125,6 +197,9 @@ contexts = wishful.context.get_context_for(sort) | Criterion | Measurement | |-----------|-------------| | Works with evolve() | Context appears in LLM prompt during evolution | +| Works with explore() | Context appears in variant-generation prompt | +| Works with static/dynamic imports | Context appears alongside discovered import-site context | +| Stable defaults | Static cache behavior remains cache-first unless configured | | Pattern consistency | API feels like `@wishful.type` | | No breaking changes | Existing wishful code unaffected | | Test coverage | ≥90% for new code | @@ -134,13 +209,15 @@ contexts = wishful.context.get_context_for(sort) ## Constraints and Assumptions ### Constraints -- Must follow existing wishful patterns (`@wishful.type`, `@wishful.static`) +- Must follow existing wishful patterns (`@wishful.type`, `wishful.evolve`) - Must not require changes to existing user code +- Must preserve `wishful.static.*` cache-first behavior by default +- Must preserve `wishful.dynamic.*` fresh-generation behavior - Must work with Python 3.12+ ### Assumptions - Global registry is sufficient for initial release -- Users will provide context as functions or classes (not arbitrary objects) +- Users will provide context providers as functions or classes (not arbitrary instances) - Source code extraction via `inspect.getsource()` or `__wishful_source__` is reliable --- @@ -149,5 +226,7 @@ contexts = wishful.context.get_context_for(sort) - [x] Naming: `for_=` vs `target=` → **Decision: `for_=`** (most natural English) - [x] Multiple targets: Yes, via list -- [x] Priority: Order in list +- [x] Priority: Registration order for contexts under the same target - [x] Scope: Global only for now +- [x] Surfaces: v1 applies context to evolve, explore, static, and dynamic generation +- [x] Static cache default: cache-first, with explicit opt-in invalidation on context changes diff --git a/docs/specs/002-wishful-context/solution-design.md b/docs/specs/002-wishful-context/solution-design.md index 1d43ae7..f8df736 100644 --- a/docs/specs/002-wishful-context/solution-design.md +++ b/docs/specs/002-wishful-context/solution-design.md @@ -18,6 +18,15 @@ ## Implementation Context +### Current Status + +This design was refreshed on 2026-06-03 after `wishful.evolve()` shipped. +The implementation should target all live generation surfaces: + +- `evolve()` in `src/wishful/evolve/evolver.py` and `src/wishful/evolve/mutation.py` +- `explore()` in `src/wishful/explore/explorer.py` +- static/dynamic import generation in `src/wishful/core/loader.py` + ### Required Context Sources ```yaml @@ -32,13 +41,37 @@ - file: src/wishful/evolve/mutation.py relevance: HIGH why: "Integration point - _build_evolution_context() will use context registry" + +- file: src/wishful/evolve/evolver.py + relevance: HIGH + why: "Call site that must pass the original target function to mutate_with_llm" + +- file: src/wishful/explore/explorer.py + relevance: HIGH + why: "String-path generation surface that should include registered context" + +- file: src/wishful/core/loader.py + relevance: HIGH + why: "Static/dynamic import generation surface that merges discovered import context with registered context" + +- file: src/wishful/config.py + relevance: HIGH + why: "Settings and environment-variable control surface" + +- file: src/wishful/cache/manager.py + relevance: MEDIUM + why: "Static cache policy and optional context-fingerprint metadata" + +- file: AGENTS.md + relevance: MEDIUM + why: "Live repo instructions and architecture doc to update; this repo has no CLAUDE.md" ``` ### Implementation Boundaries -- **Must Preserve**: Existing `@wishful.type` behavior, `evolve()` API -- **Can Modify**: `_build_evolution_context()` to include registered context -- **Must Not Touch**: Core wishful generation logic +- **Must Preserve**: Existing `@wishful.type` behavior, public `evolve()` API, public `explore()` API, and static cache-first behavior by default +- **Can Modify**: `config.py`, `explore/explorer.py`, `core/loader.py`, `evolve/*`, and cache metadata helpers +- **Must Not Touch**: static/dynamic namespace routing semantics, safety validation policy, or the core litellm client contract ### Project Commands @@ -46,13 +79,17 @@ # Testing uv run pytest tests/test_context.py -v # Context tests uv run pytest tests/test_evolve.py -v # Verify evolve still works +uv run pytest tests/test_explore.py -v # Verify explore context integration +uv run pytest tests/test_import_hook.py -v # Verify static/dynamic generation +uv run pytest tests/test_namespaces.py -v # Verify dynamic namespace behavior +uv run pytest tests/test_config.py -v # Verify context settings uv run pytest tests/ -v # Full suite # Type checking -uv run mypy src/wishful/context/ +uv run mypy src/wishful/context/ src/wishful/evolve/ # Validation -python -c "from wishful import context; print('OK')" +uv run python -c "from wishful import context; print('OK')" ``` --- @@ -60,7 +97,7 @@ python -c "from wishful import context; print('OK')" ## Solution Strategy - **Architecture Pattern**: Mirror `@wishful.type` exactly -- **Integration Approach**: Add optional context lookup to LLM prompt building +- **Integration Approach**: Build one context-formatting helper and call it from evolve, explore, and static/dynamic import generation - **Justification**: Proven pattern, predictable for users, minimal new concepts --- @@ -74,6 +111,8 @@ graph LR User[Developer] -->|decorates| Context[@wishful.context] Context -->|registers| Registry[ContextRegistry] Evolve[evolve/mutation.py] -->|looks up| Registry + Explore[explore/explorer.py] -->|looks up| Registry + Loader[core/loader.py] -->|looks up| Registry Registry -->|returns| Sources[Context Sources] Sources -->|included in| LLMPrompt[LLM Prompt] ``` @@ -84,12 +123,26 @@ graph LR src/wishful/ ├── context/ # NEW MODULE │ ├── __init__.py # NEW: Public exports -│ └── registry.py # NEW: ContextRegistry + decorator +│ ├── registry.py # NEW: ContextRegistry + decorator +│ └── formatting.py # NEW: prompt block builder + target expansion +├── config.py # MODIFY: context settings + env vars ├── __init__.py # MODIFY: Add context exports +├── cache/ +│ └── manager.py # MODIFY: optional static context fingerprint metadata +├── core/ +│ └── loader.py # MODIFY: merge registered context into import generation +├── explore/ +│ └── explorer.py # MODIFY: include context in variant generation └── evolve/ + ├── evolver.py # MODIFY: Pass original target function to mutation └── mutation.py # MODIFY: Integrate context lookup tests/ -└── test_context.py # NEW: Unit tests +├── test_context.py # NEW: Unit tests +├── test_evolve.py # MODIFY: Context integration tests +├── test_explore.py # MODIFY: Explore context tests +├── test_import_hook.py # MODIFY: Static import context tests +├── test_namespaces.py # MODIFY: Dynamic context tests if needed +└── test_config.py # MODIFY: Context setting tests ``` ### Data Models @@ -111,6 +164,15 @@ class ContextRegistry: def clear(self) -> None: ... def _resolve_key(self, target: Any) -> str: ... def _extract_source(self, provider: Any) -> str: ... + +@dataclass +class ContextLookupOptions: + """Resolved settings for prompt context lookup.""" + enabled: bool + surfaces: tuple[str, ...] + lookup: Literal["exact", "exact_then_module"] + static_cache_policy: Literal["cache_first", "invalidate_on_change", "ignore"] + max_entries: int ``` ### API Surface @@ -125,6 +187,14 @@ def get_context_for(target: Any) -> list[dict]: def clear_context_registry() -> None: """Clear registry (for testing).""" + +def build_context_block( + targets: list[Any], + *, + surface: Literal["evolve", "explore", "static", "dynamic"], + base_context: str | None = None, +) -> str | None: + """Merge discovered context and registered context into one prompt block.""" ``` --- @@ -136,19 +206,47 @@ def clear_context_registry() -> None: ``` 1. Module imports → @wishful.context(for_=sort) executes 2. Decorator calls _registry.register(provider, for_=sort) -3. Registry resolves target to key: "mymodule.sort" +3. Registry normalizes target to key: "mymodule.sort" 4. Registry extracts source + docstring from provider 5. Registry stores ContextEntry in _contexts["mymodule.sort"] ``` ### Lookup Flow +#### Evolve + ``` -1. evolve() calls _build_evolution_context(target_function=sort) -2. _build_evolution_context calls get_context_for(sort) -3. Registry resolves sort → "mymodule.sort" -4. Registry returns list of ContextEntry dicts -5. Context included in LLM prompt under "ADDITIONAL CONTEXT" +1. `evolve()` calls `mutate_with_llm(target_function=sort, ...)` +2. `mutate_with_llm()` passes `target_function` to `_build_evolution_context()` +3. `_build_evolution_context()` calls `build_context_block([sort], surface="evolve")` +4. Context is included under `ADDITIONAL CONTEXT` +``` + +#### Explore + +``` +1. `explore("wishful.static.text.extract_emails", ...)` splits module and function +2. Explorer builds target keys: + - exact: `"wishful.static.text.extract_emails"` + - module fallback when enabled: `"wishful.static.text"` +3. Explorer passes the merged context string to `agenerate_module_code(...)` +4. Test and benchmark callables remain execution-only unless explicitly registered +5. Winner caching remains unchanged unless static cache settings opt into context fingerprints +``` + +#### Static/Dynamic Imports + +``` +1. Loader calls `discover(fullname)` and receives import-site context plus requested symbols +2. Loader builds registered-context targets: + - module target: `fullname` + - exact function targets: `f"{fullname}.{symbol}"` for each requested symbol +3. Loader merges registered context with `ImportContext.context` +4. Static mode: + - default `cache_first`: existing cache wins + - `invalidate_on_change`: context fingerprint can invalidate stale cache + - `ignore`: static import generation skips registered context +5. Dynamic mode regenerates every time, so registered context is included on every generation by default ``` ### Target Resolution Logic @@ -156,13 +254,53 @@ def clear_context_registry() -> None: ```python def _resolve_key(self, target: Any) -> str: if isinstance(target, str): - return target # Already a string path - elif callable(target): + return target # Already a stable key; no import resolution + elif callable(target) and hasattr(target, "__module__") and hasattr(target, "__qualname__"): return f"{target.__module__}.{target.__qualname__}" else: - raise TypeError(f"Target must be callable or string, got {type(target)}") + raise TypeError( + "Target must be a string path or a function/class with a stable " + f"module-qualified name, got {type(target)}" + ) ``` +String targets are not imported or resolved during registration. They are stored +as stable keys so code can register context for generated or not-yet-importable +targets. Later lookups by callable match when the callable normalizes to the +same module-qualified key. + +Generated import targets should use their public import paths: + +- exact function context: `"wishful.static.text.extract_emails"` +- module-level context: `"wishful.static.text"` +- dynamic equivalents: `"wishful.dynamic.text.extract_emails"` and `"wishful.dynamic.text"` + +Static and dynamic are mode-specific by default. Users who want shared context +can register one provider for both targets with `for_=[...]`. + +### Context Settings + +Add these fields to `Settings`, `configure(...)`, and `reset_defaults()`: + +| Setting | Env var | Default | Meaning | +|---------|---------|---------|---------| +| `context_enabled: bool` | `WISHFUL_CONTEXT` | `True` | Master switch for registered context | +| `context_surfaces: tuple[str, ...]` | `WISHFUL_CONTEXT_SURFACES` | `("evolve", "explore", "static", "dynamic")` | Surfaces that receive registered context | +| `context_lookup: str` | `WISHFUL_CONTEXT_LOOKUP` | `"exact_then_module"` | Include exact target only, or exact plus module fallback | +| `context_static_cache_policy: str` | `WISHFUL_CONTEXT_STATIC_CACHE_POLICY` | `"cache_first"` | Static cache behavior for context changes | +| `context_max_entries: int` | `WISHFUL_CONTEXT_MAX_ENTRIES` | `8` | Max registered context entries per prompt | + +Allowed values: + +- `context_lookup`: `"exact"`, `"exact_then_module"` +- `context_static_cache_policy`: `"cache_first"`, `"invalidate_on_change"`, `"ignore"` + +Static cache policy semantics: + +- `"cache_first"`: existing static cache wins; registered context affects only cache misses and explicit regeneration. +- `"invalidate_on_change"`: static cache stores a context fingerprint and regenerates when the fingerprint changes. +- `"ignore"`: static imports do not include registered context, even when context is enabled elsewhere. + --- ## Architecture Decisions @@ -182,11 +320,21 @@ def _resolve_key(self, target: Any) -> str: - Trade-offs: Can't have module-local contexts - User confirmed: ✅ (from earlier discussion) -- [x] **ADR-4**: List targets = register for each with priority order - - Rationale: DRY for shared context, priority by position - - Trade-offs: Implicit priority might surprise some users +- [x] **ADR-4**: List targets = register the same provider for each target + - Rationale: DRY for shared context without duplicating decorators + - Trade-offs: Priority is still per target and follows registration order - User confirmed: ✅ (from earlier discussion) +- [x] **ADR-5**: Context applies to all generation surfaces in 002 + - Rationale: Context is a cross-cutting prompt primitive, not an evolve-only feature + - Trade-offs: More tests and settings are required to keep cache behavior explicit + - User confirmed: ✅ (2026-06-03 refresh) + +- [x] **ADR-6**: Static imports stay cache-first by default + - Rationale: `wishful.static.*` means cached and consistent; context should not cause surprise regeneration by default + - Trade-offs: Users must opt into `invalidate_on_change` or call `regenerate()` when changing context for existing static cache files + - User confirmed: ✅ (2026-06-03 refresh) + --- ## Integration with evolve() @@ -196,6 +344,23 @@ def _resolve_key(self, target: Any) -> str: ```python # In _build_evolution_context(), add after history section: +def mutate_with_llm( + source: str, + mutation_prompt: str, + function_name: str, + history: List[dict], + target_function: Callable | None = None, # NEW parameter +) -> str: + context = _build_evolution_context( + source, + mutation_prompt, + function_name, + history, + target_function=target_function, + ) + ... + + def _build_evolution_context( source: str, mutation_prompt: str, @@ -206,28 +371,78 @@ def _build_evolution_context( ... # NEW: Include registered context if target_function: - from wishful.context import get_context_for - contexts = get_context_for(target_function) - if contexts: - parts.extend([ - "-" * 60, - "ADDITIONAL CONTEXT (provided by developer):", - "-" * 60, - "", - ]) - for ctx in contexts: - if ctx.get("docstring"): - parts.append(f"# {ctx['docstring']}") - parts.extend([ - "```python", - ctx["source"], - "```", - "", - ]) + from wishful.context import build_context_block + registered_context = build_context_block( + [target_function], + surface="evolve", + ) + if registered_context: + parts.append(registered_context) +``` + +`evolve()` should pass the original function `fn` as `target_function`, not the +compiled candidate. The developer attached context to the target being evolved, +and that target stays stable while candidates change. + +--- + +## Integration with explore() + +In `_generate_and_evaluate_async()`, build context once per variant generation +from the `module_name` and `function_name` that `explore()` already parses: + +```python +target = f"{module_name}.{function_name}" +context = build_context_block([target], surface="explore") +source = await asyncio.wait_for( + agenerate_module_code(module_name, [function_name], context), + timeout=timeout, +) +``` + +`test` and `benchmark` functions should not be automatically embedded into the +prompt. If users want those definitions visible to the LLM, they should register +them explicitly: + +```python +@wishful.context(for_="wishful.static.text.extract_emails") +def email_extraction_context(): + """Must pass fixture-based tests and preserve input order.""" + ... ``` --- +## Integration with static/dynamic imports + +In `MagicLoader._generate_and_cache()`, merge registered context into the +discovered context before calling `generate_module_code(...)`. + +```python +registered_context = build_context_block( + [self.fullname, *[f"{self.fullname}.{name}" for name in functions]], + surface=self.mode, + base_context=context.context, +) + +source = gen_fn( + self.fullname, + functions, + registered_context, + type_schemas=context.type_schemas, + function_output_types=context.function_output_types, + mode=self.mode, +) +``` + +Static mode must check the cache before generating when +`context_static_cache_policy="cache_first"`. For `invalidate_on_change`, add a +small metadata sidecar or equivalent cache helper that stores the context +fingerprint for the cached module. Dynamic mode does not need fingerprinting +because it already regenerates. + +--- + ## Test Specifications ### Critical Test Scenarios @@ -251,6 +466,22 @@ Scenario: Multiple contexts for same target Scenario: No context registered When get_context_for(unknown_func) is called Then empty list returned (not an error) + +Scenario: Explore includes registered context + Given context registered for "wishful.static.text.extract_emails" + When explore generates variants for that path + Then agenerate_module_code receives the registered context block + +Scenario: Static import preserves cache-first behavior + Given cached source exists for wishful.static.text + And new context is registered for wishful.static.text.extract_emails + When context_static_cache_policy is cache_first + Then the cached source is used without regeneration + +Scenario: Dynamic import includes registered context every time + Given context registered for wishful.dynamic.text.extract_emails + When dynamic import generation runs + Then the registered context block is included in each generation ``` ### Test Coverage Requirements @@ -258,7 +489,11 @@ Scenario: No context registered - Registry: register, get_for, clear, key resolution - Decorator: with function, with string, with list - Source extraction: functions, classes, with/without docstrings -- Integration: context appears in evolve prompt +- Evolve integration: `evolve()` passes the original target function to mutation +- Explore integration: context appears in variant-generation prompt +- Static integration: context merges with import-site context on cache miss or configured invalidation +- Dynamic integration: context merges with runtime/import-site context on every regeneration +- Settings: global enable, surface selection, lookup mode, static cache policy, max entries --- @@ -268,6 +503,17 @@ Scenario: No context registered |------|--------|-------------| | `src/wishful/context/__init__.py` | CREATE | Public exports | | `src/wishful/context/registry.py` | CREATE | ContextRegistry + decorator | +| `src/wishful/context/formatting.py` | CREATE | Target expansion and prompt block formatting | +| `src/wishful/config.py` | MODIFY | Context settings and env vars | | `src/wishful/__init__.py` | MODIFY | Add `context, get_context_for` | -| `src/wishful/evolve/mutation.py` | MODIFY | Add `target_function` param | +| `src/wishful/explore/explorer.py` | MODIFY | Include context in variant generation | +| `src/wishful/core/loader.py` | MODIFY | Include context in static/dynamic generation | +| `src/wishful/cache/manager.py` | MODIFY | Optional context fingerprint metadata for static cache | +| `src/wishful/evolve/mutation.py` | MODIFY | Add `target_function` param to `mutate_with_llm()` and `_build_evolution_context()` | +| `src/wishful/evolve/evolver.py` | MODIFY | Pass the original `fn` into `mutate_with_llm(target_function=fn)` | | `tests/test_context.py` | CREATE | Unit tests | +| `tests/test_evolve.py` | MODIFY | Cover context prompt integration from the public evolve loop | +| `tests/test_explore.py` | MODIFY | Cover explore prompt context | +| `tests/test_import_hook.py` | MODIFY | Cover static prompt context and cache-first policy | +| `tests/test_namespaces.py` | MODIFY | Cover dynamic prompt context | +| `tests/test_config.py` | MODIFY | Cover context settings | diff --git a/docs/specs/003-wishful-code-search-workbench/concept-plan.md b/docs/specs/003-wishful-code-search-workbench/concept-plan.md index a06ba4f..a23f62b 100644 --- a/docs/specs/003-wishful-code-search-workbench/concept-plan.md +++ b/docs/specs/003-wishful-code-search-workbench/concept-plan.md @@ -19,6 +19,9 @@ Current branch reality: - The public `evolve()` loop is implemented and exported. - `wishful.context` is specified but not implemented. - The CLI/dashboard wireframe exists, but evidence/casefile mechanics do not. +- `wishful.evolve()` currently accepts callables, not import-address strings. String + target evolution is part of the future CLI/evidence surface, not the current + public Python API. ## Core Thesis @@ -112,6 +115,160 @@ A Wishful function should eventually carry: - evidence scope - accept/rollback state +## Codex-Threaded Active Imports + +The next step after "function with lineage" is an active import: generated code +that owns a resumable engineering relationship with a local Codex thread. + +The phrase: + +```text +Codex turns generated software from an artifact into a relationship. +``` + +This is the part that makes the Codex SDK matter for Wishful. The current +official Codex SDK docs describe programmatic thread creation, continuing the +same thread with later prompts, resuming a past thread by thread ID, and setting +sandbox presets such as `read_only` and `workspace_write` per thread or turn. +That is enough to make a generated function's maintainer state explicit instead +of metaphorical. + +For Wishful, the active import shape is: + +```text +missing import +-> generated implementation +-> generated or attached contract +-> tests and proof gates +-> casefile +-> persisted Codex thread ID +-> future failure reports resume the same thread +``` + +A generated function stops being only cached source. It becomes a maintained +code object: + +```json +{ + "symbol": "wishful.static.text.extract_invoice_fields", + "source_path": ".wishful/text.py", + "spec_path": ".wishful/specs/text.extract_invoice_fields.json", + "tests_path": ".wishful/tests/test_text_extract_invoice_fields.py", + "thread_id": "codex-thread-...", + "authority": { + "generation": "workspace_write", + "review": "read_only", + "allowed_roots": [".wishful", "tests/generated"] + }, + "proof": { + "required": ["ast_safety", "pytest", "ruff"], + "approval_required_for": [ + "behavior_change", + "new_dependency", + "filesystem_access" + ] + } +} +``` + +### The Import That Keeps Its Promise + +The smallest killer demo is not "AI wrote a parser." It is: + +```python +from wishful.static.dates import parse_fuzzy_date +``` + +First run: + +- Wishful generates the implementation. +- The active import record stores source, context, proof policy, and thread ID. +- Tests and static safety checks produce a casefile. +- The accepted function is cached as normal Python. + +Later, a production case fails: + +```python +wishful.report_failure( + "wishful.static.dates.parse_fuzzy_date", + input="next Friday after Easter in Berlin", + observed="2026-04-10", + expected="2026-04-03", + reason="timezone and holiday logic misread", +) +``` + +The same Codex thread resumes against: + +- the original import context +- the current cached source +- the recorded proof policy +- previous failures and rejected variants +- the new failing case + +It proposes a patch, adds a regression test, runs proof gates, writes a casefile, +and asks for approval before meaningful mutation. The psychological hit is: + +```text +The function remembered why it existed, learned from a failure, patched itself, +and left receipts. +``` + +### Tendril Boundary + +Tendril is the right graph memory and proof companion, but it should not be a +first-slice dependency for Wishful active imports. + +The local Tendril architecture treats graph objects as active work cells with +scoped runtime state, authority envelopes, proof requirements, and proposal +history. Its mutation loop is deliberately governed: + +```text +artifact -> proposal -> proof -> review -> apply +``` + +Proof checks cover source grounding, duplicates, weak rationale, unresolved +contradictions, topology drift, and authority-boundary violations before graph +mutation. This maps cleanly onto Wishful casefiles: + +- Wishful produces a function-level casefile as an artifact. +- Tendril can ingest that artifact and propose graph nodes or edges for reusable + lessons such as "locale ambiguity affects date parsing and invoice parsing." +- Tendril proof decides whether the graph may learn the relationship. +- Accepted graph changes carry provenance back to the casefile. + +So the first Wishful loop should stay local: + +```text +active import -> failure report -> resumed Codex thread -> patch -> proof -> casefile +``` + +Then Tendril can optionally receive the casefile: + +```text +casefile -> Tendril proposal -> Tendril proof -> review -> graph memory +``` + +This keeps the systems composable. Wishful owns generated-code maintenance. +Tendril owns graph learning and governed memory. + +### Danger Line + +The weirdness needs a leash. The default posture should be: + +```text +Generate freely. +Patch in sandbox. +Prove mechanically. +Explain in a casefile. +Require approval for meaningful mutation. +Commit only after review. +``` + +Do not let the first demo silently mutate real source. Active imports are only +interesting if the resident maintainer is bounded, inspectable, and forced to +prove its work. + ## Demo Selection Filter Bad demos: @@ -309,6 +466,13 @@ result = wishful.evolve( result.accept() ``` +This is a future surface sketch. For the current post-merge code, `evolve()` +takes a callable target. The 002 context work should still support string +targets in the registry because `explore()` and static/dynamic imports are +string-path surfaces. By the end of 002, registered context should apply to +`evolve()`, `explore()`, and static/dynamic generation, with settings for cache +and lookup behavior. + CLI surface: ```bash @@ -382,7 +546,8 @@ Complete: Then complete: - `docs/specs/002-wishful-context/implementation-plan.md` -- `@wishful.context` registry and evolve integration +- `@wishful.context` registry, settings, and integration across evolve, + explore, static, and dynamic generation ### Phase 1: Evidence-First Evolve Result @@ -476,8 +641,8 @@ Wishful has crossed from joke to serious when: `accept()` once evidence exists? 2. Should `fitness` return only a float, or a richer object with metrics and notes? -3. Should `@wishful.context` allow returning structured data, or only source and - docstring for v1? +3. After v1 source/docstring context, should `@wishful.context` also evaluate + providers and capture structured return data? 4. Should casefiles live under `.wishful/evidence/` or `.wishful/runs/`? 5. Should demos live in `examples/` or `demos/`? 6. Which first flagship demo do we build: parser gauntlet or hot path forge? @@ -488,8 +653,11 @@ When resuming this work: ```bash cd /home/pyro/projects/private/wishful -git switch feat/001-wishful-evolve +git switch main +git pull --ff-only origin main +git switch -c feat/002-wishful-context uv sync +uv run pytest tests/test_evolve.py -q WISHFUL_FAKE_LLM=1 uv run pytest tests/test_evolve.py -v ``` @@ -500,5 +668,5 @@ Then read: 3. `docs/specs/002-wishful-context/implementation-plan.md` 4. this file -Start by finishing the public `evolve()` loop before touching casefiles, -CLI, demos, or dashboard work. +Start by implementing `docs/specs/002-wishful-context/implementation-plan.md` +before touching casefiles, CLI, demos, or dashboard work.