Skip to content

Latest commit

 

History

History
760 lines (631 loc) · 37.7 KB

File metadata and controls

760 lines (631 loc) · 37.7 KB

psyflow change log

0.1.23 (2026-04-05)

Summary

  • Restored TaskSettings.add_subinfo() to fall back to ./outputs/human when save_path is unset or empty, while still creating explicit nested output directories.
  • Added pure-Python regression tests for psyflow.sim.loader, psyflow.sim.logging, psyflow.utils.trials, and TaskSettings.add_subinfo().

Validation

  • python -m unittest discover -s tests -v passed.

0.1.22 (2026-04-05)

Summary

  • Made psyflow.utils lazy-load PsychoPy-dependent helpers so importing psyflow.utils no longer eagerly imports display or experiment.
  • Removed the duplicate sim-side deadline helper and reused psyflow.utils.trials.resolve_deadline() from psyflow.sim.context_helpers.
  • Added smoke coverage for the import boundary and sim deadline resolution.
  • Addresses issue #21: utils/__init__.py eager psychopy imports block cross-module reuse.

Validation

  • python -m unittest discover -s tests -p "test_smoke.py" -v passed.
  • python -m unittest discover -s tests -p "test_sim_golden.py" -v passed.

0.1.21 (2026-03-04)

Summary

  • Removed in-repo skills/task-build from psyflow.
  • Extracted task-build as a standalone canonical repository at e:/Taskbeacon/skills/task-build.
  • No backward-compatibility shim is kept in psyflow; skill ownership now lives outside the framework repo.

