From 35baf4fb6ac301ed2543fcf4f2b82d102e32081b Mon Sep 17 00:00:00 2001 From: Ali Romman <226877337+aliromman@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:32:08 -0500 Subject: [PATCH 1/3] Resolve current task baseline commands without changing frozen cards --- docs/research/selected-baseline.md | 42 ++++++++++++++----- scripts/choose_research_task.py | 29 ++++++++++++- tests/test_research_assignment.py | 65 ++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/docs/research/selected-baseline.md b/docs/research/selected-baseline.md index 78655e806f..51e7fd03d4 100644 --- a/docs/research/selected-baseline.md +++ b/docs/research/selected-baseline.md @@ -1,6 +1,6 @@ # Productive-helper baseline orientation -For the selected #4359 productive-helper certificate, run from the repository root: +For the current selected productive-helper certificate, run from the repository root: ```sh python3 -B scripts/research_selected_baseline.py @@ -56,8 +56,8 @@ python3 -B scripts/research_baseline_sparse.py ``` Inspect its suggested sparse-checkout additions and materialize the inputs. -Then use `research_selected_baseline.py` for the productive-helper summary, -in place of the historical baseline command in its `next` field. Diagnosis +Then run the current command in its `next` field. It uses +`research_selected_baseline.py` for the productive-helper summary. Diagnosis does not verify the inputs; the successor's source check remains mandatory. ## Compatibility and validation @@ -85,13 +85,37 @@ The test filename is included by the existing research onboarding discovery command. The full selected cold replay is a separate obligation and was not run for this reporting adapter. -## Open next questions +## Current task-picker commands + +The task-picker CLI now resolves the exact generic command +`python3 -B scripts/research_baseline.py` to the current selected baseline. +Each affected suggestion retains its complete original entry in +`catalog_first_step` and explains the change in `first_step_resolution`. +Only the command changes. The inspection text, scope, task, method, scope key +and acceptance gate stay the same. Other scientific first-step commands are +not changed. The CLI checks that the current helper exists, or is an exact +HEAD file omitted by a sparse checkout, before it emits the resolved command. +A sparse result names the missing helper for materialization. + +The catalogue bytes, `select()` result and package `TASK.json` original-card +receipts remain exact. This distinction is deliberate: the CLI gives current +orientation, while the stored card records the historical assignment. Do not +copy the resolved command into an old package or repin its frozen inputs. +[PR4906](https://github.com/CrocSwap/integer-mult-bounds/pull/4906) found 57 +runtime consumers of the catalogue and requested this receipt-preserving +migration. [PR5359](https://github.com/CrocSwap/integer-mult-bounds/pull/5359) +provides the separate authenticated historical complex-ceiling replay. +That scientific command is outside this generic-baseline resolution rule. + +The displayed scientific starting points still need a current overlap and +scope check. Command resolution is not a strategic reassessment or scientific +acceptance. Tests cover all 16 generic cards, unchanged other cards, exact +command matching, missing/deleted/sparse helpers and actual CLI output. +Run them with `python3 -B -m unittest discover -s tests -p test_research_assignment.py`. +Current CLI resolution and these controls: Ali Romman with substantial OpenAI +Codex assistance; Apache-2.0. -The active catalogue and pinned brief still name the historical entry point. -A maintainer can migrate those references after choosing how to preserve the -dependent frozen packages' source receipts. The first step is to inventory -their `VERIFY.json` and `SOURCES.json` dependencies; changing the helper bytes -or silently repinning old inputs is not part of this contribution. +## Open next questions Future selected certificate schemas need an explicit budget-stage adapter. Start from the new selected record and reviewed source schema, then add a diff --git a/scripts/choose_research_task.py b/scripts/choose_research_task.py index 5c2ed0bab2..d51607fe54 100644 --- a/scripts/choose_research_task.py +++ b/scripts/choose_research_task.py @@ -179,6 +179,29 @@ def draw(label): return {"seed": seed, "track": track["id"], "suggestions": suggestions} +def resolve_current_first_steps(result, root): + """Resolve generic orientation at the CLI boundary; keep the source card.""" + old = ["python3", "-B", "scripts/research_baseline.py"] + current = "scripts/research_selected_baseline.py" + cards = [card for card in result["suggestions"] + if card["first_step"]["command"] == old] + if not cards: + return [] + omitted = check_catalog_files(root, [ + (current, "missing current first-step script: " + current), + ]) + for card in cards: + original = card["first_step"] + card["catalog_first_step"] = original + card["first_step"] = {**original, "command": ["python3", "-B", current]} + card["first_step_resolution"] = ( + "The current CLI uses the selected-certificate baseline for generic " + "orientation. catalog_first_step is the exact frozen card entry. " + "The task, scientific checks and acceptance gate are unchanged." + ) + return omitted + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--seed", help="32 hex digits; default: fresh OS-random 128-bit nonce") @@ -192,6 +215,7 @@ def main(): catalog = json.loads(catalog_bytes) omitted = validate_catalog(catalog, ROOT) result = select(catalog, args.seed if args.seed is not None else secrets.token_hex(16), args.track, args.task) + current_omitted = resolve_current_first_steps(result, ROOT) selected_bytes = (ROOT / "certificates/selected-result.json").read_bytes() selected = json.loads(selected_bytes) try: @@ -227,10 +251,11 @@ def main(): "status": "SUGGESTIONS_ONLY_NOT_RESERVED", "next_step": "First check #1166 for a suitable open handoff. Use these suggestions if none fits, check live overlap and existing coverage, then record a bounded scope. Claims never exclude independent audits.", }) - if omitted: + if omitted or current_omitted: result["sparse_checkout"] = { "omitted_catalogue_paths": omitted, - "scope": "These absent regular files match HEAD and skip-worktree index entries. Materialize the chosen task's inputs before running it; this validates catalogue references, not replay dependencies.", + "omitted_current_first_step_paths": current_omitted, + "scope": "These absent regular files match HEAD and skip-worktree index entries. Materialize the chosen task's inputs before running it; this validates first-step references, not replay dependencies.", } except (OSError, ValueError, KeyError, TypeError) as exc: parser.error(str(exc)) diff --git a/tests/test_research_assignment.py b/tests/test_research_assignment.py index 81131ccd9e..cf6ef52c8c 100644 --- a/tests/test_research_assignment.py +++ b/tests/test_research_assignment.py @@ -107,6 +107,55 @@ def test_cli_dynamic_selected_record_and_receipt(self): self.assertEqual(receipt["priority_log"], CATALOG["priority_log"]) self.assertEqual(len(receipt["catalog_sha256"]), 64) + def test_current_commands_preserve_every_frozen_card(self): + before = copy.deepcopy(CATALOG) + resolved = 0 + for track in CATALOG["tracks"]: + for task in track["tasks"]: + result = assignment.select(CATALOG, "a1" * 16, task_id=task["id"]) + original = copy.deepcopy(result) + assignment.resolve_current_first_steps(result, ROOT) + card = result["suggestions"][0] + if task["first_step"]["command"] == ["python3", "-B", "scripts/research_baseline.py"]: + resolved += 1 + self.assertEqual(card["catalog_first_step"], task["first_step"]) + self.assertEqual(card["first_step"]["command"], + ["python3", "-B", "scripts/research_selected_baseline.py"]) + self.assertTrue(card.pop("first_step_resolution")) + card["first_step"] = card.pop("catalog_first_step") + self.assertEqual(result, original) + self.assertEqual(resolved, 16) + self.assertEqual(CATALOG, before) + + def test_resolution_requires_exact_generic_command(self): + result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") + result = copy.deepcopy(result) + result["suggestions"][0]["first_step"]["command"].append("--historical-fixture") + original = copy.deepcopy(result) + self.assertEqual(assignment.resolve_current_first_steps(result, ROOT), []) + self.assertEqual(result, original) + + def test_resolution_rejects_missing_current_helper(self): + result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") + original = copy.deepcopy(result) + with tempfile.TemporaryDirectory() as directory: + with self.assertRaisesRegex(ValueError, "missing current first-step script"): + assignment.resolve_current_first_steps(result, Path(directory)) + self.assertEqual(result, original) + + def test_cli_emits_current_and_original_commands(self): + output = subprocess.run( + [sys.executable, "-B", str(ROOT / "scripts/choose_research_task.py"), + "--seed", "0" * 32, "--task", "portable-replay"], + cwd=ROOT, capture_output=True, text=True, check=True, + ) + card = json.loads(output.stdout)["suggestions"][0] + self.assertEqual(card["first_step"]["command"], + ["python3", "-B", "scripts/research_selected_baseline.py"]) + self.assertEqual(card["catalog_first_step"]["command"], + ["python3", "-B", "scripts/research_baseline.py"]) + + class SparseResearchAssignmentTests(unittest.TestCase): def setUp(self): @@ -155,6 +204,22 @@ def receipt(self): ) return json.loads(result.stdout) + def test_current_helper_sparse_omission_and_deletion(self): + name = "scripts/research_selected_baseline.py" + path = self.root / name + path.write_text("print('fixture')\n", encoding="utf-8") + self.git("add", name) + self.git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "-c", "core.hooksPath=/dev/null", "commit", "-qm", "helper") + self.git("update-index", "--skip-worktree", name) + path.unlink() + result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") + self.assertEqual(assignment.resolve_current_first_steps(result, self.root), [name]) + self.git("update-index", "--no-skip-worktree", name) + result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") + with self.assertRaisesRegex(ValueError, "missing current first-step script"): + assignment.resolve_current_first_steps(result, self.root) + def test_sparse_cli_preserves_assignment_and_reports_omitted_inputs(self): full = self.receipt() self.sparse() From 6da8503fa6ecf74172029df0258803448b5783d3 Mon Sep 17 00:00:00 2001 From: Ali Romman <226877337+aliromman@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:57:27 -0500 Subject: [PATCH 2/3] Preserve the frozen picker in a separate current-orientation CLI --- AGENTS.md | 6 ++ docs/research/agent-procedure.md | 10 +-- docs/research/selected-baseline.md | 12 ++- scripts/choose_current_research_task.py | 67 +++++++++++++++ scripts/choose_research_task.py | 29 +------ tests/test_research_assignment.py | 65 --------------- tests/test_research_current_assignment.py | 99 +++++++++++++++++++++++ 7 files changed, 187 insertions(+), 101 deletions(-) create mode 100644 scripts/choose_current_research_task.py create mode 100644 tests/test_research_current_assignment.py diff --git a/AGENTS.md b/AGENTS.md index aea5573d1b..fa006ceb6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,12 @@ before using the task picker; its coverage fields identify prior checks and remaining obligations. Explicit user tasks take precedence over that default assignment; do not reroll an already assigned task. +For a fresh assignment, run `python3 -B scripts/choose_current_research_task.py`. +This entry point resolves generic baseline commands and retains each frozen +card beside the current command. The historical picker and root guide stay +unchanged because research packages pin their bytes. The current command +examples are in [the standing procedure](docs/research/agent-procedure.md). + For repeated/overnight loops, follow the [upstream refresh checkpoint](autoresearch.md#looping-agents-refresh-upstream-at-each-research-checkpoint) at every new round, before a major pivot and before submission. Fetch canonical diff --git a/docs/research/agent-procedure.md b/docs/research/agent-procedure.md index 9294c540bf..9a2d1b819e 100644 --- a/docs/research/agent-procedure.md +++ b/docs/research/agent-procedure.md @@ -102,15 +102,15 @@ Run this standard-library helper: ```sh # Python 3.10 or later; no third-party dependencies. -python3 scripts/choose_research_task.py +python3 scripts/choose_current_research_task.py # To reproduce a selection, pass the emitted 32-hex-digit nonce: -python3 scripts/choose_research_task.py --seed YOUR_NONCE +python3 scripts/choose_current_research_task.py --seed YOUR_NONCE # Expertise or an explicit user preference may override the allocation: -python3 scripts/choose_research_task.py --track complex-mechanisms +python3 scripts/choose_current_research_task.py --track complex-mechanisms # Retrieve a specific card after assignment: -python3 scripts/choose_research_task.py --task connector-alphabet-change +python3 scripts/choose_current_research_task.py --task connector-alphabet-change # Optional: surface structured handoffs/claims from an existing overlap cache. -python3 scripts/choose_research_task.py --coordination-cache /tmp/overlap.json +python3 scripts/choose_current_research_task.py --coordination-cache /tmp/overlap.json ``` It reads [the task catalogue](agent-task-catalog.json), draws a diff --git a/docs/research/selected-baseline.md b/docs/research/selected-baseline.md index 51e7fd03d4..ee6c992bf2 100644 --- a/docs/research/selected-baseline.md +++ b/docs/research/selected-baseline.md @@ -87,7 +87,7 @@ run for this reporting adapter. ## Current task-picker commands -The task-picker CLI now resolves the exact generic command +The current task-picker CLI, `scripts/choose_current_research_task.py`, resolves the exact generic command `python3 -B scripts/research_baseline.py` to the current selected baseline. Each affected suggestion retains its complete original entry in `catalog_first_step` and explains the change in `first_step_resolution`. @@ -97,8 +97,12 @@ not changed. The CLI checks that the current helper exists, or is an exact HEAD file omitted by a sparse checkout, before it emits the resolved command. A sparse result names the missing helper for materialization. -The catalogue bytes, `select()` result and package `TASK.json` original-card -receipts remain exact. This distinction is deliberate: the CLI gives current +The historical `scripts/choose_research_task.py`, catalogue bytes, `select()` +result and package `TASK.json` original-card receipts remain exact. The current +entry point runs the historical CLI with unchanged arguments and resolves its +JSON output. Help and error output keep the child status and text. This separate +entry point is necessary because a frozen package also pins the picker script +as a runtime input. It must not be changed or silently repinned. This distinction is deliberate: the CLI gives current orientation, while the stored card records the historical assignment. Do not copy the resolved command into an old package or repin its frozen inputs. [PR4906](https://github.com/CrocSwap/integer-mult-bounds/pull/4906) found 57 @@ -111,7 +115,7 @@ The displayed scientific starting points still need a current overlap and scope check. Command resolution is not a strategic reassessment or scientific acceptance. Tests cover all 16 generic cards, unchanged other cards, exact command matching, missing/deleted/sparse helpers and actual CLI output. -Run them with `python3 -B -m unittest discover -s tests -p test_research_assignment.py`. +Run them with `python3 -B -m unittest discover -s tests -p test_research_current_assignment.py`. Current CLI resolution and these controls: Ali Romman with substantial OpenAI Codex assistance; Apache-2.0. diff --git a/scripts/choose_current_research_task.py b/scripts/choose_current_research_task.py new file mode 100644 index 0000000000..9836d57d46 --- /dev/null +++ b/scripts/choose_current_research_task.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Show current first steps and preserve the frozen task-picker output. + +Ali Romman with substantial OpenAI Codex assistance. Apache-2.0. +Arguments and selection are delegated to the historical picker CLI. +""" +import json +from pathlib import Path +import runpy +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[1] +LEGACY = ROOT / "scripts/choose_research_task.py" +check_catalog_files = runpy.run_path(str(LEGACY))["check_catalog_files"] + + +def resolve_current_first_steps(result, root): + """Resolve generic orientation at the CLI boundary; keep the source card.""" + old = ["python3", "-B", "scripts/research_baseline.py"] + current = "scripts/research_selected_baseline.py" + cards = [card for card in result["suggestions"] + if card["first_step"]["command"] == old] + if not cards: + return [] + omitted = check_catalog_files(root, [ + (current, "missing current first-step script: " + current), + ]) + for card in cards: + original = card["first_step"] + card["catalog_first_step"] = original + card["first_step"] = {**original, "command": ["python3", "-B", current]} + card["first_step_resolution"] = ( + "The current CLI uses the selected-certificate baseline for generic " + "orientation. catalog_first_step is the exact frozen card entry. " + "The task, scientific checks and acceptance gate are unchanged." + ) + return omitted + + +def main(): + child = subprocess.run([sys.executable, "-B", str(LEGACY), *sys.argv[1:]], + capture_output=True, text=True, encoding="utf-8") + if child.returncode or any(arg in ("-h", "--help") for arg in sys.argv[1:]): + sys.stdout.write(child.stdout) + sys.stderr.write(child.stderr) + return child.returncode + try: + result = json.loads(child.stdout) + omitted = resolve_current_first_steps(result, ROOT) + if omitted: + sparse = result.setdefault("sparse_checkout", {"omitted_catalogue_paths": []}) + sparse["omitted_current_first_step_paths"] = omitted + sparse["scope"] = ( + "These absent regular files match HEAD and skip-worktree index entries. " + "Materialize the chosen task's inputs before running it; this validates " + "first-step references, not replay dependencies." + ) + except (OSError, ValueError, KeyError, TypeError) as exc: + sys.stderr.write("current first-step resolution failed: " + str(exc) + "\n") + return 2 + print(json.dumps(result, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/choose_research_task.py b/scripts/choose_research_task.py index d51607fe54..5c2ed0bab2 100644 --- a/scripts/choose_research_task.py +++ b/scripts/choose_research_task.py @@ -179,29 +179,6 @@ def draw(label): return {"seed": seed, "track": track["id"], "suggestions": suggestions} -def resolve_current_first_steps(result, root): - """Resolve generic orientation at the CLI boundary; keep the source card.""" - old = ["python3", "-B", "scripts/research_baseline.py"] - current = "scripts/research_selected_baseline.py" - cards = [card for card in result["suggestions"] - if card["first_step"]["command"] == old] - if not cards: - return [] - omitted = check_catalog_files(root, [ - (current, "missing current first-step script: " + current), - ]) - for card in cards: - original = card["first_step"] - card["catalog_first_step"] = original - card["first_step"] = {**original, "command": ["python3", "-B", current]} - card["first_step_resolution"] = ( - "The current CLI uses the selected-certificate baseline for generic " - "orientation. catalog_first_step is the exact frozen card entry. " - "The task, scientific checks and acceptance gate are unchanged." - ) - return omitted - - def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--seed", help="32 hex digits; default: fresh OS-random 128-bit nonce") @@ -215,7 +192,6 @@ def main(): catalog = json.loads(catalog_bytes) omitted = validate_catalog(catalog, ROOT) result = select(catalog, args.seed if args.seed is not None else secrets.token_hex(16), args.track, args.task) - current_omitted = resolve_current_first_steps(result, ROOT) selected_bytes = (ROOT / "certificates/selected-result.json").read_bytes() selected = json.loads(selected_bytes) try: @@ -251,11 +227,10 @@ def main(): "status": "SUGGESTIONS_ONLY_NOT_RESERVED", "next_step": "First check #1166 for a suitable open handoff. Use these suggestions if none fits, check live overlap and existing coverage, then record a bounded scope. Claims never exclude independent audits.", }) - if omitted or current_omitted: + if omitted: result["sparse_checkout"] = { "omitted_catalogue_paths": omitted, - "omitted_current_first_step_paths": current_omitted, - "scope": "These absent regular files match HEAD and skip-worktree index entries. Materialize the chosen task's inputs before running it; this validates first-step references, not replay dependencies.", + "scope": "These absent regular files match HEAD and skip-worktree index entries. Materialize the chosen task's inputs before running it; this validates catalogue references, not replay dependencies.", } except (OSError, ValueError, KeyError, TypeError) as exc: parser.error(str(exc)) diff --git a/tests/test_research_assignment.py b/tests/test_research_assignment.py index cf6ef52c8c..81131ccd9e 100644 --- a/tests/test_research_assignment.py +++ b/tests/test_research_assignment.py @@ -107,55 +107,6 @@ def test_cli_dynamic_selected_record_and_receipt(self): self.assertEqual(receipt["priority_log"], CATALOG["priority_log"]) self.assertEqual(len(receipt["catalog_sha256"]), 64) - def test_current_commands_preserve_every_frozen_card(self): - before = copy.deepcopy(CATALOG) - resolved = 0 - for track in CATALOG["tracks"]: - for task in track["tasks"]: - result = assignment.select(CATALOG, "a1" * 16, task_id=task["id"]) - original = copy.deepcopy(result) - assignment.resolve_current_first_steps(result, ROOT) - card = result["suggestions"][0] - if task["first_step"]["command"] == ["python3", "-B", "scripts/research_baseline.py"]: - resolved += 1 - self.assertEqual(card["catalog_first_step"], task["first_step"]) - self.assertEqual(card["first_step"]["command"], - ["python3", "-B", "scripts/research_selected_baseline.py"]) - self.assertTrue(card.pop("first_step_resolution")) - card["first_step"] = card.pop("catalog_first_step") - self.assertEqual(result, original) - self.assertEqual(resolved, 16) - self.assertEqual(CATALOG, before) - - def test_resolution_requires_exact_generic_command(self): - result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") - result = copy.deepcopy(result) - result["suggestions"][0]["first_step"]["command"].append("--historical-fixture") - original = copy.deepcopy(result) - self.assertEqual(assignment.resolve_current_first_steps(result, ROOT), []) - self.assertEqual(result, original) - - def test_resolution_rejects_missing_current_helper(self): - result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") - original = copy.deepcopy(result) - with tempfile.TemporaryDirectory() as directory: - with self.assertRaisesRegex(ValueError, "missing current first-step script"): - assignment.resolve_current_first_steps(result, Path(directory)) - self.assertEqual(result, original) - - def test_cli_emits_current_and_original_commands(self): - output = subprocess.run( - [sys.executable, "-B", str(ROOT / "scripts/choose_research_task.py"), - "--seed", "0" * 32, "--task", "portable-replay"], - cwd=ROOT, capture_output=True, text=True, check=True, - ) - card = json.loads(output.stdout)["suggestions"][0] - self.assertEqual(card["first_step"]["command"], - ["python3", "-B", "scripts/research_selected_baseline.py"]) - self.assertEqual(card["catalog_first_step"]["command"], - ["python3", "-B", "scripts/research_baseline.py"]) - - class SparseResearchAssignmentTests(unittest.TestCase): def setUp(self): @@ -204,22 +155,6 @@ def receipt(self): ) return json.loads(result.stdout) - def test_current_helper_sparse_omission_and_deletion(self): - name = "scripts/research_selected_baseline.py" - path = self.root / name - path.write_text("print('fixture')\n", encoding="utf-8") - self.git("add", name) - self.git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", - "-c", "core.hooksPath=/dev/null", "commit", "-qm", "helper") - self.git("update-index", "--skip-worktree", name) - path.unlink() - result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") - self.assertEqual(assignment.resolve_current_first_steps(result, self.root), [name]) - self.git("update-index", "--no-skip-worktree", name) - result = assignment.select(CATALOG, "0" * 32, task_id="portable-replay") - with self.assertRaisesRegex(ValueError, "missing current first-step script"): - assignment.resolve_current_first_steps(result, self.root) - def test_sparse_cli_preserves_assignment_and_reports_omitted_inputs(self): full = self.receipt() self.sparse() diff --git a/tests/test_research_current_assignment.py b/tests/test_research_current_assignment.py new file mode 100644 index 0000000000..49f7c86969 --- /dev/null +++ b/tests/test_research_current_assignment.py @@ -0,0 +1,99 @@ +"""Current orientation must preserve frozen task-selection evidence.""" +import copy +import importlib.util +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +SPEC = importlib.util.spec_from_file_location("current_picker", ROOT / "scripts/choose_current_research_task.py") +current = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(current) +LEGACY = current.LEGACY +legacy = current.runpy.run_path(str(LEGACY)) +CATALOG = json.loads((ROOT / legacy["CATALOG"]).read_text(encoding="utf-8")) + + +class CurrentResearchAssignmentTests(unittest.TestCase): + def test_every_frozen_card_and_other_fields_stay_exact(self): + before = copy.deepcopy(CATALOG) + resolved = 0 + for track in CATALOG["tracks"]: + for task in track["tasks"]: + result = legacy["select"](CATALOG, "a1" * 16, task_id=task["id"]) + original = copy.deepcopy(result) + current.resolve_current_first_steps(result, ROOT) + card = result["suggestions"][0] + if task["first_step"]["command"] == ["python3", "-B", "scripts/research_baseline.py"]: + resolved += 1 + self.assertEqual(card["catalog_first_step"], task["first_step"]) + self.assertEqual(card["first_step"]["command"], + ["python3", "-B", "scripts/research_selected_baseline.py"]) + self.assertTrue(card.pop("first_step_resolution")) + card["first_step"] = card.pop("catalog_first_step") + self.assertEqual(result, original) + self.assertEqual(resolved, 16) + self.assertEqual(CATALOG, before) + + def test_only_exact_generic_command_is_resolved(self): + result = copy.deepcopy(legacy["select"](CATALOG, "0" * 32, task_id="portable-replay")) + result["suggestions"][0]["first_step"]["command"].append("--historical-fixture") + original = copy.deepcopy(result) + self.assertEqual(current.resolve_current_first_steps(result, ROOT), []) + self.assertEqual(result, original) + + def test_missing_current_helper_does_not_change_receipt(self): + result = legacy["select"](CATALOG, "0" * 32, task_id="portable-replay") + original = copy.deepcopy(result) + with tempfile.TemporaryDirectory() as directory: + with self.assertRaisesRegex(ValueError, "missing current first-step script"): + current.resolve_current_first_steps(result, Path(directory)) + self.assertEqual(result, original) + + def cli(self, script, args): + return subprocess.run([sys.executable, "-B", str(script), *args], cwd=ROOT, + capture_output=True, text=True, encoding="utf-8") + + def test_cli_preserves_legacy_output_and_emits_both_commands(self): + args = ["--seed", "0" * 32, "--task", "portable-replay"] + original = self.cli(LEGACY, args) + output = self.cli(ROOT / "scripts/choose_current_research_task.py", args) + self.assertEqual(original.returncode, 0) + self.assertEqual(output.returncode, 0) + old, new = json.loads(original.stdout), json.loads(output.stdout) + card = new["suggestions"][0] + self.assertEqual(card["first_step"]["command"], + ["python3", "-B", "scripts/research_selected_baseline.py"]) + self.assertEqual(card.pop("catalog_first_step"), old["suggestions"][0]["first_step"]) + self.assertTrue(card.pop("first_step_resolution")) + card["first_step"] = old["suggestions"][0]["first_step"] + self.assertEqual(new, old) + self.assertEqual(self.cli(LEGACY, args).stdout, original.stdout) + + def test_help_and_error_preserve_child_status_and_output(self): + for args, code in ((["--help"], 0), (["--unknown-option"], 2), (["--seed", "bad"], 2)): + original = self.cli(LEGACY, args) + output = self.cli(ROOT / "scripts/choose_current_research_task.py", args) + self.assertEqual(output.returncode, code) + self.assertEqual((output.stdout, output.stderr), (original.stdout, original.stderr)) + + def test_sparse_helper_is_allowed_but_deletion_is_rejected(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + name = "scripts/research_selected_baseline.py" + path = root / name; path.parent.mkdir(); path.write_text("print('fixture')\n") + def git(*args): + subprocess.run(["git", *args], cwd=root, check=True, capture_output=True) + git("init", "-q"); git("add", name) + git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "-c", "core.hooksPath=/dev/null", "commit", "-qm", "helper") + git("update-index", "--skip-worktree", name); path.unlink() + result = legacy["select"](CATALOG, "0" * 32, task_id="portable-replay") + self.assertEqual(current.resolve_current_first_steps(result, root), [name]) + git("update-index", "--no-skip-worktree", name) + result = legacy["select"](CATALOG, "0" * 32, task_id="portable-replay") + with self.assertRaisesRegex(ValueError, "missing current first-step script"): + current.resolve_current_first_steps(result, root) From 69556bd8cf0125c4eed84d547ad6a222993797d8 Mon Sep 17 00:00:00 2001 From: Ali Romman <226877337+aliromman@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:09:47 -0500 Subject: [PATCH 3/3] Check handoff ordering against the current picker entry point --- tests/test_research_coordination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_research_coordination.py b/tests/test_research_coordination.py index af573f1291..95d7dda49a 100644 --- a/tests/test_research_coordination.py +++ b/tests/test_research_coordination.py @@ -121,7 +121,7 @@ def test_root_entry_and_priorities_links(self): self.assertIn('docs/research/current-priorities.md',root) self.assertIn('docs/research/agent-procedure.md',root) procedure=(ROOT/'docs/research/agent-procedure.md').read_text() - self.assertLess(procedure.index('**Before drawing a card**'),procedure.index('python3 scripts/choose_research_task.py')) + self.assertLess(procedure.index('**Before drawing a card**'),procedure.index('python3 scripts/choose_current_research_task.py')) if __name__=='__main__':unittest.main()