diff --git a/checkpoint.json b/checkpoint.json new file mode 100644 index 000000000..02c2329fe --- /dev/null +++ b/checkpoint.json @@ -0,0 +1,52 @@ +{ + "timestamp": "2026-03-15T22:54:07.295492Z", + "current_node": "toolchain", + "completed_nodes": [ + "start", + "toolchain" + ], + "node_retries": { + "start": 1, + "toolchain": 1 + }, + "context_values": { + "graph.goal": "# Random Edge Selection\n\n## Context\n\nEdge selection is currently fully deterministic — ties broken by highest weight then lexical node ID. This adds a `selection=\"random\"` node attribute so outgoing edges use weighted-random tiebreaking instead. The cascade priority (conditions → preferred label → suggested next → unconditional → fallback) is unchanged; randomness only replaces the pick-one-from-candidates step. Validation rejects `selection=\"random\"` combined with conditional edges.\n\n## Approach: Red/Green TDD\n\nWrite failing tests first, then write the minimum code to make them pass. Each step below is a red→green cycle. Run `cargo test -p fabro-workflows` after each change.\n\n---\n\n### Step 1: RED — `Node::selection()` accessor tests\n\nAdd tests to `graph/types.rs` `mod tests`:\n- `node_selection_default`: `Node::new(\"x\").selection()` == `\"deterministic\"` → fails (method doesn't exist)\n- `node_selection_random`: node with `selection=\"random\"` attr → `selection()` == `\"random\"` → fails\n\n### Step 2: GREEN — `Node::selection()` accessor\n\nAdd to `Node` impl in `graph/types.rs` (~after `backend()`, line 236):\n```rust\npub fn selection(&self) -> &str {\n self.str_attr(\"selection\").unwrap_or(\"deterministic\")\n}\n```\n\n---\n\n### Step 3: RED — `weighted_random` tests\n\nAdd tests to `engine.rs` `mod tests`:\n- `weighted_random_empty_returns_none` → fails (function doesn't exist)\n- `weighted_random_single_edge` → fails\n- `weighted_random_zero_weight_all_selected`: 200 trials, both targets seen at least once → fails\n- `weighted_random_high_weight_dominates`: weight 100 vs 1, 500 trials, heavy >90% → fails\n\n### Step 4: GREEN — `weighted_random` function\n\nAdd after `best_by_weight_then_lexical` (~line 386) in `engine.rs`:\n- `weight <= 0` treated as 1 for probability calculation\n- Uses `rand::thread_rng()` (already imported at line 11)\n- Manual weighted sampling loop\n\n---\n\n### Step 5: RED — `select_edge` with `selection` param tests\n\nUpdate all ~8 existing `select_edge` tests to pass `\"deterministic\"` as 5th arg → fails (signature mismatch).\n\nAdd new tests:\n- `select_edge_random_returns_some_edge`: two unconditional edges, `selection=\"random\"` → fails\n- `select_edge_random_preferred_label_still_wins`: preferred label overrides random → fails\n\n### Step 6: GREEN — `select_edge` signature + `pick_edge` dispatcher\n\n**a)** Add `pick_edge` dispatcher:\n```rust\nfn pick_edge<'a>(edges: &[&'a Edge], selection: &str) -> Option<&'a Edge> {\n match selection {\n \"random\" => weighted_random(edges),\n _ => best_by_weight_then_lexical(edges),\n }\n}\n```\n\n**b)** Add `selection: &str` param to `select_edge`, replace 3 `best_by_weight_then_lexical` calls (lines 411, 442, 446) with `pick_edge(..., selection)`.\n\n**c)** Update 2 call sites to pass `node.selection()`:\n- Line 1548 (skip by StageStart hook)\n- Line 1759 (normal execution)\n\n---\n\n### Step 7: RED — validation rule tests\n\nAdd tests to `validation/rules.rs` `mod tests`:\n- `random_selection_no_conditions_clean`: random node + unconditional edges → no diagnostics → fails (rule doesn't exist)\n- `random_selection_with_conditions_errors`: random node + conditional edge → 1 Error diagnostic → fails\n- `deterministic_selection_with_conditions_ok`: deterministic node + conditional edges → no diagnostics → fails\n\n### Step 8: GREEN — validation Rule 23\n\nAdd `RandomSelectionNoConditionsRule` struct + `LintRule` impl:\n- Iterates nodes with `selection()==\"random\"`, checks outgoing edges for conditions\n- Error severity, message: `Node '{id}' has selection=\"random\" but also has conditional edges; random selection and conditions cannot be combined`\n- Fix: `Remove the condition attributes from outgoing edges, or remove selection=\"random\" from the node`\n- Register in `built_in_rules()` vec (line 35)\n\n---\n\n### Step 9: Docs (no TDD cycle)\n\n**`docs/workflows/transitions.mdx`:**\n- Update intro paragraph (line 6): `\"deterministic by default\"`, link to random selection section\n- Add \"Random selection\" section after \"Weight tiebreaking\" (~after line 157) with DOT example and `` about validation constraint\n\n**`docs/reference/dot-language.mdx`:**\n- Add `selection` row to \"All nodes\" attribute table (~line 191)\n\n---\n\n### Step 10: Final verification\n\n1. `cargo test -p fabro-workflows` — all tests pass\n2. `cargo clippy --workspace -- -D warnings` — clean\n3. `cargo fmt --check --all` — formatted\n\n## Files\n\n| File | Change |\n|---|---|\n| `lib/crates/fabro-workflows/src/graph/types.rs` | `selection()` accessor + tests |\n| `lib/crates/fabro-workflows/src/engine.rs` | `weighted_random`, `pick_edge`, modify `select_edge` sig + call sites + tests |\n| `lib/crates/fabro-workflows/src/validation/rules.rs` | Rule 23 + register + tests |\n| `docs/workflows/transitions.mdx` | Random selection section |\n| `docs/reference/dot-language.mdx` | `selection` attribute row |\n", + "command.stderr": "", + "current.preamble": "Goal: # Random Edge Selection\n\n## Context\n\nEdge selection is currently fully deterministic — ties broken by highest weight then lexical node ID. This adds a `selection=\"random\"` node attribute so outgoing edges use weighted-random tiebreaking instead. The cascade priority (conditions → preferred label → suggested next → unconditional → fallback) is unchanged; randomness only replaces the pick-one-from-candidates step. Validation rejects `selection=\"random\"` combined with conditional edges.\n\n## Approach: Red/Green TDD\n\nWrite failing tests first, then write the minimum code to make them pass. Each step below is a red→green cycle. Run `cargo test -p fabro-workflows` after each change.\n\n---\n\n### Step 1: RED — `Node::selection()` accessor tests\n\nAdd tests to `graph/types.rs` `mod tests`:\n- `node_selection_default`: `Node::new(\"x\").selection()` == `\"deterministic\"` → fails (method doesn't exist)\n- `node_selection_random`: node with `selection=\"random\"` attr → `selection()` == `\"random\"` → fails\n\n### Step 2: GREEN — `Node::selection()` accessor\n\nAdd to `Node` impl in `graph/types.rs` (~after `backend()`, line 236):\n```rust\npub fn selection(&self) -> &str {\n self.str_attr(\"selection\").unwrap_or(\"deterministic\")\n}\n```\n\n---\n\n### Step 3: RED — `weighted_random` tests\n\nAdd tests to `engine.rs` `mod tests`:\n- `weighted_random_empty_returns_none` → fails (function doesn't exist)\n- `weighted_random_single_edge` → fails\n- `weighted_random_zero_weight_all_selected`: 200 trials, both targets seen at least once → fails\n- `weighted_random_high_weight_dominates`: weight 100 vs 1, 500 trials, heavy >90% → fails\n\n### Step 4: GREEN — `weighted_random` function\n\nAdd after `best_by_weight_then_lexical` (~line 386) in `engine.rs`:\n- `weight <= 0` treated as 1 for probability calculation\n- Uses `rand::thread_rng()` (already imported at line 11)\n- Manual weighted sampling loop\n\n---\n\n### Step 5: RED — `select_edge` with `selection` param tests\n\nUpdate all ~8 existing `select_edge` tests to pass `\"deterministic\"` as 5th arg → fails (signature mismatch).\n\nAdd new tests:\n- `select_edge_random_returns_some_edge`: two unconditional edges, `selection=\"random\"` → fails\n- `select_edge_random_preferred_label_still_wins`: preferred label overrides random → fails\n\n### Step 6: GREEN — `select_edge` signature + `pick_edge` dispatcher\n\n**a)** Add `pick_edge` dispatcher:\n```rust\nfn pick_edge<'a>(edges: &[&'a Edge], selection: &str) -> Option<&'a Edge> {\n match selection {\n \"random\" => weighted_random(edges),\n _ => best_by_weight_then_lexical(edges),\n }\n}\n```\n\n**b)** Add `selection: &str` param to `select_edge`, replace 3 `best_by_weight_then_lexical` calls (lines 411, 442, 446) with `pick_edge(..., selection)`.\n\n**c)** Update 2 call sites to pass `node.selection()`:\n- Line 1548 (skip by StageStart hook)\n- Line 1759 (normal execution)\n\n---\n\n### Step 7: RED — validation rule tests\n\nAdd tests to `validation/rules.rs` `mod tests`:\n- `random_selection_no_conditions_clean`: random node + unconditional edges → no diagnostics → fails (rule doesn't exist)\n- `random_selection_with_conditions_errors`: random node + conditional edge → 1 Error diagnostic → fails\n- `deterministic_selection_with_conditions_ok`: deterministic node + conditional edges → no diagnostics → fails\n\n### Step 8: GREEN — validation Rule 23\n\nAdd `RandomSelectionNoConditionsRule` struct + `LintRule` impl:\n- Iterates nodes with `selection()==\"random\"`, checks outgoing edges for conditions\n- Error severity, message: `Node '{id}' has selection=\"random\" but also has conditional edges; random selection and conditions cannot be combined`\n- Fix: `Remove the condition attributes from outgoing edges, or remove selection=\"random\" from the node`\n- Register in `built_in_rules()` vec (line 35)\n\n---\n\n### Step 9: Docs (no TDD cycle)\n\n**`docs/workflows/transitions.mdx`:**\n- Update intro paragraph (line 6): `\"deterministic by default\"`, link to random selection section\n- Add \"Random selection\" section after \"Weight tiebreaking\" (~after line 157) with DOT example and `` about validation constraint\n\n**`docs/reference/dot-language.mdx`:**\n- Add `selection` row to \"All nodes\" attribute table (~line 191)\n\n---\n\n### Step 10: Final verification\n\n1. `cargo test -p fabro-workflows` — all tests pass\n2. `cargo clippy --workspace -- -D warnings` — clean\n3. `cargo fmt --check --all` — formatted\n\n## Files\n\n| File | Change |\n|---|---|\n| `lib/crates/fabro-workflows/src/graph/types.rs` | `selection()` accessor + tests |\n| `lib/crates/fabro-workflows/src/engine.rs` | `weighted_random`, `pick_edge`, modify `select_edge` sig + call sites + tests |\n| `lib/crates/fabro-workflows/src/validation/rules.rs` | Rule 23 + register + tests |\n| `docs/workflows/transitions.mdx` | Random selection section |\n| `docs/reference/dot-language.mdx` | `selection` attribute row |\n\n", + "command.output": "cargo 1.94.0 (85eff7c80 2026-01-15)\n", + "internal.retry_count.start": 1, + "outcome": "success", + "thread.start.current_node": "toolchain", + "failure_signature": "", + "internal.fidelity": "compact", + "graph.rankdir": "LR", + "internal.thread_id": "start", + "internal.retry_count.toolchain": 1, + "internal.node_visit_count": 1, + "current_node": "toolchain", + "internal.run_id": "01KKSV9P020MWAMJ44XB7M93HW", + "failure_class": "", + "graph.model_stylesheet": "\n * { backend: api; model: claude-opus-4-6;}\n " + }, + "logs": [], + "node_outcomes": { + "toolchain": { + "status": "success", + "context_updates": { + "command.output": "cargo 1.94.0 (85eff7c80 2026-01-15)\n", + "command.stderr": "" + }, + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "duration_ms": 55 + }, + "start": { + "status": "success", + "duration_ms": 0 + } + }, + "next_node_id": "preflight_compile", + "node_visits": { + "start": 1, + "toolchain": 1 + } +} \ No newline at end of file diff --git a/nodes/start/status.json b/nodes/start/status.json new file mode 100644 index 000000000..f62b21991 --- /dev/null +++ b/nodes/start/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": null, + "failure_reason": null, + "timestamp": "2026-03-15T22:54:07.230807+00:00" +} \ No newline at end of file diff --git a/nodes/toolchain/script_invocation.json b/nodes/toolchain/script_invocation.json new file mode 100644 index 000000000..d68c414c4 --- /dev/null +++ b/nodes/toolchain/script_invocation.json @@ -0,0 +1,5 @@ +{ + "command": "command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "language": "shell", + "timeout_ms": null +} \ No newline at end of file diff --git a/nodes/toolchain/script_timing.json b/nodes/toolchain/script_timing.json new file mode 100644 index 000000000..3ec21f9cf --- /dev/null +++ b/nodes/toolchain/script_timing.json @@ -0,0 +1,5 @@ +{ + "duration_ms": 54, + "exit_code": 0, + "timed_out": false +} \ No newline at end of file diff --git a/nodes/toolchain/status.json b/nodes/toolchain/status.json new file mode 100644 index 000000000..7f2d9f758 --- /dev/null +++ b/nodes/toolchain/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "failure_reason": null, + "timestamp": "2026-03-15T22:54:07.295263+00:00" +} \ No newline at end of file