Validation

  • git status in psyflow confirms only skills/task-build/* removals plus this changelog update.

0.1.20 (2026-03-02)

Summary

  • Moved condition-weight resolution into TaskSettings:
    • added TaskSettings.resolve_condition_weights() in psyflow/TaskSettings.py.
  • Removed psyflow.utils.trials.resolve_condition_weights and all related exports.
  • Updated runtime/template standards to use settings.resolve_condition_weights():
    • cookiecutter main.py and config comments now point to the TaskSettings method.
  • Updated contracts and task-build skill assets/checklists to standardize this policy.
  • Updated condition-weight unit tests to exercise the TaskSettings method directly.

Validation

  • python -m unittest tests.test_condition_weights passed.
  • python -m unittest tests.test_validate passed.
  • python -m psyflow.validate "psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}" passed with FAIL=0.

0.1.19 (2026-03-02)

Summary

  • Added framework-level condition-weight resolver:
    • psyflow.utils.trials.resolve_condition_weights(...)
    • exported via psyflow.resolve_condition_weights and psyflow.utils.
  • Standardized config/contract semantics for weighted condition generation:
    • task.condition_weights is optional and validated when provided.
    • mapping/list formats must align with task.conditions; positive numeric weights required.
    • omitted/null task.condition_weights means even/default generation unless task code documents custom generation.
  • Updated cookiecutter template to include optional task.condition_weights and pass resolved weights into default condition scheduler.
  • Updated task-build skill docs/checklists/templates to enforce explicit task.condition_weights when weighted generation is used.
  • Added unit coverage for weight resolver behavior in tests/test_condition_weights.py.

Validation

  • python -m unittest tests.test_condition_weights passed.
  • python -m psyflow.validate "psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}" passed with FAIL=0.

0.1.18 (2026-03-02)

Summary

  • Fixed CI contract validation failure on the cookiecutter template by adding required reference artifacts under:
    • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/references/
      • references.yaml
      • references.md
      • parameter_mapping.md
      • stimulus_mapping.md
      • task_logic_audit.md
  • All added template files follow the reference_artifacts contract headings/table-column requirements and avoid forbidden unresolved markers.

Validation

  • python -m psyflow.validate 'psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}' now passes with FAIL=0.

0.1.17 (2026-03-02)

Summary

  • Added enforceable localization-safe runtime policy so participant-facing text is config-driven, not hardcoded in src/run_trial.py.
    • Extended responder-context contract with hardcoded-text guards (TextStim/TextBox literal text, .setText('...'), and .text='...' assignment checks).
    • Implemented AST-based checks in psyflow/validate.py.
  • Added a new reference-artifact contract:
    • psyflow/contracts/v0.1.0/reference_artifacts.yaml
    • wired into contract manifest.yaml
    • validator now checks required reference files, required headings/table columns, and references.yaml required keys/paper fields.
  • Updated contract docs/patterns to reflect localization + reference-artifact standards:
    • psyflow/contracts/v0.1.0/README.md
    • psyflow/contracts/v0.1.0/run_trial_pattern.md
  • Upgraded skills/task-build to align with these standards:
    • updated SKILL.md with config-first localization and reference-artifact schema requirements;
    • added references/reference_artifact_contract.md;
    • added mapping templates:
      • assets/templates/parameter_mapping_template.md
      • assets/templates/stimulus_mapping_template.md
    • strengthened scripts/check_task_standard.py and scripts/build_reference_bundle.py to emit/check standardized reference artifact formats.
  • Expanded validator regression coverage in tests/test_validate.py:
    • minimal compliant fixture now includes full reference artifacts;
    • added failure test for hardcoded participant-facing runtime text.

Validation

  • python -m py_compile psyflow/validate.py skills/task-build/scripts/check_task_standard.py skills/task-build/scripts/build_reference_bundle.py passed.
  • python -m unittest -q tests.test_validate passed.

0.1.16 (2026-02-24)

Summary

  • Improved skills/task-build guidance with generic anti-overdesign and PsyFlow-first decision rules (docs/templates only; no new checker enforcement).
  • Updated skills/task-build/SKILL.md to clarify:
    • start from the simplest PsyFlow-native path;
    • prefer built-in block condition generation before custom generators;
    • treat utils.py as optional and scoped to real framework gaps;
    • prefer config-driven participant-facing text and response mappings when appropriate;
    • prefer auditable main.py/run_trial.py patterns with minimal fallback logic.
  • Expanded skills/task-build/assets/templates/task_logic_audit_template.md with architecture-decision fields for:
    • condition generation method and justification,
    • runtime determinism/reproducibility,
    • response key source,
    • utils.py/controller usage,
    • fallback/compatibility scope.
  • Updated reusable reference docs to reinforce generic design heuristics:
    • skills/task-build/references/psyflow_task_standard_checklist.md
    • skills/task-build/references/task_development_experience.md so future task builds remain simpler, more PsyFlow-aligned, and easier to audit.

0.1.15 (2026-02-23)

Summary

  • Decoupled psyflow.sim.context_helpers.set_trial_context from task-specific controller logic.
    • Automatically resolves sequence durations via max() internally, eliminating the need for boilerplate _deadline_s functions in task code.
    • Removed controller dependency for trial_id resolution from context_helpers.
  • Added generic trial increment tracking natively to psyflow via next_trial_id() to standardize sequential trial IDs across tasks.

0.1.14 (2026-02-18)

Summary

  • Hardened skills/task-build to enforce logic-first development and non-placeholder stimulus implementation.
  • Updated skills/task-build/SKILL.md workflow:
    • added mandatory Phase 0: Paradigm Logic Audit before coding;
    • now requires references/task_logic_audit.md (seeded from template);
    • clarified hard bans on placeholder/template participant text and raw condition-token displays;
    • added explicit multi-option/multi-stimulus layout requirements and QA readability checks.
  • Added reusable audit scaffold:
    • skills/task-build/assets/templates/task_logic_audit_template.md.
  • Strengthened task standard checker (skills/task-build/scripts/check_task_standard.py):
    • now requires references/task_logic_audit.md;
    • fails placeholder cue/target patterns (e.g., CUE: ..., TARGET: ...);
    • fails common template stock text in participant stimuli (e.g., generic response/continue/quit text);
    • fails participant-facing text that is only raw condition labels.
  • Updated policy/checklist references:
    • skills/task-build/references/psyflow_task_standard_checklist.md
    • skills/task-build/references/stimulus_fidelity_policy.md to align with the stricter stimulus and layout standards.

Validation

  • python C:/Users/frued/.codex/skills/.system/skill-creator/scripts/quick_validate.py C:/Users/frued/.codex/skills/task-build passed.
  • python -m py_compile skills/task-build/scripts/check_task_standard.py passed.
  • Spot-check on a placeholder-heavy task now fails as expected with explicit stimulus-fidelity violations.

0.1.13 (2026-02-18)

Summary

  • Updated responder context contract/validator to support truly generic, task-specific phase labels via phase-token checks instead of fixed phase values.
  • Extended template compliance so CI validation passes on the cookiecutter task scaffold:
    • README now includes required section heading ## 4. Methods (for academic publication) and Language metadata row.
    • Template .gitignore now includes recommended housekeeping ignores (.pytest_cache, .mypy_cache, virtualenv dirs).
    • Template CHANGELOG.md now includes recommended Changed and Fixed sections.
    • Template run-trial phase labels updated to generic task-ready names (pre_response_fixation, response_window).
    • Added template outputs/.gitkeep to satisfy recommended path checks.

Validation

  • python -m psyflow.validate 'psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}' now passes with FAIL=0.

0.1.12 (2026-02-18)

Summary

  • Generalized task contracts to remove MID-specific phase coupling in responder checks.
    • responder_context now validates task-agnostic response phases via required_context_phase_values_any.
    • Removed fixed cue -> anticipation -> target -> feedback stage-order requirement from contract docs.
  • Enhanced validator logic in psyflow/validate.py:
    • added support for required_context_phase_values_any, forbidden_context_phase_values, required_stage_tokens_any, and recommended_stage_tokens_any;
    • improved README contract validation with required section-heading checks and subsection recommendations.
  • Updated contract documentation and patterns to clarify that phase/stage labels are task-defined, not MID-only.
  • Improved StimBank.get_and_format(...) reliability:
    • now rebuilds formatted text stimuli from template definitions to preserve layout properties,
    • surfaces clearer errors for missing format keys and rebuild failures.
  • Updated scaffolding quality:
    • template sampler responder now includes lifecycle hooks and RNG fallback compatibility,
    • task README-generation prompt refreshed for standardized metadata/flow/config/method sections.

Validation

  • python -m py_compile psyflow/validate.py passed.
  • python -m psyflow.validate e:\\Taskbeacon\\T000021-drifting-double-bandit passed (13/13).

0.1.11 (2026-02-17)

Summary

  • Added a new build-automation skill package at skills/task-build for end-to-end PsyFlow/TAPS task construction from literature evidence.
  • Implemented a decision-complete workflow in skills/task-build/SKILL.md covering:
    • literature discovery and filtering,
    • reference bundle generation,
    • task standard alignment,
    • placeholder asset generation,
    • validate/qa/sim gate execution with retry loop,
    • git commit/push publishing.
  • Added curated high-impact journal reference list for selection policy enforcement:
    • skills/task-build/references/high_impact_psyneuro_journals.yaml.
  • Added task-build reference guides:
    • literature_search_playbook.md
    • task_param_inference_rules.md
    • psyflow_task_standard_checklist.md
    • publish_checklist.md
  • Added task-build automation scripts:
    • scripts/select_papers.py
    • scripts/build_reference_bundle.py
    • scripts/check_task_standard.py
    • scripts/run_gates.py
    • scripts/publish_task.py
    • scripts/create_placeholder_assets.py
  • Added reusable task templates under:
    • skills/task-build/assets/templates/* (README, CHANGELOG, taskbeacon, and split config templates).
  • Added/updated skill agent metadata:
    • skills/task-build/agents/openai.yaml

Validation

  • quick_validate.py passes for skills/task-build.
  • Script syntax checks pass (py_compile) for all task-build scripts.
  • Dry-run completed on sandbox copy of T000010-rest:
    • paper selection succeeded,
    • reference bundle generated,
    • standard check passed,
    • gate runner passed,
    • publish script verified with --no-push.

0.1.10 (2026-02-16)

Summary

  • Split simulation config standards into two explicit profiles:
    • config/config_scripted_sim.yaml for built-in scripted responder runs
    • config/config_sampler_sim.yaml for task-specific sampler responders
  • Updated validator contract manifest and checks to validate both sim profiles independently.
  • Strengthened config contract rules:
    • base config.yaml now forbids embedded qa / sim sections
    • config_qa.yaml now requires full base task sections and forbids sim
    • added smoke-profile checks (shorter-than-base, condition coverage, basic trial/block consistency)
  • Extended validator value-spec engine with:
    • disallowed list support
    • string pattern matching
    • generic forbidden_nested_keys enforcement
  • Updated responder plugin contract to target sampler config (config_sampler_sim.yaml) and explicit custom responder path checks.
  • Updated template required files contract to require:
    • config/config.yaml
    • config/config_qa.yaml
    • config/config_scripted_sim.yaml
    • config/config_sampler_sim.yaml

Template updates

  • Reworked cookiecutter config files for readability and auditability:
    • section headers/comments, cleaner spacing, explicit mode scope per file
    • base config now human-only
    • qa config now qa-only and short smoke profile
    • removed legacy monolithic config/config_sim.yaml
    • added new config/config_scripted_sim.yaml and config/config_sampler_sim.yaml
  • Added responders/ folder in template with starter sampler responder scaffold:
    • responders/task_sampler.py
    • responders/__init__.py
    • responders/README.md
  • Updated template main.py default sim config mapping to config/config_scripted_sim.yaml.

Runtime/CLI

  • Updated task launcher sim shortcut default config to config/config_scripted_sim.yaml.

Tests

  • Expanded validator tests to cover:
    • split sim config files
    • responder folder/class checks
    • stricter smoke-profile expectations for qa/sim config profiles.

0.1.9 (2026-02-16)

Summary

  • Added versioned task contracts at psyflow/contracts/v0.1.0 for standardized psyflow/TAPS task development and audit.
  • Added new validator CLI: psyflow-validate <task_path> with per-contract PASS/WARN/FAIL feedback and actionable suggestions.
  • Added contract adoption tracking in task metadata via taskbeacon.yaml -> contracts.psyflow_taps.
  • Updated cookiecutter template to include taskbeacon.yaml, CHANGELOG.md, and assets/README.md so new tasks match required structure by default.
  • Expanded contract coverage with explicit checks for:
    • .gitignore task artifact rules (outputs/* / data/* migration-safe),
    • stimulus schema and asset-backed path conventions (image/movie/sound from assets/),
    • responder/sampler plugin standards (sim.responder.type, local responders/ class checks).
  • Added stricter config validation semantics across contracts (mandatory / optional / recommended keys and value specs).
  • Updated CI to install minimal validator dependency (pyyaml) and run contract validation smoke checks against the task template.
  • Added validator regression tests for asset-backed stimuli and responder contract failures.

New modules/files

  • psyflow/validate.py
  • psyflow/contracts/v0.1.0/*
  • tests/test_validate.py

Packaging/CLI

  • New console script: psyflow-validate
  • Included contracts/**/* in package data.

0.1.8 (2026-02-16)

Summary

  • Standardized responder configuration on responder.type (built-ins or import path); removed responder.kind/responder.class behavior.
  • Strengthened psyflow-qa into a strict gate:
    • qa.acceptance_criteria is now required in config/config_qa.yaml.
    • static + runtime + trace/events validation remains bundled in one command.
  • Added automatic post-QA metadata refresh on pass:
    • promotes taskbeacon.yaml maturity (no downgrade),
    • updates README maturity badge to a cleaner flat-square style.
  • Hardened README badge update for non-UTF8 files via encoding fallback.
  • Updated templates and MID example configs to use type consistently (triggers.driver.type, sim.responder.type).
  • Updated docs/tests for the new QA/sim contract and launcher behavior.

Files (high impact)

  • psyflow/task_launcher.py
  • psyflow/sim/loader.py
  • psyflow/sim/context.py
  • tests/test_task_launcher.py
  • tests/test_responder_contract.py
  • tests/test_sim_golden.py
  • docs/tutorials/cli_usage.md
  • docs/tutorials/qa_runner.md
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/config/config.yaml
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/config/config_qa.yaml
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/config/config_sim.yaml
  • pyproject.toml

0.1.7 (2026-02-15)

Summary

  • Removed qa and sim subcommands from root CLI (psyflow now exposes init only).
  • Standardized task execution on explicit task entrypoint arguments (python main.py [human|qa|sim] --config ...).
  • Removed framework emphasis on env-driven QA/sim wrappers from docs; documentation now points to task-level mode/config flow.

Breaking changes

  • Removed psyflow qa ... command.
  • Removed psyflow sim ... command.
  • QA/sim runs should now be started from the task script directly.

Updated files

  • psyflow/cli.py
  • tests/test_cli_root.py
  • docs/tutorials/cli_usage.md
  • docs/tutorials/qa_runner.md

0.1.6 (2026-02-15)

Summary

  • Added a sampler-ready simulation architecture with a stable responder plugin contract (SessionInfo, Observation, Action, Feedback, lifecycle hooks).
  • Added a centralized responder adapter and policy layer (strict|warn|coerce) used by StimUnit.capture_response() and StimUnit.wait_and_continue() so injected responses flow through one validation seam.
  • Added deterministic simulation plumbing (seed/session/rng), structured JSONL simulation audit logs, and replay helpers.
  • Added plugin loader/config support (built-ins + external import-path responders) and a demo external responder.
  • Moved runtime context/session plumbing under psyflow.sim.context so responder/runtime logic stays in sim.
  • Updated template MID task and T000006 MID task to standardize trial context fields (trial_id, phase, deadline_s, valid_keys, condition_id, task_factors) for simulation readiness.
  • Added contract and determinism tests for responder plugins and sim runs.
  • CLI redesign: moved to one root command psyflow with subcommands init, qa, sim for compact, terminal-friendly usage.
  • Breaking packaging change: removed separate script entrypoints (psyflow-init, psyflow-qa, psyflow-sim) in favor of psyflow.
  • Added clearer CLI terminal summaries for QA/sim runs (status + artifact paths).
  • Neutralized runtime env names (PSYFLOW_RESPONDER_*, PSYFLOW_OUTPUT_DIR, PSYFLOW_* timing knobs) to remove QA-specific naming in simulation paths.
  • Added shared runtime command executor helpers used by both qa and sim commands.

Breaking changes

  • CLI entrypoints changed:
    • old: psyflow-init, psyflow-qa, psyflow-sim
    • new: psyflow init, psyflow qa, psyflow sim
  • psyflow.__init__ CLI export changed from climain to cli_main.

New modules

  • psyflow/sim/contracts.py
  • psyflow/sim/adapter.py
  • psyflow/sim/loader.py
  • psyflow/sim/logging.py
  • psyflow/sim/rng.py
  • psyflow/sim/context_helpers.py
  • psyflow/sim/context.py
  • psyflow/sim/__init__.py
  • psyflow/sim_command.py
  • examples/sim/demo_responder.py
  • tests/test_responder_contract.py
  • tests/test_sim_golden.py
  • tests/test_sim_command.py
  • tests/test_cli_root.py

Core updates

  • psyflow/StimUnit.py
  • psyflow/sim/context.py
  • psyflow/qa/__init__.py
  • psyflow/qa_command.py
  • psyflow/sim_command.py
  • psyflow/cli.py
  • psyflow/commands/runtime.py
  • psyflow/utils/config.py
  • psyflow/__init__.py
  • pyproject.toml
  • setup.py

Template/docs updates

  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/main.py
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/src/run_trial.py
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/config/config.yaml
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/acceptance_criteria.yaml
  • README.md
  • docs/tutorials/cli_usage.md
  • docs/tutorials/qa_runner.md
  • docs/tutorials/getting_started.md
  • docs/tutorials/getting_started_cn.md

0.1.5 (2026-02-12)

Summary

  • StimUnit.add_stim(...) now recognizes PsychoPy runtime audio backends (via _SoundBase and resolved backend classes), fixing false rejections of valid sound stimuli.
  • Unsupported stimulus objects in StimUnit.add_stim(...) now emit a warning and raise a clearer TypeError that lists supported classes.
  • TaskSettings.from_dict(...) was hardened:
    • validates that input config is a dict,
    • passes only init=True dataclass fields into constructor,
    • validates trial_per_block / trials_per_block aliases for consistency,
    • enforces declared trials-per-block matches ceil(total_trials / total_blocks) when provided.
  • initialize_exp(...) now defaults to settings.screen when screen_id is not explicitly passed.

Files

  • psyflow/StimUnit.py
  • psyflow/TaskSettings.py
  • psyflow/utils/experiment.py

0.1.4 (2026-02-12)

Summary

  • Breaking cleanup: removed TriggerSender compatibility layer; trigger flow is now TriggerRuntime only.
  • Added initialize_triggers(...) bootstrap under psyflow.io, returning an opened TriggerRuntime.
  • Added TriggerRuntime.send(code, wait=True) for simple code-based trigger sends.
  • StimUnit now consumes runtime= directly and no longer supports legacy triggersender= fallback.
  • Utility cleanup: trigger bootstrap moved out of utils; display helper module renamed to utils/display.py; serial-port alias cleanup.
  • Cookiecutter template updated to the runtime-first trigger pattern (including QA mock setup).

Trigger API Simplification (Runtime-First)

Files:

  • psyflow/TriggerSender.py (removed)
  • psyflow/io/runtime.py
  • psyflow/io/trigger.py
  • psyflow/io/__init__.py
  • psyflow/StimUnit.py
  • psyflow/__init__.py

What changed

  • Removed TriggerSender and its exports from the public package API.
  • initialize_triggers(...) now builds driver + TriggerRuntime, calls runtime.open(), and returns the runtime directly.
  • Added TriggerRuntime.send(...) as a convenience for immediate integer trigger sends.
  • StimUnit trigger path is now runtime-only:
    • constructor uses runtime=...
    • internal trigger emit path uses runtime.emit(...)
    • legacy triggersender fallback path was removed.

Migration pattern

# before
from psyflow import TriggerSender
trigger_sender = TriggerSender(...)
trigger_sender.send(code)
StimUnit("trial", win, kb, triggersender=trigger_sender)

# after
from psyflow import initialize_triggers
trigger_runtime = initialize_triggers(cfg)
trigger_runtime.send(code)
StimUnit("trial", win, kb, runtime=trigger_runtime)

Utility/Module Structure Cleanup

Files:

  • psyflow/utils/ports.py
  • psyflow/utils/display.py (renamed from handy_display.py)
  • psyflow/utils/__init__.py
  • psyflow/io/trigger.py (trigger bootstrap moved from utils)

What changed

  • Removed list_serial_ports() alias; standardized on show_ports().
  • Renamed timing/display helper module to utils/display.py (exported API count_down remains available).
  • Moved trigger bootstrap ownership into psyflow.io for clearer package boundaries.

Template Migration (New Default Pattern)

Files:

  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/main.py
  • psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/src/run_trial.py

What changed

  • Template now initializes triggers via initialize_triggers(cfg).
  • QA mode uses initialize_triggers(mock=True) (no hardware dependency).
  • Task code uses trigger_runtime.send(...) and passes runtime=trigger_runtime into StimUnit.
  • Runtime is closed via trigger_runtime.close() during teardown.

0.1.3 (2026-02-11)

Summary

  • Timing/response-stage fixes in StimUnit (flip-synced stamps, RT consistency, close semantics, flip-locked offsets).
  • Trigger sending made safer for flip callbacks (TriggerSender.send(..., wait=False) path).
  • New trigger architecture (EEG/fMRI-ready): TriggerRuntime + pluggable drivers (MockDriver, SerialDriver, FanoutDriver), with TriggerSender kept as a thin compatibility wrapper.
  • Added QA tooling: psyflow-qa CLI + psyflow.qa helpers for static contract checks and runtime trace/event validation with standardized artifacts under outputs/qa/.
  • Added QA-mode scripted response injection in StimUnit.capture_response() and StimUnit.wait_and_continue() (no PsychoPy Keyboard emulation), plus optional QA timing scaling and trigger planned/executed event logging.
  • Correctness fixes: BlockUnit.summarize() no longer crashes; taps() locates templates correctly.
  • Packaging/import cleanup: Python >= 3.10 declared; LLM utilities removed (deps/docs/exports cleaned); import psyflow is now lazy.
  • Docs hygiene: docs deployment now does a clean build; trigger/CLI tutorials updated to be copy-paste safe.
  • Basic CI smoke checks added.

QA runner + scripted responder (static QA + runtime QA)

Files:

  • psyflow/qa/*
  • psyflow/qa_cli.py
  • psyflow/StimUnit.py
  • pyproject.toml
  • setup.py
  • docs/tutorials/qa_runner.md
  • docs/index.rst

What changed

  • Added a lightweight QA runner CLI: psyflow-qa <task_dir> [--runtime-cmd \"python main.py\"].
    • Always runs static checks (config/acceptance contract lint).
    • Writes machine-readable static artifacts:
      • outputs/qa/static_report.json
      • outputs/qa/contract_report.json
    • If --runtime-cmd is provided, runs the task in a subprocess with PSYFLOW_MODE=qa and validates:
      • outputs/qa/qa_trace.csv against acceptance_criteria.yaml (required columns) + generic invariants.
      • outputs/qa/qa_events.jsonl for trigger planned vs executed mismatches (when present).
  • Introduced a pure-Python psyflow.qa subpackage (no PsychoPy imports) for:
    • acceptance criteria lint (contract_lint)
    • static checks (static_qa)
    • trace CSV validation (validate_trace_csv)
    • QA event validation (validate_events)
    • standardized failure taxonomy in outputs/qa/qa_report.json.
    • optional static validation of task.key_list (non-empty, subset of allowed_keys when provided) and trigger-code sanity (int-or-null, uniqueness when present).
  • pyyaml is required only when loading YAML files from disk (load_yaml()); the QA modules remain importable in minimal environments (e.g., CI) without installing dependencies.

Responder injection seam (psyflow-level, minimal surface)

  • StimUnit.capture_response() and StimUnit.wait_and_continue() can now inject scripted responses when a QA context is active (mode=qa):
    • responder acts on an Observation dict (stim descriptor fields are optional and pulled from StimUnit state when present).
    • no attempt is made to emulate PsychoPy's Keyboard API globally.

QA timing scaling (opt-in)

  • QA mode supports opt-in duration scaling with guardrails:
    • default: scaling disabled
    • never below one refresh interval
    • min_frames default 2 in QA mode
    • logs both nominal and scaled durations into state (*_duration_nominal, *_duration_scaled) when scaling is enabled.

Trigger planned vs executed logging (QA mode)

  • In QA mode, flip-scheduled triggers are logged as:
    • trigger_planned (when scheduled)
    • trigger_executed (when the send path runs)
    • JSONL stream: outputs/qa/qa_events.jsonl (when QA context is active).

Template updates (cookiecutter)

  • The bundled task template now includes:
    • acceptance_criteria.yaml with required columns and basic expectations.
    • config/config.yaml so the scaffold runs out of the box.
    • PSYFLOW_MODE=qa support to skip GUI subject info and avoid hardware triggers.

TriggerRuntime + Driver Architecture (EEG/fMRI-ready)

Files:

  • psyflow/io/*
  • psyflow/TriggerSender.py
  • psyflow/StimUnit.py
  • docs/tutorials/send_trigger.md

What changed

  • Added TriggerRuntime (timing semantics + audit logging) and driver abstractions:
    • MockDriver: development/QA without hardware
    • SerialDriver: pyserial-backed byte writes (encoder lives in the driver)
    • FanoutDriver: broadcast to multiple drivers
    • CallableDriver: wrap a custom send function
  • TriggerSender is now a thin compatibility wrapper around TriggerRuntime. Existing code calling TriggerSender.send(code) continues to work.
  • StimUnit.show() and StimUnit.capture_response() now emit triggers through TriggerRuntime when available (automatically picked up via TriggerSender.runtime), preserving flip-locked semantics via the runtime.

StimUnit.run fixed-window response stage (behavior + API)

File: psyflow/StimUnit.py

What changed

  • StimUnit.run(terminate_on_response=True) now truly ends the stage immediately after the first valid response (or timeout) is registered.
  • Previously, run() kept flipping until the timeout-derived window ended and only stopped drawing stimuli after a response, which could silently add blank time and made "trial end" ambiguous.
  • run() now supports an explicit fixed-length window mode that keeps flipping for the full window duration even after a response/timeout is registered.

New parameters (keyword-only)

  • fixed_response_window: bool = False: If True, the stage does not end early; it continues flipping until the window ends.
  • post_response_display: str = "stimuli": Only used when fixed_response_window=True. Values: "stimuli" (keep drawing) or "blank" (stop drawing) after a response/timeout is registered, until the window ends.
  • max_duration: float | None = None: If provided, explicitly sets the stage window length (seconds) and overrides any timeout-derived duration.

Window duration rules

  • Priority 1: max_duration (explicit window length).
  • Priority 2: maximum registered on_timeout() duration.
  • Priority 3: if fixed_response_window=True and no duration source exists, raise ValueError.
  • Priority 4: otherwise fall back to 5.0 seconds (backward-compatible default).

Response/keyboard handling change

  • Response hooks fire only for the first valid response; subsequent keypresses do not trigger more hooks in the same run() call.
  • Key events are still drained every frame to reduce spillover into later stages.

Migration notes

  • Old "keep flipping but stop drawing after response" behavior can be replicated with fixed_response_window=True, post_response_display="blank", and a defined window length (via max_duration or on_timeout() hooks).

Example

# End immediately on response (now matches terminate_on_response semantics)
StimUnit("trial", win, kb).add_stim(stim).run(terminate_on_response=True)

# Fixed 1.0s window; keep stimuli visible after response
StimUnit("trial", win, kb).add_stim(stim).run(
    fixed_response_window=True,
    post_response_display="stimuli",
    max_duration=1.0,
)

# Fixed 1.0s window; blank after response (closest to the old implicit behavior)
StimUnit("trial", win, kb).add_stim(stim).run(
    fixed_response_window=True,
    post_response_display="blank",
    max_duration=1.0,
)

Flip-synced onset timestamps (callOnFlip argument evaluation fix)

File: psyflow/StimUnit.py

What changed

  • Onset timestamps are now evaluated at flip-time by scheduling a callback that reads clocks inside the win.flip() callback, instead of passing pre-evaluated float values into win.callOnFlip(...).
  • Added internal helper StimUnit._stamp_onset(...) and updated these methods to use it:
    • StimUnit.run()
    • StimUnit.show()
    • StimUnit.capture_response()
    • StimUnit.wait_and_continue()

Related ordering change

  • In response-related methods, kb.clearEvents and clock.reset are now scheduled on the flip (before stamping onset) to reduce pre-onset spillover and make "time zero" align with the onset flip more reliably.

RT consistency + stage-close semantics + flip-locked offsets

Files:

  • psyflow/StimUnit.py

What changed

  • RTs are now based on PsychoPy's asynchronous keyboard timestamps (KeyPress.rt) rather than poll-time (self.clock.getTime()) where applicable.
    • StimUnit.capture_response() now uses kp.rt for rt/response_time.
    • StimUnit.wait_and_continue() now uses kp.rt for response_time.
  • The keyboard clock is now explicitly reset on the onset flip (via win.callOnFlip) so that KeyPress.rt is unambiguously onset-relative:
    • StimUnit.run()
    • StimUnit.capture_response()
    • StimUnit.wait_and_continue()
  • Stage-close fields are now more consistent:
    • Added internal helper StimUnit._stamp_close(...) and schedule it on the final flip for fixed-duration stages (flip-synced close).
    • In capture_response(), close_time is only set to the response RT when terminate_on_response=True; otherwise it is stamped at the end of the response window.
  • StimUnit.show() offset triggers are now flip-locked:
    • Offset trigger + close_time stamping are scheduled via win.callOnFlip(...) on the final displayed frame.
    • Added offset_flip_time to record the final flip's timestamp (from win.flip()).

Timing contract (current)

  • onset_time and close_time are stage-local seconds on StimUnit.clock, which is reset on the onset flip.
  • onset_time_global and close_time_global are epoch seconds for those events.
    • close_time_global is computed consistently from onset_time_global + close_time when possible (including in _stamp_close).
  • flip_time (and offset_flip_time in show()) are the return value of PsychoPy win.flip() (PsychoPy's monotonic timebase; not epoch).
  • rt / response_time come from PsychoPy Keyboard RTs (asynchronous, onset-relative). response_time_global is derived as onset_time_global + rt.

TriggerSender jitter reduction for flip callbacks

Files:

  • psyflow/TriggerSender.py
  • psyflow/StimUnit.py

What changed

  • TriggerSender.send(code, wait=True) now accepts wait: bool and no longer prints on every send.
  • When wait=False, TriggerSender.send(...) skips core.wait(post_delay) and does not call on_trigger_end.
    • This is intended for use inside flip callbacks (e.g., via win.callOnFlip) to reduce the risk of dropped frames.
  • StimUnit.send_trigger(code, wait=True) now forwards wait, and all flip-scheduled trigger sends in StimUnit.show() / StimUnit.capture_response() use wait=False.

StimUnit TriggerSender import (typing/runtime fix)

File: psyflow/StimUnit.py

  • Replaced from psyflow import TriggerSender with a relative import (from .TriggerSender import TriggerSender) so that Optional[TriggerSender] type annotations refer to the class (not the submodule).

BlockUnit summarize() fix + quieter condition generation

Files:

  • psyflow/BlockUnit.py

What changed

  • BlockUnit.summarize() no longer crashes (it now summarizes from self.results instead of mistakenly iterating the return value of to_dict()).
  • BlockUnit.generate_conditions() no longer prints the full condition list to stdout; it logs via PsychoPy logging instead.

taps() template path fix

Files:

  • psyflow/utils.py

What changed

  • utils.taps() now locates the bundled cookiecutter template via importlib.resources.files("psyflow.templates") / template (instead of looking under the package root).

Minimal config validation helper

Files:

  • psyflow/utils.py

What changed

  • Added validate_config(...) for lightweight top-level section/type checks.
  • load_config(..., validate=True, required_sections=...) can now opt into validation at load time.

Import/Packaging Cleanup (Post-LLM Removal)

Files:

  • psyflow/__init__.py
  • pyproject.toml
  • setup.py
  • docs/index.rst
  • docs/api/psyflow.rst
  • docs/tutorials/getting_started.md
  • docs/tutorials/getting_started_cn.md
  • docs/tutorials/get_subinfo.md
  • docs/tutorials/get_subinfo_cn.md

What changed

  • psyflow/__init__.py now uses lazy exports (__getattr__) so import psyflow is lightweight and does not import PsychoPy unless needed.
  • Declared Python requirement >=3.10 (PEP 604 unions are used in the codebase).
  • Removed LLM-related dependencies from packaging metadata after removing the LLM utilities.
  • Removed LLM-related docs entry points from the docs tree.

CI Smoke Checks

Files:

  • .github/workflows/ci.yml
  • tests/test_smoke.py

What changed

  • Added a basic CI workflow to compile sources and run a small unittest smoke suite.

ASCII Cleanup (Console/Encoding Robustness)

Files:

  • psyflow/StimBank.py
  • psyflow/StimUnit.py
  • psyflow/SubInfo.py
  • psyflow/cli.py

What changed

  • Replaced non-essential emoji/fancy punctuation with ASCII to avoid console encoding issues on Windows setups.