diff --git a/dev-reports/issue-490/design.md b/dev-reports/issue-490/design.md new file mode 100644 index 000000000..b8fe0a3e3 --- /dev/null +++ b/dev-reports/issue-490/design.md @@ -0,0 +1,41 @@ +# Issue #490 design + +Baseline: `b13ef093e462084956bb792e10616d6848e00f40`, including #466, +#478 and #488. The working tree was clean. Read the full reviewed Issue +narrative (R1–R5), frozen campaign decision/report, harness and relevant code. + +The defect is in `admission::preserve`: every same-kind/path Verify is +collected as an output owner. A later package reader therefore prevents an +otherwise intact original reader from satisfying the all-owner boundary. + +Add a leaf module for supported reader obligations. Initially support the +closed profile package-manifest inspection instruction and the existing exact +Next.js package-check grammar. Require both: neither Verify kind, a path, +instruction wording alone, nor arbitrary JavaScript establishes read-only +behavior. Creation instructions, side effects and unknown external scripts +continue through the existing ownership rule. No runtime metadata is changed. + +Match each acquired reader to one distinct proposed reader containing its +instruction, expected result, all required inputs and complete check group. +Keep reader order and both sides of the original executable-owner boundaries, +including split owners. Additional readers do not enlarge output ownership. +Model and host capture, formation, lint, registration, fallback and the existing +three-attempt limit remain authoritative. + +Port frozen P01/P02-D input and N01–N07 differences into portable corpus. +First reproduce P02-D failure through normal generation, persistence, before-phase +and registration. Add atomic-duty, acquired before/update/after, non-applicability, +host fallback and runtime pass/fail/missing controls. Preserve registration paths, +commands, producer scope and the artifact-only README rule. Record original build +loss separately as an unresolved preset-conversion diagnostic. + +Run focused regressions, related #466/#478/#488 and corpus checks, fmt, clippy and +the complete test suite using the dispatched dedicated target/cache/tmp/runtime. +This is a planner change, not a release or live application evaluation. Commit +only task-owned code, portable fixtures and the three worker contract documents. + +The existing flow file was already at its total growth limit with a zero test +budget. Move its unchanged phase-entry sequence (resolve, persist, before-phase, +register) to `phase/phase_entry.rs` and call it from flow. Test checkpoint/observation +hooks live in that leaf; production order, events and error handoffs are preserved. +This reduces the chokepoint rather than raising its baseline. diff --git a/dev-reports/issue-490/implementation-summary.md b/dev-reports/issue-490/implementation-summary.md new file mode 100644 index 000000000..079c3e203 --- /dev/null +++ b/dev-reports/issue-490/implementation-summary.md @@ -0,0 +1,64 @@ +# Issue #490 implementation + +Supported package readers now retain one complete obligation on one distinct +Verify execution instead of joining all same-path readers into an output-owner +set. Matching preserves instruction, expected result, required inputs, complete +checks, reader order and both sides of original executable owners (including +split outputs). No expected_paths, runtime prechecks or registered paths are +removed. Unclassified Verify steps sharing the input remain potential owners +and block the exemption. + +Support requires the exact existing package-manifest inspection instruction and +nonempty commands accepted by the exact generated Next.js package-check grammar. +Other instructions, creation work, arbitrary inline code, unknown external +scripts and model read-only claims retain the existing ownership rule. There is +no side-effect inference or unconditional Verify exemption. + +`admission::preserve` calls the new reader leaf, then applies the unchanged owner +rule to other obligations. Existing model/host acquisition, #488 projection, +formation, lint, finish/fallback, registration, final acceptance and the +three-attempt limit remain in place. Events and schemas are unchanged. + +The phase-entry sequence was mechanically extracted from `phase/flow.rs` to +`phase/phase_entry.rs` so portable replay hooks fit without increasing guardrail +baselines. The product still resolves, saves, runs before_phase and registers +before execution, with the same error handoffs and event order. Test hooks only +restore the witnessed setup checkpoint, observe persisted/registered state and +stop after successful core registration. They do not modify proposals or bypass +Admission. The flow chokepoint shrank from 1,690 to 1,575 lines. + +## Regression evidence + +- P02-D failed before the production change with the documented complete-scope / + all-owner-boundary error, exhausted three attempts and never registered core. + The same portable raw input now registers with both package paths intact. +- P01 registers; N01–N05/N07 refuse their targeted loss and leave the core contract + unchanged. N06 is host reaugmentation recovery, explicitly separate from + acquired-host refusal evidence. +- R1: actual Admission capture and finish/fallback tests reject missing reader + instruction, result, checks, input, instruction/check collage, distributed + checks and ID/path-only correspondence. +- R2: a history acquiring both before/update/after obligations has a passing + control. Either deletion, cross-update movement or one-execution merge fails. + Normal-flow tests also register the positive and refuse deletions/movements. +- R3: creation, side-effect, external-script and self-declared-reader controls + do not receive the exemption, including extra unknown same-path Verify steps. +- R4: every P02-D step has pinned metadata/precheck flags. Original, retained and + added readers run real package checks for pass, wrong-port failure and missing + input. Pass short-circuits; failures reach the existing execution/repair path + and propagate its controlled error. The same runtime test passes with original + ownership wiring and the fix; missing input remains a missing-path and existing + dependency-boundary result. +- R5: the existing #478 acquired-host Admission and finish/fallback refusal test + remains unchanged and is included in related/full verification. +- Registration retains the old contract and adds exactly README.md, + verify-ui.cjs and node verify-ui.cjs. The sole new producer is script-only + Implement/pass. Saved/reloaded plan, before_phase result, live contract and + saved contract agree. `test -f README.md` remains only in the plan. + +The committed corpus contains normalized frozen inputs, acquisition history, +variants, expected registration/runtime metadata and provenance hashes. Tests +need no historical run or local absolute path. Original preset build loss is +retained in a separate stage-correct diagnostic; it is not fixed by #490. +No live app/model/GUI success, #488 projection improvement, release or Issue +lifecycle change is claimed. diff --git a/dev-reports/issue-490/verification.md b/dev-reports/issue-490/verification.md new file mode 100644 index 000000000..bbadc8cee --- /dev/null +++ b/dev-reports/issue-490/verification.md @@ -0,0 +1,75 @@ +# Issue #490 verification + +- Status: `passed` + +## Checks + +- `cargo test --offline --lib issue490 -- --nocapture`: `passed` +- `cargo test --offline --lib recovery_step_plan_binding`: `passed` +- `cargo test --offline --test corpus_regression --test generality_guardrails`: `passed` +- `cargo fmt --all -- --check`: `passed` +- `cargo clippy --offline --all-targets -- -D warnings`: `passed` +- `cargo test`: `passed` +- `git diff --check`: `passed` + +## Results and boundaries + +The focused suite passed all 8 tests (with per-case loops). Related binding tests +passed 57, including the unchanged +`issue478_admission_and_fallback_refuse_missing_or_nonexecuting_host_duties`, +#466 finite retry/fallback/registration and #488 admission/registration controls. +Corpus passed 7 tests; guardrails passed 10 without baseline changes. +The complete cargo test command exited 0: library tests passed 2,632 with 19 +existing ignored tests, followed by successful integration and documentation +tests. Existing opt-in/ignored tests were not enabled. Final clippy has no warnings. + +Before the production fix, the normal-flow P02-D test failed with exit 101: +`formation lost original check/expected result or its complete scope and boundary`. +It exhausted the existing three attempts and did not register core. The P01 / +N01–N07 companion test passed on that baseline. This expected red regression is +the before-fix demonstration, not a failed final check. After the fix, identical +P02-D input passes generation, save/reload, before_phase and normal registration, +with no path deletion or Admission intervention. + +The runtime test was also run successfully with the original admission ownership +wiring temporarily restored (`cargo test --offline --lib issue490_runtime -- +--nocapture`), then with the fix and again in the complete suite. It pins metadata +and precheck applicability for all six proposed steps and executes nine reader +controls: original/retained/additional reader × pass/wrong-port/missing input. +Missing package.json retains both missing-path and existing dependency-boundary +observations; failure and absence enter the ordinary executor/repair boundary +and propagate its controlled error. Neither is counted as a successful precheck. +No runtime verification implementation was changed. + +Normal-flow observations assert exact script/path additions, unchanged existing +commands/paths, script-only Implement/pass producer, complete model/host +instructions, unchanged saved/read-back plan and agreement between live and saved +run contracts. README existence stays a plan check, excluded from final-success +commands by the existing artifact-only rule. Reader-attribute and acquired +before/update/after matrices additionally exercise actual Admission capture and +finish/fallback; full-flow controls independently test registration/refusal. +N06 is recorded as host restoration, not evidence of host-loss refusal. + +The separate build-loss diagnostic reproduces the frozen sanitized-to-preset +transition, including its original conversion goal, and remains a known issue. +No live application, GUI, model service or #488 projection improvement is claimed. +This is not a release task; release build/version/hash checks are not applicable. + +## Execution environment and evidence + +Baseline commit: `b13ef093e462084956bb792e10616d6848e00f40`. +Every cargo process exported the dispatched dedicated locations: + +```text +CARGO_TARGET_DIR=/Volumes/SSD_NX/tmp/commandagent-orchestrate-490-20260917-01/target +CARGO_HOME=/Volumes/SSD_NX/tmp/commandagent-orchestrate-490-20260917-01/cache/cargo +TMPDIR=/Volumes/SSD_NX/tmp/commandagent-orchestrate-490-20260917-01/tmp +``` + +Raw logs remain uncommitted in that run's `runtime/`: `pre-fix.log`, +`runtime-baseline.log`, `focused.log`, `related.log`, `corpus-guardrails.log`, +`fmt-check.log`, `clippy.log`, and `full-test.log`. The portable corpus's provenance +hashes and absence of local absolute paths were checked. CI uses only committed +fixtures and temporary workspaces; it does not require these logs or the old SSD +campaign. HOME, live `.anvil`, frozen experiments and external Issue state were +not changed. diff --git a/src/planner/recovery_step_plan_binding.rs b/src/planner/recovery_step_plan_binding.rs index 4de64bd7e..0337d399b 100644 --- a/src/planner/recovery_step_plan_binding.rs +++ b/src/planner/recovery_step_plan_binding.rs @@ -16,6 +16,7 @@ mod literal_marker_formation; pub(crate) mod package_owner_scope; mod package_script_formation; pub(crate) mod profile_augmentation; +mod reader_obligations; mod verifier_formation; pub(crate) fn bind_generated( diff --git a/src/planner/recovery_step_plan_binding/admission.rs b/src/planner/recovery_step_plan_binding/admission.rs index 4b6fea1b3..4cc767a13 100644 --- a/src/planner/recovery_step_plan_binding/admission.rs +++ b/src/planner/recovery_step_plan_binding/admission.rs @@ -355,7 +355,11 @@ fn check_model_ownership( } pub(super) fn preserve(original: &StepPlan, proposed: &StepPlan) -> anyhow::Result<()> { + super::reader_obligations::preserve(original, proposed)?; for step in &original.steps { + if super::reader_obligations::supported(step) { + continue; + } let mut owner_indices = Vec::new(); for path in &step.expected_paths { let expected = scope::normalized(path)?; diff --git a/src/planner/recovery_step_plan_binding/issue466_tests.rs b/src/planner/recovery_step_plan_binding/issue466_tests.rs index 2c980b414..a92ab3264 100644 --- a/src/planner/recovery_step_plan_binding/issue466_tests.rs +++ b/src/planner/recovery_step_plan_binding/issue466_tests.rs @@ -171,3 +171,6 @@ mod issue484; #[path = "issue488_tests.rs"] mod issue488; + +#[path = "issue490_tests.rs"] +mod issue490; diff --git a/src/planner/recovery_step_plan_binding/issue490_tests.rs b/src/planner/recovery_step_plan_binding/issue490_tests.rs new file mode 100644 index 000000000..2b1f7730a --- /dev/null +++ b/src/planner/recovery_step_plan_binding/issue490_tests.rs @@ -0,0 +1,230 @@ +//! Admission/finish controls, distinct from the normal-flow registration replay. +use super::*; + +fn fixture(name: &str) -> serde_json::Value { + serde_json::from_slice( + &std::fs::read( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/corpus/apps/issue490-verify-readers") + .join(name), + ) + .unwrap(), + ) + .unwrap() +} +fn acquired() -> (StepPlan, StepPlan) { + let p = fixture("acquired-before-after.json"); + ( + serde_json::from_value(p["original"].clone()).unwrap(), + serde_json::from_value(p["proposed"].clone()).unwrap(), + ) +} +fn check(after: StepPlan, refusal: bool) { + let root = tempfile::tempdir().unwrap(); + let c = issue478::nextjs_config(root.path()); + let (raw, _) = acquired(); + let mut original = raw.clone(); + let mut admission = admission::Admission::default(); + admission.strengthen(&c, &mut original); + assert!(matches!( + admission + .check(&c, Some("core-implementation"), &raw, &mut original, 1) + .unwrap(), + admission::Decision::Retry(_) + )); + let log = events(&c); + let capture = log + .iter() + .find(|e| e["event"] == "recovery_verifier_plan_admission") + .unwrap(); + assert!( + capture["reason"] + .as_str() + .unwrap() + .contains("mixes application/configuration scope") + ); + let captured: StepPlan = serde_json::from_value(capture["original_scope"].clone()).unwrap(); + assert_eq!(captured.steps[0], raw.steps[0]); + assert_eq!(captured.steps.last(), raw.steps.last()); + let mut candidate = after.clone(); + admission.strengthen(&c, &mut candidate); + let decision = admission + .check(&c, Some("core-implementation"), &after, &mut candidate, 2) + .unwrap(); + if refusal { + let admission::Decision::Retry(reason) = decision else { + panic!("lost reader accepted"); + }; + assert!( + reason.contains("lost original read-only Verify duty"), + "{reason}" + ); + let error = admission + .finish(&c, Some("core-implementation"), candidate) + .unwrap_err(); + assert!(error.to_string().contains("cannot return fallback")); + assert!(error.to_string().contains("read-only Verify duty")); + } else { + assert!(matches!(decision, admission::Decision::Ready(false))); + assert_eq!( + admission + .finish(&c, Some("core-implementation"), candidate.clone()) + .unwrap(), + candidate + ); + assert_eq!(candidate.steps[0].instruction, raw.steps[0].instruction); + assert_eq!(candidate.steps[0].verify, raw.steps[0].verify); + assert!( + candidate + .steps + .iter() + .any(|s| s.verify == raw.steps.last().unwrap().verify) + ); + } +} +#[test] +fn issue490_acquired_reader_attributes_remain_atomic_in_admission_and_finish() { + let (_, valid) = acquired(); + check(valid.clone(), false); + for case in fixture("reader-cases.json")["atomic"].as_array().unwrap() { + let case = case.as_str().unwrap(); + let mut p = valid.clone(); + match case { + "instruction" => p.steps[0].instruction = "Unrelated check".into(), + "expected_result" => p.steps[0].expected_result = "fail".into(), + "checks" => { + p.steps[0].verify.remove(0); + } + "input" => p.steps[0].expected_paths.clear(), + "instruction_check_collage" => { + let mut checks = p.steps[0].clone(); + checks.id = "checks-only".into(); + checks.instruction = "Run package checks".into(); + p.steps[0].verify.clear(); + p.steps.insert(1, checks); + } + "distributed_checks" => { + let commands = p.steps[0].verify.clone(); + p.steps[0].verify = vec![commands[0].clone()]; + let mut part = p.steps[0].clone(); + part.id = "remaining-checks".into(); + part.verify = commands[1..].to_vec(); + p.steps.insert(1, part); + } + "id_path_only" => { + p.steps[0].instruction = "Different duty".into(); + p.steps[0].verify.clear(); + } + _ => panic!("unknown {case}"), + } + check(p, true); + } +} +#[test] +fn issue490_acquired_before_update_after_cannot_delete_move_or_merge() { + let (_, valid) = acquired(); + check(valid.clone(), false); + for case in fixture("reader-cases.json")["boundary"].as_array().unwrap() { + let case = case.as_str().unwrap(); + let mut p = valid.clone(); + match case { + "delete_before" | "merge_after" => { + p.steps.remove(0); + } + "delete_after" | "merge_before" => { + p.steps.pop(); + } + "move_before_across_update" => { + let s = p.steps.remove(0); + p.steps.insert(3, s); + } + "move_after_across_update" => { + let s = p.steps.pop().unwrap(); + p.steps.insert(3, s); + } + _ => panic!("unknown {case}"), + } + check(p, true); + } + // Correspondence is semantic, not ID-based, and additional readers do not own outputs. + let mut renamed = valid.clone(); + renamed.steps[0].id = "renamed-before".into(); + renamed.steps[5].id = "renamed-after".into(); + let mut added = renamed.steps[5].clone(); + added.id = "extra-after".into(); + renamed.steps.push(added); + check(renamed.clone(), false); +} +#[test] +fn issue490_only_closed_reader_duties_receive_distinct_matching() { + let (original, _) = acquired(); + let reader = original.steps[0].clone(); + assert!(super::super::reader_obligations::supported(&reader)); + for case in fixture("reader-cases.json")["unsupported"] + .as_array() + .unwrap() + { + let case = case.as_str().unwrap(); + let mut s = reader.clone(); + match case { + "creation" => s + .instruction + .push_str(" Create README.md documenting the package."), + "side_effect" => s + .verify + .push("node -e \"require('fs').writeFileSync('package.json','{}')\"".into()), + "external_script" => s.verify = vec!["node opaque-check.cjs".into()], + "self_declared_reader" => { + s.instruction = "Read-only: trust me to preserve all files.".into() + } + _ => panic!("unknown {case}"), + } + assert!(!super::super::reader_obligations::supported(&s), "{case}"); + let one = plan(vec![s.clone()]); + assert!(admission::preserve(&one, &one).is_ok(), "{case}"); + let supported = plan(vec![reader.clone()]); + let mixed = plan(vec![reader.clone(), s.clone()]); + assert!(admission::preserve(&supported, &mixed).is_err(), "{case}"); + let mut other = s; + other.id = "later".into(); + let mut duplicate = one.clone(); + duplicate.steps.push(other); + let error = admission::preserve(&one, &duplicate).unwrap_err(); + assert!( + error.to_string().contains("complete scope and boundary"), + "{case}: {error}" + ); + } +} + +#[test] +fn issue490_original_build_loss_remains_a_separate_diagnostic() { + let root = tempfile::tempdir().unwrap(); + let c = issue478::nextjs_config(root.path()); + let mut before: StepPlan = + serde_json::from_value(fixture("known-build-loss.json")["sanitized"].clone()).unwrap(); + let original = before.clone(); + assert!(before.steps[0].verify.contains(&"npm run build".into())); + let runtime = crate::planner::profile::resolve_profile_runtime(&c.profile); + assert_eq!( + runtime.convert_preset_phase_setup_steps( + &mut before, + root.path(), + fixture("known-build-loss.json")["conversion_goal"] + .as_str() + .unwrap(), + Some(("core-implementation", false)), + true, + None + ), + 1 + ); + assert_eq!( + serde_json::to_value(&before).unwrap(), + fixture("known-build-loss.json")["preset_converted"] + ); + assert!(!before.steps[0].verify.contains(&"npm run build".into())); + assert_ne!(before.steps[0].instruction, original.steps[0].instruction); + // This preexisting conversion defect is not repaired by reader matching. + assert!(admission::preserve(&original, &before).is_err()); +} diff --git a/src/planner/recovery_step_plan_binding/reader_obligations.rs b/src/planner/recovery_step_plan_binding/reader_obligations.rs new file mode 100644 index 000000000..a840a14d2 --- /dev/null +++ b/src/planner/recovery_step_plan_binding/reader_obligations.rs @@ -0,0 +1,112 @@ +//! A supported reader consumes inputs; it does not own their production. +//! This is a closed profile check grammar, not side-effect analysis of scripts. +use super::*; +use crate::planner::recovery_contract_authority::verifier_obligations as scope; +use crate::planner::recovery_inspection::verifier_obligations::{FailureClass, failure}; + +pub(super) fn supported(step: &PlanStep) -> bool { + // Require the complete bounded inspection duty AND exact product-generated + // commands. Kind, ID, paths or a model's read-only claim are insufficient. + // Other instructions (including appended creation work), empty checks, + // arbitrary inline code and external scripts retain the old ownership rule. + step.step_kind() == StepKind::Verify + && step.instruction + == "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure." + && !step.expected_paths.is_empty() + && step + .expected_paths + .iter() + .all(|p| scope::normalized(p).is_ok_and(|p| p == "package.json")) + && !step.verify.is_empty() + && step + .verify + .iter() + .all(|c| crate::planner::profiles::nextjs::recovery_authority::is_package_check(c)) +} + +pub(super) fn preserve(original: &StepPlan, proposed: &StepPlan) -> anyhow::Result<()> { + let mut previous = None; + for (original_index, reader) in original.steps.iter().enumerate() { + if !supported(reader) { + continue; + } + // An unclassified Verify sharing the input might also write it. Do not + // silently remove that potential owner from the old ownership boundary. + let unknown_owner = proposed.steps.iter().any(|step| { + step.step_kind() == StepKind::Verify + && scope::overlaps(reader, step) + && !supported(step) + }); + // One complete, distinct execution discharges one acquired obligation. + // Greedy ordered matching leaves maximum room for subsequent readers. + let matched = proposed + .steps + .iter() + .enumerate() + .find(|(index, candidate)| { + !unknown_owner + && previous.is_none_or(|previous| previous < *index) + && supported(candidate) + && candidate.instruction == reader.instruction + && candidate.expected_result == reader.expected_result + && reader.expected_paths.iter().all(|p| { + candidate + .expected_paths + .iter() + .any(|c| scope::normalized(p).ok() == scope::normalized(c).ok()) + }) + && reader.verify.iter().all(|c| candidate.verify.contains(c)) + && within_boundary(original, proposed, original_index, *index) + }); + let Some((index, _)) = matched else { + return Err(failure( + FailureClass::ProposalRepairable, + format!( + "formation lost original read-only Verify duty {}: keep instruction, expected result, required inputs and complete checks together on a distinct Verify within its original output-owner boundary", + reader.id + ), + )); + }; + previous = Some(index); + } + Ok(()) +} + +fn within_boundary( + original: &StepPlan, + proposed: &StepPlan, + reader_index: usize, + candidate_index: usize, +) -> bool { + original.steps.iter().enumerate().all(|(index, owner)| { + if !matches!(owner.step_kind(), StepKind::Implement | StepKind::Setup) { + return true; + } + // Retain both sides, including all outputs of a split producer. The + // ordinary preservation rule separately validates each owner's duties. + owner.expected_paths.iter().all(|path| { + let Ok(path) = scope::normalized(path) else { + return false; + }; + let owners: Vec<_> = proposed + .steps + .iter() + .enumerate() + .filter(|(_, s)| { + s.step_kind() == owner.step_kind() + && s.expected_paths + .iter() + .any(|p| scope::normalized(p).is_ok_and(|p| p == path)) + }) + .collect(); + !owners.is_empty() + && owners.iter().all(|(candidate_owner, _)| { + if index < reader_index { + *candidate_owner < candidate_index + } else { + candidate_index < *candidate_owner + } + }) + }) + }) +} diff --git a/src/planner/runner/phase/flow.rs b/src/planner/runner/phase/flow.rs index bdd2370bd..591df9c9d 100644 --- a/src/planner/runner/phase/flow.rs +++ b/src/planner/runner/phase/flow.rs @@ -61,6 +61,8 @@ mod fix_before; mod investigation_before; #[path = "phase_boundary.rs"] mod phase_boundary; +#[path = "phase_entry.rs"] +mod phase_entry; #[path = "../../ultra_plan_flow/phase_plan_resolution.rs"] mod phase_plan_resolution; #[path = "../../pipeline.rs"] @@ -314,146 +316,29 @@ pub fn run_ultra_plan_with_ui( let mut promotion_state = ProfilePromotionState::for_run(plan, config); let mut setup_authority_state = UltraRunSetupAuthorityState::default(); emit_ultra_context_initialized(config, plan, &ultra_context, ultra_session.messages.len()); - let mut phase_machine = pipeline::PhaseRun::start()?; + let mut phase_machine = phase_entry::start(config, plan, &mut ultra_context)?; let phases = plan.phases.clone(); for (index, phase) in phases.iter().enumerate() { let runtime = resolve_profile_runtime(&plan.profile); - if ui.interrupted() { - phase_machine.interrupt("phase_start")?; - anyhow::bail!("interrupted by user"); - } - emit_ultra_phase_event( - config, - "ultra_phase_start", - plan, - phase, - index, - "start", - None, - None, - None, - ); - let profile_snapshot = profile_before_plan(&config.workspace_root, plan)?; - ultra_context.emit_attached(config, plan, phase, index, &ultra_session); - phase_machine.phase_started()?; let final_phase = index + 1 == plan.phases.len(); - let phase_prompt = - ultra_phase_prompt(plan, phase, config, &ultra_context, fix_runtime.as_ref()); - let phase_prompt = crate::planner::pack::runtime::append_phase_material_from_environment( - phase_prompt, - &config.workspace_root, - &plan.profile, - &plan.intent, - &phase.id, - )?; - let step_plan_result = phase_machine.resolve( + let Some((step_plan, profile_snapshot)) = phase_entry::prepare( planner, - &phase_prompt, - config, - ui, - phase, - plan, - fix_runtime.as_ref(), - preset_plan, - final_phase, - ); - let mut step_plan = step_plan_result.map_err(|err| { - let rejected_verify_commands = - crate::planner::lint_rejection::rejected_commands_from_error(&err); - let message = err.to_string(); - emit_ultra_phase_event( - config, - "ultra_phase_failed", - plan, - phase, - index, - "scaffold", - Some(false), - Some(&message), - None, - ); - emit_planner_error( - config, - planner.label(), - &config.planner_model, - "scaffold", - "phase_scaffold_error", - &format!("phase scaffold failed: {}", message), - index + 1, - ); - let handoff = save_ultra_phase_recovery_handoff( - config, - plan, - phase, - UltraPhaseRecoveryRequest { - failure_kind: "phase_scaffold_error", - reason: &message, - missing_paths: &missing_final_artifacts( - &config.workspace_root, - &final_expected_paths, - ), - missing_signals: &[], - repair_targets: &["phase_scaffold".to_string()], - verify_commands: &rejected_verify_commands, - }, - ); - anyhow::anyhow!( - "{}", - render_failure_stop_reason(format!("phase scaffold failed: {message}"), handoff,) - ) - })?; - phase_machine.plan_resolved()?; - crate::planner::fix_runtime::bind_step_plan(fix_runtime.as_mut(), phase, &mut step_plan); - emit_ultra_phase_event( - config, - "ultra_phase_scaffold_complete", - plan, - phase, - index, - "scaffold", - Some(true), - None, - Some(step_plan.steps.len()), - ); - emit_ultra_phase_event( config, - "ultra_phase_plan_validated", plan, phase, index, - "lint", - Some(true), - None, - Some(step_plan.steps.len()), - ); - save_step_plan(&config.workspace_root, &step_plan)?; - let fix_before = fix_runtime - .as_ref() - .is_some_and(|runtime| runtime.is_before_phase(index)); - let investigation_before = investigation_runtime - .as_ref() - .is_some_and(|runtime| runtime.is_reproducer_phase(index)); - phase_machine.plan_persisted(fix_before, investigation_before)?; - let Some(step_plan) = before_phase::run( - planner, + ui, + &ultra_context, + &ultra_session, + &mut phase_machine, fix_runtime.as_mut(), investigation_runtime.as_mut(), - &phase_prompt, - step_plan, - config, - plan, - phase, - index, - ui, + &final_expected_paths, preset_plan, - final_phase, )? else { - phase_machine.before_phase_completed(true, final_phase)?; continue; }; - phase_machine.before_phase_completed(false, final_phase)?; - recovery_authority::register_plan(config, &step_plan)?; let step_outcome = match run_step_plan_with_session_with_ui_and_run_authority( execution, &mut ultra_session, diff --git a/src/planner/runner/phase/issue490_runtime_tests.rs b/src/planner/runner/phase/issue490_runtime_tests.rs new file mode 100644 index 000000000..6379cbfda --- /dev/null +++ b/src/planner/runner/phase/issue490_runtime_tests.rs @@ -0,0 +1,183 @@ +use super::super::super::super::{ContractEnforcement, StepPromptContext, run_step}; +use super::*; + +#[derive(Default, Clone)] +struct RepairBoundary { + calls: Arc>, +} +impl ChatClient for RepairBoundary { + fn label(&self) -> &str { + "issue490-runtime-boundary" + } + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + fn chat( + &mut self, + _: &str, + messages: &[crate::state::ConversationMessage], + _: &[crate::tools::registry::ToolSpec], + _: bool, + ) -> anyhow::Result { + *self.calls.lock().unwrap() += 1; + let prompt = serde_json::to_string(messages)?; + assert!(prompt.contains("package.json")); + anyhow::bail!("issue490: existing execution/repair boundary") + } +} +#[test] +fn issue490_runtime_metadata_precheck_pass_fail_missing() { + let first = raw("P02-D", 1); + let proposed = raw("P02-D", 2); + let reader = &first.steps[0]; + assert_eq!(proposed.steps[0], *reader); + assert_eq!(proposed.steps[5].verify, reader.verify); + let root = tempfile::tempdir().unwrap(); + copy_tree( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join(FIXTURE) + .join("scaffold"), + root.path(), + ); + let metadata: Vec = + serde_json::from_str(&fixture("runtime-metadata.json")).unwrap(); + for (step, expected) in proposed.steps.iter().zip(metadata) { + let runtime = + resolve_profile_runtime(crate::planner::profile_descriptor::NEXTJS_PROFILE_ID); + let (effective, synthesized) = runtime.runtime_step_with_profile_checks( + root.path(), + &proposed.goal, + step, + Some("core-implementation"), + None, + ); + assert_eq!(serde_json::to_value(&effective).unwrap(), expected["step"]); + assert_eq!(synthesized, expected["synthesized"].as_bool().unwrap()); + assert_eq!( + runtime.step_short_circuit_precheck_applicable(&effective), + expected["precheck"].as_bool().unwrap() + ); + } + for condition in ["pass", "fail", "missing"] { + for step in [&first.steps[0], &proposed.steps[0], &proposed.steps[5]] { + let root = tempfile::tempdir().unwrap(); + let mut c = + Config::from_cli(crate::cli::Cli::parse_from(["commandagent", "--ux-demo"])) + .unwrap(); + c.workspace_root = root.path().into(); + c.state_dir = root.path().join("state"); + c.profile = crate::planner::profile_descriptor::NEXTJS_PROFILE_ID.into(); + c.offline = true; + c.chat_retries = 0; + c.eval_events_path = Some(root.path().join("events.jsonl")); + if condition != "missing" { + let mut package: serde_json::Value = + serde_json::from_str(&fixture("scaffold/package.json")).unwrap(); + if condition == "fail" { + package["scripts"]["dev"] = json!("next dev -p 3000"); + } + std::fs::write( + root.path().join("package.json"), + serde_json::to_vec(&package).unwrap(), + ) + .unwrap(); + } + let runtime = + resolve_profile_runtime(crate::planner::profile_descriptor::NEXTJS_PROFILE_ID); + let (effective, synthesized) = runtime.runtime_step_with_profile_checks( + root.path(), + &first.goal, + step, + Some("core-implementation"), + None, + ); + assert!(!synthesized); + assert_eq!(effective, *step); + assert!(runtime.step_short_circuit_precheck_applicable(&effective)); + let report = + crate::planner::verify::verify_step_with_profile_setup_observed_with_offline( + root.path(), + &effective, + Some(crate::planner::profile_descriptor::NEXTJS_PROFILE_ID), + crate::minimal_loop::dependency_setup::NodeDependencySetupAuthority::None, + true, + ) + .0; + assert_eq!( + report.is_pass(), + condition == "pass", + "{condition}: {report:?}" + ); + assert_eq!( + report.missing_paths, + if condition == "missing" { + vec!["package.json"] + } else { + vec![] + } + ); + if condition == "missing" { + assert_eq!(report.dependency_missing.len(), step.verify.len()); + assert!( + report + .dependency_missing + .iter() + .all(|m| m.contains("dependency_setup_authority_required")) + ); + } + if condition == "fail" { + assert_eq!(report.command_failures.len(), 1); + assert!( + report + .command_failures + .iter() + .all(|f| step.verify.contains(&f.command)) + ); + assert!(report.command_failures.iter().all(|f| !f.reason.is_empty())); + } + let plan = StepPlan { + goal: first.goal.clone(), + steps: vec![step.clone()], + }; + let mut client = RepairBoundary::default(); + let result = run_step( + &mut client, + &mut SessionSnapshot::new(), + &plan, + step, + &StepPromptContext { + overall_goal: first.goal.clone(), + ..Default::default() + }, + &c, + &NOOP_UI, + "issue490-runtime", + ContractEnforcement::Enforce, + Some("core-implementation"), + None, + ); + let log = events(&c); + if condition == "pass" { + assert_eq!( + result.unwrap().stop_reason.as_deref(), + Some("StepShortCircuited") + ); + assert_eq!(*client.calls.lock().unwrap(), 0); + let e = log + .iter() + .find(|e| e["event"] == "step_short_circuited") + .unwrap(); + assert_eq!(e["required_paths"], json!(step.expected_paths)); + assert_eq!(e["verify_commands"], json!(step.verify)); + } else { + let error = result.unwrap_err(); + assert!( + error.message.contains("existing execution/repair boundary"), + "{error:?}" + ); + assert_eq!(*client.calls.lock().unwrap(), 1); + assert!(!log.iter().any(|e| e["event"] == "step_short_circuited")); + } + } + } +} diff --git a/src/planner/runner/phase/issue490_tests.rs b/src/planner/runner/phase/issue490_tests.rs new file mode 100644 index 000000000..37ae629e6 --- /dev/null +++ b/src/planner/runner/phase/issue490_tests.rs @@ -0,0 +1,447 @@ +//! Portable replay of the frozen setup checkpoint. Hooks only observe or stop +//! after normal registration; no proposed plan or admission result is modified. +use super::*; +use crate::minimal_loop::completion::CompletionContract; +use crate::planner::recovery_contract_authority as authority; +use crate::planner::recovery_contract_authority::verifier_obligations as scope; +use crate::providers::AssistantReply; +use clap::Parser; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +const FIXTURE: &str = "tests/corpus/apps/issue490-verify-readers"; +#[derive(Default)] +struct Observation { + returned: Option, + registered: Option, + before: Option, + after: Option, + producers: Vec, +} +thread_local! { + static ACTIVE: RefCell> = const { RefCell::new(None) }; +} +struct Reset; +impl Drop for Reset { + fn drop(&mut self) { + ACTIVE.with(|s| *s.borrow_mut() = None); + } +} +fn active() -> bool { + ACTIVE.with(|s| s.borrow().is_some()) +} +fn fixture(name: &str) -> String { + std::fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join(FIXTURE) + .join(name), + ) + .unwrap() +} +fn raw(case: &str, attempt: usize) -> StepPlan { + serde_json::from_str(&fixture(&format!("{case}-attempt-{attempt}.json"))).unwrap() +} +fn events(c: &Config) -> Vec { + std::fs::read_to_string(c.eval_events_path.as_ref().unwrap()) + .unwrap_or_default() + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect() +} +pub(super) fn skip_setup(index: usize) -> bool { + active() && index == 0 +} +pub(super) fn restore( + c: &Config, + plan: &UltraPlan, + context: &mut UltraRunContext, + machine: &mut pipeline::PhaseRun, +) -> anyhow::Result<()> { + if !active() { + return Ok(()); + } + assert_eq!(plan.phases[0].id, "project-setup"); + let prior = crate::planner::step_plan::parse_step_plan(&fixture("setup-plan.yaml"))?; + recovery_authority::register_plan(c, &prior)?; + let contract: CompletionContract = serde_json::from_str(&fixture("setup-contract.json"))?; + crate::planner::runner::bind_completion_contract_for_acceptance( + c, + "plan-run", + &c.profile, + contract.goal.as_deref().unwrap(), + &contract.required_paths, + &contract.required_capabilities, + &contract.required_evidence, + &contract.required_obligations, + )?; + let outcome = crate::planner::runner::StepPlanRunOutcome { + completed_steps: 2, + total_steps: 2, + ..Default::default() + }; + context.update_after_phase(&plan.phases[0], &outcome, Vec::new()); + let paths = resolve_profile_runtime(&plan.profile) + .expected_scaffold_paths(&c.workspace_root, &plan.goal); + recovery_authority::initialize(c, plan, &paths)?; + let before = authority::load_for_handoff(c)?.unwrap(); + assert_eq!( + serde_json::to_value(&before)?, + serde_json::from_str::(&fixture("run-contract.json"))? + ); + assert!(scope::generated(c).is_empty()); + ACTIVE.with(|s| s.borrow_mut().as_mut().unwrap().before = Some(before)); + machine.phase_started()?; + machine.plan_resolved()?; + machine.plan_persisted(false, false)?; + machine.before_phase_completed(false, false)?; + machine.step_succeeded(false)?; + machine.invariant_observed(true)?; + machine.phase_committed(false, &plan.intent)?; + Ok(()) +} +pub(super) fn returned_and_saved(c: &Config, plan: &StepPlan) { + if !active() { + return; + } + let paths: Vec<_> = std::fs::read_dir(crate::runtime_paths::plans_dir(&c.workspace_root)) + .unwrap() + .map(|p| p.unwrap().path()) + .collect(); + assert_eq!(paths.len(), 1); + let text = std::fs::read_to_string(&paths[0]).unwrap(); + assert_eq!( + crate::planner::step_plan::parse_step_plan(&text).unwrap(), + *plan + ); + ACTIVE.with(|s| s.borrow_mut().as_mut().unwrap().returned = Some(plan.clone())); +} +pub(super) fn registered(c: &Config, plan: &StepPlan) -> bool { + if !active() { + return false; + } + let after = authority::load_for_handoff(c).unwrap().unwrap(); + let path = crate::planner::completion_contract_path::generated_path( + &c.workspace_root, + c.eval_events_path.as_deref(), + "completion-contract-ultra-plan-run.json", + ); + let disk: CompletionContract = serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap(); + assert_eq!(after, disk); + ACTIVE.with(|s| { + let mut s = s.borrow_mut(); + let s = s.as_mut().unwrap(); + assert_eq!( + s.returned.as_ref(), + Some(plan), + "before_phase retained returned/saved plan" + ); + s.registered = Some(plan.clone()); + s.after = Some(after); + s.producers = scope::generated(c); + }); + true +} +#[derive(Clone)] +struct Replay { + replies: Arc>>, + calls: Arc>, +} +impl ChatClient for Replay { + fn label(&self) -> &str { + "issue490-fixed-replay" + } + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + fn chat( + &mut self, + _: &str, + _: &[crate::state::ConversationMessage], + _: &[crate::tools::registry::ToolSpec], + _: bool, + ) -> anyhow::Result { + *self.calls.lock().unwrap() += 1; + let plan = self + .replies + .lock() + .unwrap() + .pop_front() + .expect("at most three attempts"); + Ok(AssistantReply::text(serde_json::to_string(&plan)?)) + } +} +struct Forbidden; +impl ChatClient for Forbidden { + fn label(&self) -> &str { + "forbidden-implementation" + } + fn boxed_clone(&self) -> Box { + Box::new(Self) + } + fn chat( + &mut self, + _: &str, + _: &[crate::state::ConversationMessage], + _: &[crate::tools::registry::ToolSpec], + _: bool, + ) -> anyhow::Result { + panic!("registration replay must stop before implementation") + } +} +fn copy_tree(source: &Path, target: &Path) { + std::fs::create_dir_all(target).unwrap(); + for entry in std::fs::read_dir(source).unwrap() { + let entry = entry.unwrap(); + let dest = target.join(entry.file_name()); + if entry.file_type().unwrap().is_dir() { + copy_tree(&entry.path(), &dest); + } else { + std::fs::copy(entry.path(), dest).unwrap(); + } + } +} +fn replay(first: StepPlan, second: StepPlan, refusal: Option<&str>) -> Observation { + let root = tempfile::tempdir().unwrap(); + copy_tree( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join(FIXTURE) + .join("scaffold"), + root.path(), + ); + for dir in ["node_modules", ".next", ".commandagent/runs/issue490"] { + std::fs::create_dir_all(root.path().join(dir)).unwrap(); + } + let mut c = + Config::from_cli(crate::cli::Cli::parse_from(["commandagent", "--ux-demo"])).unwrap(); + c.workspace_root = root.path().into(); + c.state_dir = root.path().join("state"); + c.eval_events_path = Some(root.path().join(".commandagent/runs/issue490/events.jsonl")); + c.profile = crate::planner::profile_descriptor::NEXTJS_PROFILE_ID.into(); + c.profile_explicit = true; + c.offline = true; + c.plan_preset = crate::config::PlanPreset::Profile; + c.intent_override = Some(crate::planner::adjudication::contract::IntentId::Create); + let plan = parse_ultra_plan(&fixture("ultra-plan.yaml")).unwrap(); + c.action = crate::config::Action::UltraPlanRun(plan.goal.clone()); + let mut planner = Replay { + replies: Arc::new(Mutex::new([first, second.clone(), second].into())), + calls: Arc::new(Mutex::new(0)), + }; + ACTIVE.with(|s| *s.borrow_mut() = Some(Observation::default())); + let _reset = Reset; + let result = run_ultra_plan_with_ui(&mut planner, &mut Forbidden, &plan, &c, &NOOP_UI); + let log = events(&c); + let admissions: Vec<_> = log + .iter() + .filter(|e| e["event"] == "recovery_verifier_plan_admission") + .collect(); + assert!( + admissions[0]["reason"] + .as_str() + .unwrap() + .contains("mixes application/configuration scope") + ); + assert_eq!(admissions[0]["remaining_planner_attempts"], 2); + assert_eq!(admissions[0]["recovery_budget_changed"], false); + assert!( + admissions[0]["original_obligation_sources"]["host"]["steps"][0]["instruction"] + .as_str() + .unwrap() + .contains("runnable Next.js app") + ); + let observation = ACTIVE.with(|s| s.borrow_mut().take().unwrap()); + if let Some(reason) = refusal { + assert!(result.is_err(), "{reason}: {result:?}"); + assert_eq!(*planner.calls.lock().unwrap(), 3); + assert!( + admissions.last().unwrap()["reason"] + .as_str() + .unwrap() + .contains(reason), + "{admissions:?}" + ); + assert!(observation.returned.is_none()); + assert!(observation.registered.is_none()); + assert!(scope::generated(&c).is_empty()); + let disk_path = crate::planner::completion_contract_path::generated_path( + &c.workspace_root, + c.eval_events_path.as_deref(), + "completion-contract-ultra-plan-run.json", + ); + let disk: CompletionContract = + serde_json::from_slice(&std::fs::read(disk_path).unwrap()).unwrap(); + assert_eq!(observation.before.as_ref(), Some(&disk)); + } else { + assert_eq!( + result.unwrap_err().to_string(), + "issue490: registered before execution" + ); + assert_eq!(*planner.calls.lock().unwrap(), 2); + assert!(observation.registered.is_some()); + assert_registration(&observation); + let registered = observation.registered.as_ref().unwrap(); + let host = &admissions[0]["original_obligation_sources"]["host"]["steps"][0]; + let app = registered + .steps + .iter() + .find(|s| s.id == "application-config") + .unwrap(); + assert!( + app.instruction + .contains(host["instruction"].as_str().unwrap()) + ); + assert_eq!( + app.expected_result, + host["expected_result"].as_str().unwrap() + ); + for p in host["expected_paths"].as_array().unwrap() { + assert!( + app.expected_paths + .contains(&p.as_str().unwrap().to_string()) + ); + } + let model: StepPlan = + serde_json::from_value(admissions[0]["original_obligation_sources"]["model"].clone()) + .unwrap(); + let mixed = model + .steps + .iter() + .find(|s| s.id == "mixed-verifier-doc") + .unwrap(); + for id in ["verifier-owner", "documentation-owner"] { + assert!( + registered + .steps + .iter() + .find(|s| s.id == id) + .unwrap() + .instruction + .contains(&mixed.instruction) + ); + } + } + observation +} +fn assert_registration(o: &Observation) { + let before = o.before.as_ref().unwrap(); + let after = o.after.as_ref().unwrap(); + for p in &before.required_paths { + assert!(after.required_paths.contains(p)); + } + for c in &before.verify_commands { + assert!(after.verify_commands.contains(c)); + } + for p in ["README.md", "verify-ui.cjs"] { + assert!(!before.required_paths.contains(&p.into())); + assert!(after.required_paths.contains(&p.into())); + } + let expected: serde_json::Value = serde_json::from_str(&fixture("registration.json")).unwrap(); + let added_paths: std::collections::BTreeSet<_> = after + .required_paths + .iter() + .filter(|p| !before.required_paths.contains(p)) + .cloned() + .collect(); + assert_eq!( + added_paths, + serde_json::from_value(expected["added_required_paths"].clone()).unwrap() + ); + let added_commands: Vec<_> = after + .verify_commands + .iter() + .filter(|c| !before.verify_commands.contains(c)) + .cloned() + .collect(); + assert_eq!(json!(added_commands), expected["added_verify_commands"]); + assert!(after.verify_commands.contains(&"node verify-ui.cjs".into())); + assert!(!after.verify_commands.contains(&"test -f README.md".into())); + let p = o.registered.as_ref().unwrap(); + assert!( + p.steps + .iter() + .any(|s| s.verify.contains(&"test -f README.md".into())) + ); + assert_eq!(o.producers.len(), 1); + assert_eq!(o.producers[0].id, "verifier-owner"); + assert_eq!(o.producers[0].kind, "implement"); + assert_eq!(o.producers[0].expected_result, "pass"); + assert_eq!(o.producers[0].expected_paths, ["verify-ui.cjs"]); + let app = p + .steps + .iter() + .find(|s| s.id == "application-config") + .unwrap(); + assert!( + app.instruction + .contains("Implement the UI and package configuration.") + ); + assert!(app.instruction.contains("runnable Next.js app")); + assert!(app.verify.contains(&"npm run build".into())); +} +#[test] +fn issue490_p02_d_normal_return_save_before_phase_registration() { + let first = raw("P02-D", 1); + let second = raw("P02-D", 2); + let o = replay(first.clone(), second, None); + let plan = o.registered.unwrap(); + assert_eq!(plan.steps, raw("P02-D", 2).steps); + assert_eq!(plan.steps[0], first.steps[0]); + assert_eq!(plan.steps[5].expected_paths, ["package.json"]); + assert_eq!(plan.steps[5].verify, first.steps[0].verify); +} +#[test] +fn issue490_p01_and_n01_through_n07_registration_controls() { + let first = raw("P01", 1); + let second = raw("P01", 2); + let positive = replay(first.clone(), second.clone(), None) + .registered + .unwrap(); + let cases: Vec = serde_json::from_str(&fixture("variants.json")).unwrap(); + for case in cases { + let mut next = second.clone(); + next.steps = serde_json::from_value(case["steps"].clone()).unwrap(); + let o = replay(first.clone(), next, case["refusal"].as_str()); + if case["case"] == "N06" { + assert_eq!( + o.registered.unwrap(), + positive, + "host reaugmentation is recovery, not refusal" + ); + } + } +} + +#[path = "issue490_runtime_tests.rs"] +mod runtime; + +#[test] +fn issue490_acquired_before_and_after_reach_normal_registration_separately() { + let fixture: serde_json::Value = + serde_json::from_str(&fixture("acquired-before-after.json")).unwrap(); + let original: StepPlan = serde_json::from_value(fixture["original"].clone()).unwrap(); + let proposed: StepPlan = serde_json::from_value(fixture["proposed"].clone()).unwrap(); + let o = replay(original.clone(), proposed.clone(), None); + let registered = o.registered.unwrap(); + assert_eq!(registered.steps[0], original.steps[0]); + assert_eq!(registered.steps.last(), original.steps.last()); + for which in [0, 5] { + let mut missing = proposed.clone(); + missing.steps.remove(which); + replay( + original.clone(), + missing, + Some("lost original read-only Verify duty"), + ); + } + for (from, to) in [(0, 3), (5, 3)] { + let mut moved = proposed.clone(); + let s = moved.steps.remove(from); + moved.steps.insert(to, s); + replay( + original.clone(), + moved, + Some("lost original read-only Verify duty"), + ); + } +} diff --git a/src/planner/runner/phase/phase_entry.rs b/src/planner/runner/phase/phase_entry.rs new file mode 100644 index 000000000..873562151 --- /dev/null +++ b/src/planner/runner/phase/phase_entry.rs @@ -0,0 +1,187 @@ +//! Resolve, persist and register a phase plan before executing any steps. +use super::*; + +pub(super) fn start( + config: &Config, + plan: &UltraPlan, + context: &mut UltraRunContext, +) -> anyhow::Result { + let machine = pipeline::PhaseRun::start()?; + #[cfg(test)] + let machine = { + let mut machine = machine; + issue490_tests::restore(config, plan, context, &mut machine)?; + machine + }; + let _ = (config, plan, context); + Ok(machine) +} + +#[allow(clippy::too_many_arguments)] +pub(super) fn prepare( + planner: &mut dyn ChatClient, + config: &Config, + plan: &UltraPlan, + phase: &UltraPhase, + index: usize, + ui: &dyn InteractionUi, + ultra_context: &UltraRunContext, + ultra_session: &SessionSnapshot, + phase_machine: &mut pipeline::PhaseRun, + mut fix_runtime: Option<&mut crate::planner::fix_runtime::FixRuntime>, + investigation_runtime: Option<&mut crate::planner::investigation_runtime::InvestigationRuntime>, + final_expected_paths: &[String], + preset_plan: bool, +) -> anyhow::Result> { + #[cfg(test)] + if issue490_tests::skip_setup(index) { + return Ok(None); + } + if ui.interrupted() { + phase_machine.interrupt("phase_start")?; + anyhow::bail!("interrupted by user"); + } + emit_ultra_phase_event( + config, + "ultra_phase_start", + plan, + phase, + index, + "start", + None, + None, + None, + ); + let profile_snapshot = profile_before_plan(&config.workspace_root, plan)?; + ultra_context.emit_attached(config, plan, phase, index, ultra_session); + phase_machine.phase_started()?; + let final_phase = index + 1 == plan.phases.len(); + let phase_prompt = + ultra_phase_prompt(plan, phase, config, ultra_context, fix_runtime.as_deref()); + let phase_prompt = crate::planner::pack::runtime::append_phase_material_from_environment( + phase_prompt, + &config.workspace_root, + &plan.profile, + &plan.intent, + &phase.id, + )?; + let step_plan_result = phase_machine.resolve( + planner, + &phase_prompt, + config, + ui, + phase, + plan, + fix_runtime.as_deref(), + preset_plan, + final_phase, + ); + let mut step_plan = step_plan_result.map_err(|err| { + let rejected_verify_commands = + crate::planner::lint_rejection::rejected_commands_from_error(&err); + let message = err.to_string(); + emit_ultra_phase_event( + config, + "ultra_phase_failed", + plan, + phase, + index, + "scaffold", + Some(false), + Some(&message), + None, + ); + emit_planner_error( + config, + planner.label(), + &config.planner_model, + "scaffold", + "phase_scaffold_error", + &format!("phase scaffold failed: {}", message), + index + 1, + ); + let handoff = save_ultra_phase_recovery_handoff( + config, + plan, + phase, + UltraPhaseRecoveryRequest { + failure_kind: "phase_scaffold_error", + reason: &message, + missing_paths: &missing_final_artifacts( + &config.workspace_root, + final_expected_paths, + ), + missing_signals: &[], + repair_targets: &["phase_scaffold".to_string()], + verify_commands: &rejected_verify_commands, + }, + ); + anyhow::anyhow!( + "{}", + render_failure_stop_reason(format!("phase scaffold failed: {message}"), handoff,) + ) + })?; + phase_machine.plan_resolved()?; + crate::planner::fix_runtime::bind_step_plan(fix_runtime.as_deref_mut(), phase, &mut step_plan); + emit_ultra_phase_event( + config, + "ultra_phase_scaffold_complete", + plan, + phase, + index, + "scaffold", + Some(true), + None, + Some(step_plan.steps.len()), + ); + emit_ultra_phase_event( + config, + "ultra_phase_plan_validated", + plan, + phase, + index, + "lint", + Some(true), + None, + Some(step_plan.steps.len()), + ); + save_step_plan(&config.workspace_root, &step_plan)?; + #[cfg(test)] + issue490_tests::returned_and_saved(config, &step_plan); + let fix_before = fix_runtime + .as_deref() + .is_some_and(|runtime| runtime.is_before_phase(index)); + let investigation_before = investigation_runtime + .as_deref() + .is_some_and(|runtime| runtime.is_reproducer_phase(index)); + phase_machine.plan_persisted(fix_before, investigation_before)?; + let Some(step_plan) = before_phase::run( + planner, + fix_runtime, + investigation_runtime, + &phase_prompt, + step_plan, + config, + plan, + phase, + index, + ui, + preset_plan, + final_phase, + )? + else { + phase_machine.before_phase_completed(true, final_phase)?; + return Ok(None); + }; + phase_machine.before_phase_completed(false, final_phase)?; + recovery_authority::register_plan(config, &step_plan)?; + #[cfg(test)] + if issue490_tests::registered(config, &step_plan) { + anyhow::bail!("issue490: registered before execution"); + } + Ok(Some((step_plan, profile_snapshot))) +} + +#[cfg(test)] +#[path = "issue490_tests.rs"] +mod issue490_tests; diff --git a/tests/corpus/apps/issue490-verify-readers/P01-attempt-1.json b/tests/corpus/apps/issue490-verify-readers/P01-attempt-1.json new file mode 100644 index 000000000..355677926 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/P01-attempt-1.json @@ -0,0 +1,32 @@ +{ + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "id": "mixed-verifier-doc", + "kind": "implement", + "expected_result": "pass", + "instruction": "Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs", + "README.md" + ], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + } + ] +} diff --git a/tests/corpus/apps/issue490-verify-readers/P01-attempt-2.json b/tests/corpus/apps/issue490-verify-readers/P01-attempt-2.json new file mode 100644 index 000000000..be98bce07 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/P01-attempt-2.json @@ -0,0 +1,49 @@ +{ + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] +} diff --git a/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-1.json b/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-1.json new file mode 100644 index 000000000..807df7d3e --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-1.json @@ -0,0 +1,46 @@ +{ + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-original", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + }, + { + "id": "mixed-verifier-doc", + "kind": "implement", + "expected_result": "pass", + "instruction": "Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs", + "README.md" + ], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + } + ] +} diff --git a/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-2.json b/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-2.json new file mode 100644 index 000000000..f062bd8a5 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/P02-D-attempt-2.json @@ -0,0 +1,77 @@ +{ + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-original", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + }, + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-later", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + ] +} diff --git a/tests/corpus/apps/issue490-verify-readers/README.md b/tests/corpus/apps/issue490-verify-readers/README.md new file mode 100644 index 000000000..27b6584e5 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/README.md @@ -0,0 +1,60 @@ +# Issue #490: reader obligations and output ownership + +Portable fixed-response replay based on the frozen 2026-09-17 ownership-protection +campaign at `b13ef093e462084956bb792e10616d6848e00f40`. No network, model service, +local SSD, earlier run directory or application implementation is needed. + +`P01-attempt-{1,2}.json` and `P02-D-attempt-{1,2}.json` retain the first two raw +proposals. The third response repeats the second. `variants.json` contains the +frozen N01–N07 proposal steps and the expected targeted refusal. All use P01's +first acquired proposal and goal. P02-D uses the original explicit package paths: +there is no P02-B path deletion, injection or admission bypass. + +The normal-flow tests restore only the witnessed completed project-setup +checkpoint, using the frozen scaffold, setup plan and contracts. Empty +`node_modules`/`.next` directories reproduce presence, not working dependencies. +They then use the product phase resolver, parser, host augmentation, sanitizer, +preset conversion, Admission/retries, plan save/reload, before_phase, and normal +register_plan. Test hooks observe returned/persisted/registered plans and stop +immediately after successful registration, before any implementation call. +Assertions distinguish this stop from success of setup registration or test exit. +N01–N05/N07 stop at the intended preservation failure with no core registration +or contract change. N06 recovers through host reaugmentation and is not refusal +evidence. `registration.json` fixes the additions, dedicated producer and +artifact-only README rule; both saved contract and live run scope are checked. + +`acquired-before-after.json` derives a second history in which both package +readers are acquired in attempt 1 around the application/package writer. This +is different from P02's newly added later reader. The direct Admission/finish +matrix in `reader-cases.json` covers atomic attributes, instruction/check collage, +distributed commands, ID/path-only matching, both deletions, both cross-update +moves and both one-step merges. Its positive and deletion/movement controls also +run through normal registration. IDs may change without discharging attributes. +Unknown scripts, a side-effect command, additional creation instructions and a +model's read-only claim retain the old output-ownership judgment. + +Read-only support is deliberately bounded: the complete existing profile +package-manifest inspection instruction plus nonempty commands recognized by +`nextjs::recovery_authority::is_package_check` (exact generated dev/start port +and build-script expressions). This is not a general shell/JavaScript analyzer +or provenance inferred from a name. Every required input/check remains in the +plan. Other instructions and command forms retain the existing rule. + +`runtime-metadata.json` fixes every P02-D step's metadata, synthesized-check flag +and precheck applicability. The runtime test runs the original, retained and +additional readers with passing package inputs, wrong dev port and missing +package.json. It checks real command reports and the actual runner: pass skips +the executor; wrong values fail the declared command; absence records the input +and existing dependency boundary. Both failures reach the ordinary execution/ +repair path and propagate its controlled error, never a successful short circuit. +The same test was run with original ownership wiring and with the fix. + +`known-build-loss.json` extracts the `verify-build` step at raw, sanitized and +preset-converted stages of frozen full-plan attempt 2. Its diagnostic asserts the still-existing preset conversion +loss of `npm run build` and its original instruction. This is a separate known +issue, not an improvement claimed for #490. The P01/P02 fixtures instead keep +build on the application producer after its work. + +The corpus discovery check validates fixture availability. The focused Rust +checks provide behavior evidence; neither corpus `acceptance_passed` metadata +nor fixed planner replies claim live app, GUI, build or #488 projection success. diff --git a/tests/corpus/apps/issue490-verify-readers/acquired-before-after.json b/tests/corpus/apps/issue490-verify-readers/acquired-before-after.json new file mode 100644 index 000000000..a8d4fca82 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/acquired-before-after.json @@ -0,0 +1,139 @@ +{ + "original": { + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-original", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + }, + { + "id": "mixed-verifier-doc", + "kind": "implement", + "expected_result": "pass", + "instruction": "Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs", + "README.md" + ], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-after-acquired", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + ] + }, + "proposed": { + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-original", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + }, + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-after-acquired", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + ] + } +} diff --git a/tests/corpus/apps/issue490-verify-readers/expectations.toml b/tests/corpus/apps/issue490-verify-readers/expectations.toml new file mode 100644 index 000000000..8c738bebc --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/expectations.toml @@ -0,0 +1,21 @@ +case_id = "issue490-verify-readers" +source = "Issue #490 portable fixed-response planner replay, reader duties and runtime controls; no live app success claim" +required_paths = [] +verify_commands = [] +required_capabilities = [] +required_evidence = [] +required_obligations = [] +deferred_verify_requirements = [] +evidence_hint_tokens = [] +acceptance_passed = true +primary_reason = "pass" +[compile] +expect = "not_checked" +[fixture_contains] +P02-D-attempt-1.json = ["package-read-original", "mixed-verifier-doc"] +P02-D-attempt-2.json = ["package-read-later", "verifier-owner", "package.json"] +acquired-before-after.json = ["package-read-after-acquired", "application-config"] +reader-cases.json = ["instruction_check_collage", "distributed_checks", "merge_after", "external_script"] +registration.json = ["README.md", "node verify-ui.cjs", "test -f README.md"] +runtime-metadata.json = ["precheck", "package-read-original", "package-read-later"] +known-build-loss.json = ["verify-build", "npm run build", "unresolved by #490"] diff --git a/tests/corpus/apps/issue490-verify-readers/known-build-loss.json b/tests/corpus/apps/issue490-verify-readers/known-build-loss.json new file mode 100644 index 000000000..bbcd47396 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/known-build-loss.json @@ -0,0 +1,54 @@ +{ + "source": "Frozen full-plan replay T/1 attempt 2, verify-build only; unresolved by #490.", + "parsed_model": { + "goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence", + "steps": [ + { + "expected_paths": [], + "expected_result": "pass", + "id": "verify-build", + "instruction": "Run the Next.js production build to confirm the entire app compiles without type errors, all route modules resolve, and the output is valid.", + "kind": "verify", + "verify": [ + "npm run build" + ] + } + ] + }, + "sanitized": { + "goal": "Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。", + "steps": [ + { + "expected_paths": [], + "expected_result": "pass", + "id": "verify-build", + "instruction": "Run the Next.js production build to confirm the entire app compiles without type errors, all route modules resolve, and the output is valid.", + "kind": "verify", + "verify": [ + "npm run build", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + ] + }, + "preset_converted": { + "goal": "Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。", + "steps": [ + { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "verify-build", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + ] + }, + "conversion_goal": "Original ultra goal: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。\nProfile: nextjs\nStyle: default\nIntent: create\nPhase id: core-implementation\nPhase task: Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n\nWorkspace snapshot:\n- .next\n- next-env.d.ts\n- node_modules\n- package-lock.json\n- package.json\n- postcss.config.js\n- src\n- tailwind.config.ts\n- tsconfig.json\n\nPrior ultra context:\n- Completed phases:\n - project-setup (2/2)\n\nUnmet final requirements from earlier phases:\n- none\n\nRequested features not yet detected:\n- accepting\n- affordance\n- alone\n- another\n- anvil\n- api\n- … and 112 more\n\nProfile generation rules:\n- Profile nextjs/create: preserve a real Next.js app contract. Include next/react/react-dom dependencies, keep scripts.build as next build, and end with a build verification phase. Put dependency setup before any npm run build verification when node_modules is not already present; setup instructions may install dependencies, but verify must not contain npm install. If dependency setup is not allowed or cannot run, stop with dependency_missing instead of claiming build success. Keep a single route-bound implementation; do not leave capability components unimported. For interactive UI, implement the requested interface, replacing unrelated scaffold content, and preserve data-anvil-* attributes on route-bound UI: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). If you use Tailwind utility classes or @tailwind directives, include tailwindcss/postcss/autoprefixer and create tailwind.config.* plus postcss.config.*; postcss.config plugins must include BOTH tailwindcss and autoprefixer. Otherwise use plain CSS and do not write Tailwind utility classes. Keep scripts.dev and scripts.start on the explicit requested port when the goal or plan requests one; otherwise use port 3011 with next dev/start -p 3011 or --port 3011.\n- For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result.\n- For server-side file persistence, initialize only a missing file (ENOENT); distinguish valid empty data from missing data. Parse errors, directory replacement and other read or write I/O failures must return 5xx without overwriting any existing or related data. Serialize the complete read-modify-write transaction across all writers (including multiple processes), or use generation checks with conflict handling; write to a temporary file and atomically rename it. Atomic rename alone does not prevent lost updates.\n- Validate the complete proposed state before committing: aggregate quantities by entity key before checking availability, scope totals to the requested period, define rejection/cascade/reassignment for referenced parent deletion, and match strict/inclusive comparisons to the goal wording. Reject non-finite or invalid quantities and amounts at the API boundary.\n\nProfile runtime contract:\n- Preserve the workspace as a real Next.js app.\n- Keep next/react/react-dom dependencies in package.json.\n- Keep scripts.build as next build; do not replace it with echo/skip/no-op commands.\n- If npm run build cannot run because dependencies are not installed, report dependency_missing or use an explicit setup step; do not fake success.\n- Keep scripts.dev and scripts.start on the explicitly requested port 60302: next dev/start -p 60302 or --port 60302.\n- If using Tailwind utility classes or @tailwind directives, keep the Tailwind toolchain complete: tailwindcss/postcss/autoprefixer dependencies, tailwind.config.*, and postcss.config plugins with BOTH tailwindcss and autoprefixer. Otherwise use plain CSS.\n- Keep TypeScript and app router configuration coherent.\n- Keep a single route-bound implementation; do not leave capability components unimported.\n- For interactive UI, expose data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state after each render. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial).\n- Do not treat scaffold-only, package-only, or build-only output as complete.\nRoute-bound implementation constraint:\n- Keep a single route-bound implementation; do not leave capability components unimported.\n\nPre-provisioned scaffold note:\n- Required scaffold files are authored before phase 1 when absent; verify or extend the scaffold rather than re-planning file creation.\nDeterministic verification preference:\n- npm run build\n\n\nRequired final artifacts:\n- package.json\n- tsconfig.json\n- postcss.config.js\n- tailwind.config.ts\n- src/app/layout.tsx\n- src/app/page.tsx\n- src/app/globals.css\n- src/app/global.d.ts\n\nRequired final capabilities:\n- stateful_interaction\n- user_input_or_action\n- visible_state_change\n- persistence\n\nRequired final evidence:\n- nextjs_route_evidence\n- build_command_or_dependency_missing_boundary\n- implementation_artifact\n- visible_interactive_surface_evidence\n- user_input_handler_evidence\n- stateful_update_evidence\n- interactive_ui_source_evidence\n- non_static_screen_evidence\n- persistence_evidence" +} diff --git a/tests/corpus/apps/issue490-verify-readers/provenance.json b/tests/corpus/apps/issue490-verify-readers/provenance.json new file mode 100644 index 000000000..b1f7c7d72 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/provenance.json @@ -0,0 +1,18 @@ +{ + "source_commit": "b13ef093e462084956bb792e10616d6848e00f40", + "source_campaign": "ownership-protection-20260917-01; known-build-loss from ownership-fullplan-20260917-01/reply-2", + "representation": "JSON formatting normalized; first two replies retained; repeated third is synthesized by replay; N variants store differing steps; no admission/path interventions", + "sha256": { + "P01-attempt-1.json": "7aaeb78f31421b90d435c0ad663fbc55a93b13a1ee50d9cc00004c119e8ac6e1", + "P01-attempt-2.json": "5cd7632c484e1e77ea3f01b830482ea125bafac3c22b08fc000cca1ef66508e2", + "P02-D-attempt-1.json": "46c2368fad2d3c967535c7a9ddbbe9db8f414d305464e8acfeae846a4c150106", + "P02-D-attempt-2.json": "611a25c828a8af84aa130e65ceda220f31cff1b2bb5375c8c6d1772d5306b2c9", + "variants.json": "fcff49223656eb9c2340db215110708b47417bd201291bcc40f562e1caea092f", + "setup-plan.yaml": "b56a9103b688e55ea6c35fc75420890cd768a645b89a1aa483b4b129cc0e13a3", + "setup-contract.json": "ec4a266d4b30d18fc6e4340606a7b2b9bc4cda3f2bdfa36e6b2d3a5e76b07154", + "run-contract.json": "eb2b04647a28ab10508c21e9c1cd026bea293a1c7f95dda261f4bd5529d8c9ab", + "ultra-plan.yaml": "daf9b856281c18ed9081c854ea97280defb81a0dae41e9a591f36eb39795c369", + "acquired-before-after.json": "8cd8893a0ca7205e37ef833d6e086e37753cbe1edb62242a1852724dd910d38e", + "known-build-loss.json": "e15f1024da83e208592cb9375fd76fad78a6b10041e4320b6f1d456960a6891e" + } +} diff --git a/tests/corpus/apps/issue490-verify-readers/reader-cases.json b/tests/corpus/apps/issue490-verify-readers/reader-cases.json new file mode 100644 index 000000000..a3feebf1c --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/reader-cases.json @@ -0,0 +1,25 @@ +{ + "atomic": [ + "instruction", + "expected_result", + "checks", + "input", + "instruction_check_collage", + "distributed_checks", + "id_path_only" + ], + "boundary": [ + "delete_before", + "delete_after", + "move_before_across_update", + "move_after_across_update", + "merge_before", + "merge_after" + ], + "unsupported": [ + "creation", + "side_effect", + "external_script", + "self_declared_reader" + ] +} diff --git a/tests/corpus/apps/issue490-verify-readers/registration.json b/tests/corpus/apps/issue490-verify-readers/registration.json new file mode 100644 index 000000000..f3bf3f2a2 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/registration.json @@ -0,0 +1,18 @@ +{ + "added_required_paths": [ + "README.md", + "verify-ui.cjs" + ], + "added_verify_commands": [ + "node verify-ui.cjs" + ], + "artifact_only": "test -f README.md", + "producer": { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "expected_paths": [ + "verify-ui.cjs" + ] + } +} diff --git a/tests/corpus/apps/issue490-verify-readers/run-contract.json b/tests/corpus/apps/issue490-verify-readers/run-contract.json new file mode 100644 index 000000000..e0b2edd87 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/run-contract.json @@ -0,0 +1,45 @@ +{ + "required_paths": [ + "package.json", + "tsconfig.json", + "postcss.config.js", + "tailwind.config.ts", + "src/app/layout.tsx", + "src/app/page.tsx", + "src/app/globals.css", + "src/app/global.d.ts" + ], + "protected_paths": [], + "verify_commands": [ + "npm run build" + ], + "profile": "nextjs", + "goal": "小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。", + "required_capabilities": [ + "stateful_interaction", + "user_input_or_action", + "visible_state_change" + ], + "deterministic_oracles": [], + "required_evidence": [ + "nextjs_route_evidence", + "build_command_or_dependency_missing_boundary", + "implementation_artifact", + "visible_interactive_surface_evidence", + "user_input_handler_evidence", + "stateful_update_evidence", + "interactive_ui_source_evidence", + "non_static_screen_evidence" + ], + "evidence_hint_tokens": [ + "プロジェクト", + "ユーザー", + "タスク", + "チームメンバー" + ], + "required_obligations": [ + "implementation" + ], + "deferred_verify_requirements": [], + "verify_repair_cap": 2 +} diff --git a/tests/corpus/apps/issue490-verify-readers/runtime-metadata.json b/tests/corpus/apps/issue490-verify-readers/runtime-metadata.json new file mode 100644 index 000000000..b2b959bae --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/runtime-metadata.json @@ -0,0 +1,104 @@ +[ + { + "id": "package-read-original", + "synthesized": false, + "precheck": true, + "step": { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-original", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + }, + { + "id": "verifier-owner", + "synthesized": false, + "precheck": false, + "step": { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + } + }, + { + "id": "documentation-owner", + "synthesized": false, + "precheck": false, + "step": { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + } + }, + { + "id": "application-config", + "synthesized": false, + "precheck": false, + "step": { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + } + }, + { + "id": "read-verifier-doc", + "synthesized": false, + "precheck": false, + "step": { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + }, + { + "id": "package-read-later", + "synthesized": false, + "precheck": true, + "step": { + "expected_paths": [ + "package.json" + ], + "expected_result": "pass", + "id": "package-read-later", + "instruction": "Verify the profile-owned package_manifest contract by running every declared check and report any exact failure.", + "kind": "verify", + "verify": [ + "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"", + "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"", + "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + ] + } + } +] diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/next-env.d.ts b/tests/corpus/apps/issue490-verify-readers/scaffold/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/package-lock.json b/tests/corpus/apps/issue490-verify-readers/scaffold/package-lock.json new file mode 100644 index 000000000..7d3609998 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/package-lock.json @@ -0,0 +1,1621 @@ +{ + "name": "commandagent-nextjs-app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "commandagent-nextjs-app", + "version": "1.0.0", + "dependencies": { + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^20.14.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.15", + "tailwindcss": "^3.4.19", + "typescript": "^5.5.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz", + "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.9", + "caniuse-lite": "^1.0.30001810", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz", + "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.430", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz", + "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.35", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/package.json b/tests/corpus/apps/issue490-verify-readers/scaffold/package.json new file mode 100644 index 000000000..9ffcb53f9 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/package.json @@ -0,0 +1,24 @@ +{ + "dependencies": { + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^20.14.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.15", + "tailwindcss": "^3.4.19", + "typescript": "^5.5.0" + }, + "name": "commandagent-nextjs-app", + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev -p 60302", + "start": "next start -p 60302" + }, + "version": "1.0.0" +} diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/postcss.config.js b/tests/corpus/apps/issue490-verify-readers/scaffold/postcss.config.js new file mode 100644 index 000000000..cce4985fb --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/postcss.config.js @@ -0,0 +1 @@ +module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } }; diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/global.d.ts b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/global.d.ts new file mode 100644 index 000000000..cbe652dbe --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/global.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/globals.css b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/globals.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/layout.tsx b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/layout.tsx new file mode 100644 index 000000000..35dee2cef --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/layout.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Interactive Challenge", + description: "A compact interactive challenge generated by commandagent", +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/page.tsx b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/page.tsx new file mode 100644 index 000000000..f1de1fbe4 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/src/app/page.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +type Hazard = { id: number; x: number; y: number; alive: boolean }; +type Pulse = { x: number; y: number }; + +const columns = 9; +const rows = 4; + +function initialHazards(): Hazard[] { + return Array.from({ length: columns * rows }, (_, id) => ({ + id, + x: 8 + (id % columns) * 10, + y: 12 + Math.floor(id / columns) * 8, + alive: true, + })); +} + +export default function Page() { + const [player, setPlayer] = useState(50); + const [pulses, setPulses] = useState([]); + const [hazards, setHazards] = useState(() => initialHazards()); + const [tick, setTick] = useState(0); + const [running, setRunning] = useState(true); + const [lives, setLives] = useState(3); + + useEffect(() => { + const onKey = (event: KeyboardEvent) => { + if (event.key === "ArrowLeft") setPlayer((value) => Math.max(5, value - 4)); + if (event.key === "ArrowRight") setPlayer((value) => Math.min(95, value + 4)); + if (event.key === " ") setPulses((value) => [...value, { x: player, y: 86 }].slice(-6)); + if (event.key.toLowerCase() === "r") { + setHazards(initialHazards()); + setPulses([]); + setRunning(true); + setLives(3); + } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [player]); + + useEffect(() => { + if (!running) return; + const timer = window.setInterval(() => { + setTick((value) => value + 1); + setPulses((value) => value.map((pulse) => ({ ...pulse, y: pulse.y - 5 })).filter((pulse) => pulse.y > 4)); + setHazards((value) => + value.map((hazard) => ({ + ...hazard, + x: hazard.x + Math.sin((tick + hazard.id) / 8) * 0.45, + y: hazard.y + 0.035, + })), + ); + }, 70); + return () => window.clearInterval(timer); + }, [running, tick]); + + useEffect(() => { + setHazards((current) => + current.map((hazard) => { + if (!hazard.alive) return hazard; + const hit = pulses.some((pulse) => Math.abs(pulse.x - hazard.x) < 3.2 && Math.abs(pulse.y - hazard.y) < 3.8); + return hit ? { ...hazard, alive: false } : hazard; + }), + ); + }, [pulses]); + + const alive = hazards.filter((hazard) => hazard.alive).length; + const score = useMemo(() => (columns * rows - alive) * 100, [alive]); + + useEffect(() => { + const breach = hazards.some((hazard) => hazard.alive && hazard.y > 78); + if (breach) setLives((value) => Math.max(0, value - 1)); + if (alive === 0 || breach || lives === 0) { + setRunning(false); + } + }, [alive, hazards, lives]); + + return ( +
+
+ INTERACTIVE CHALLENGE + SCORE {score} + LIVES {lives} + {running ? "LIVE" : alive === 0 ? "CLEAR" : "RESET READY"} +
+
+
+ {hazards.map((hazard) => + hazard.alive ? ( +
+ ) : null, + )} + {pulses.map((pulse, index) => ( +
+ ))} +
+
+ + +
+ ); +} diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/tailwind.config.ts b/tests/corpus/apps/issue490-verify-readers/scaffold/tailwind.config.ts new file mode 100644 index 000000000..dff7a46ca --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/tailwind.config.ts @@ -0,0 +1,13 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { extend: {} }, + plugins: [], +}; + +export default config; diff --git a/tests/corpus/apps/issue490-verify-readers/scaffold/tsconfig.json b/tests/corpus/apps/issue490-verify-readers/scaffold/tsconfig.json new file mode 100644 index 000000000..2be6b911c --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/scaffold/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2017","lib":["dom","dom.iterable","esnext"],"allowJs":true,"skipLibCheck":true,"strict":true,"noEmit":true,"esModuleInterop":true,"module":"esnext","moduleResolution":"bundler","resolveJsonModule":true,"isolatedModules":true,"jsx":"preserve","incremental":true,"plugins":[{"name":"next"}],"baseUrl":".","paths":{"@/*":["./src/*"]}},"include":["next-env.d.ts","src/**/*","app/**/*","pages/**/*","components/**/*","*.ts","*.tsx",".next/types/**/*.ts"],"exclude":["node_modules"]} \ No newline at end of file diff --git a/tests/corpus/apps/issue490-verify-readers/setup-contract.json b/tests/corpus/apps/issue490-verify-readers/setup-contract.json new file mode 100644 index 000000000..2a4195726 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/setup-contract.json @@ -0,0 +1,29 @@ +{ + "required_paths": [ + "package.json", + "tsconfig.json", + "postcss.config.js", + "tailwind.config.ts", + "src/app/layout.tsx", + "src/app/page.tsx", + "src/app/globals.css", + "src/app/global.d.ts" + ], + "protected_paths": [], + "verify_commands": [], + "profile": null, + "goal": "Create the deterministic Next.js scaffold.", + "required_capabilities": [], + "deterministic_oracles": [], + "required_evidence": [ + "nextjs_route_evidence", + "build_command_or_dependency_missing_boundary" + ], + "evidence_hint_tokens": [ + "deterministic", + "scaffold" + ], + "required_obligations": [], + "deferred_verify_requirements": [], + "verify_repair_cap": 2 +} diff --git a/tests/corpus/apps/issue490-verify-readers/setup-plan.yaml b/tests/corpus/apps/issue490-verify-readers/setup-plan.yaml new file mode 100644 index 000000000..7a45d6dc8 --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/setup-plan.yaml @@ -0,0 +1,33 @@ +# CommandAgent editable plan YAML. Comments are ignored. +# Edit values, add or remove list items, then validate before execution. +# Validation never executes the plan: commandagent --validate-plan +# goal: overall outcome; steps run in order. +# kind: inspect, setup, implement, verify, or report. +# expected_result: pass or fail; paths and verify commands are optional lists. +# Run only after validation: commandagent --run-plan +goal: "Create the deterministic Next.js scaffold." +steps: + - id: "nextjs-scaffold" + kind: "setup" + expected_result: "pass" + instruction: "Create or complete the Next.js App Router scaffold, package manifest, mode-appropriate language and styling config, and route-bound page. Keep package.json dev/start scripts on port 60302. Required files: package.json, tsconfig.json, postcss.config.js, tailwind.config.ts, src/app/layout.tsx, src/app/page.tsx, src/app/globals.css, src/app/global.d.ts.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial)." + expected_paths: + - "package.json" + - "tsconfig.json" + - "postcss.config.js" + - "tailwind.config.ts" + - "src/app/layout.tsx" + - "src/app/page.tsx" + - "src/app/globals.css" + - "src/app/global.d.ts" + verify: + - id: "nextjs-profile-verify" + kind: "verify" + expected_result: "pass" + instruction: "Verify package scripts use the requested port and the Next.js build command remains executable." + expected_paths: + verify: + - "node -p \"['dev'].every(function(k){return String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false}) ? true : process.exit(1)\"" + - "node -p \"['start'].every(function(k){return Object(require('./package.json').scripts)[k] ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='next' ? a.slice(i+1).find(function(x){return x})==k : false}) ? String(Object(require('./package.json').scripts)[k]).split(' ').some(function(t,i,a){return t=='--port=60302' ? true : t=='-p' ? a.slice(i+1).find(function(x){return x})=='60302' : t=='-p60302' ? true : t=='--port' ? a.slice(i+1).find(function(x){return x})=='60302' : false}) : false : true}) ? true : process.exit(1)\"" + - "node -p \"String(require('./package.json').scripts.build)=='next build' ? true : process.exit(1)\"" + - "npm run build" diff --git a/tests/corpus/apps/issue490-verify-readers/ultra-plan.yaml b/tests/corpus/apps/issue490-verify-readers/ultra-plan.yaml new file mode 100644 index 000000000..2c7d4501a --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/ultra-plan.yaml @@ -0,0 +1,19 @@ +# CommandAgent editable plan YAML. Comments are ignored. +# Edit values, add or remove list items, then validate before execution. +# Validation never executes the plan: commandagent --validate-plan +# profile/style/intent preserve the generated execution context. +# phases run in order; each id must be unique and each prompt a focused task. +# Run only after validation: commandagent --run-ultra-plan +goal: "小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。" +profile: "nextjs" +style: "default" +intent: "create" +phases: + - id: "project-setup" + prompt: "Scaffold and setup the Next.js App Router project shell. Create or complete the package manifest, TypeScript config, styling config, and route-bound scaffold so the deterministic nextjs-scaffold template owns setup artifacts." + - id: "core-implementation" + prompt: "Implement the core task-specific behavior for: 小さな代理店向けのプロジェクト管理アプリを構築してください。ユーザーはプロジェクトの作成、期限付きタスクの追加、チームメンバーへの割り当てができ、進捗状況(未着手・進行中・完了)でタスクを絞り込めるようにします。60302ポートで起動可能なNext.jsアプリとして開発してください。. Keep one route-bound implementation, implement the requested interface and replace unrelated scaffold content, and keep the implementation in the Next.js route-bound source. For TypeScript apps, put shared domain and request/response types in src/lib/types.ts and import the same types from page and route modules. Use imported helpers and export every function consumed by another module. Decide one JSON response shape per operation (bare array, {items}, or {item}) and make the UI read exactly what the route returns. Check Response.ok before accepting a mutation; return errors as {error, details?} and display them in the UI. Connect each requested UI action through its API to persistence and reload the committed result." + - id: "contract-wiring" + prompt: "Wire controls and data-anvil observability. Preserve or add data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state with a JSON snapshot of meaningful visible state. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification." + - id: "build-verification" + prompt: "Run build verification for the deterministic Next.js scaffold. Verify package scripts, dependency boundary, and npm run build / next build only; keep this final phase verification-only." diff --git a/tests/corpus/apps/issue490-verify-readers/variants.json b/tests/corpus/apps/issue490-verify-readers/variants.json new file mode 100644 index 000000000..4a104838f --- /dev/null +++ b/tests/corpus/apps/issue490-verify-readers/variants.json @@ -0,0 +1,349 @@ +[ + { + "case": "N01", + "refusal": "formation lost original check", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "test -f README.md" + ] + } + ] + }, + { + "case": "N02", + "refusal": "formation dropped original required output owner verify-ui.cjs", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] + }, + { + "case": "N03", + "refusal": "lost original requirements/expected result", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "fail", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] + }, + { + "case": "N04", + "refusal": "complete scope and boundary", + "steps": [ + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + }, + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + } + ] + }, + { + "case": "N05", + "refusal": "lost original requirements/expected result", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. ", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] + }, + { + "case": "N06", + "refusal": null, + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] + }, + { + "case": "N07", + "refusal": "lost original requirements/expected result", + "steps": [ + { + "id": "verifier-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Create the verifier. Produce verify-ui.cjs to check the task UI and document its checks in README.md.", + "expected_paths": [ + "verify-ui.cjs" + ], + "verify": [] + }, + { + "id": "documentation-owner", + "kind": "implement", + "expected_result": "pass", + "instruction": "Document the checks. ", + "expected_paths": [ + "README.md" + ], + "verify": [] + }, + { + "id": "application-config", + "kind": "implement", + "expected_result": "pass", + "instruction": "Implement the UI and package configuration.\n\nProfile contract:\nFor the nextjs profile, create a runnable Next.js app, not only package metadata. Keep the project in the workspace root unless a project subdirectory already exists. Preserve the language and styling toolchains declared by an existing project. A JavaScript project using plain CSS requires package.json, an App Router page and layout, and the imported stylesheet; do not add TypeScript, @types, Tailwind, PostCSS, or Autoprefixer only to satisfy a template. A TypeScript project additionally requires a coherent tsconfig and type dependencies. If those mode-appropriate files are absent, write the coherent App Router scaffold before further inspection. package.json must include compatible next, react, and react-dom dependencies plus scripts.build = `next build`. If Tailwind is used, package.json must include tailwindcss/postcss/autoprefixer and postcss.config plugins must include BOTH tailwindcss and autoprefixer. For TypeScript/TSX apps, create tsconfig.json before treating the app as complete. Keep a single route-bound implementation; do not leave capability components unimported. Do not use deprecated moduleResolution=node10 or target=ES5; prefer moduleResolution=bundler and target=ES2017 or newer. For interactive UI, expose task-agnostic observability hooks: data-anvil-action=\"primary\" on the main start/submit/action control, data-anvil-action=\"input\" on the main text entry surface when one exists, and data-anvil-state containing JSON for meaningful visible state after each render. The data-anvil-state snapshot must include at least one dimension that immediately responds to input, such as the current selection or edited value. When the contract includes start_or_restart_flow, every restart affordance (including terminal and active states when present) should carry data-anvil-action=\"restart\"; the initial primary action alone cannot satisfy recovery verification. A restart reachable during active use (hook or keyboard shortcut) allows behavioral verification, while an overlay-only restart may verify as unverified:terminal_state_not_reached (partial). The dev/start scripts must run on the explicitly requested port 60302: `next dev -p 60302` and `next start -p 60302` or equivalent `--port 60302` forms.", + "expected_paths": [ + "src/app/page.tsx", + "package.json" + ], + "verify": [ + "npm run build" + ] + }, + { + "id": "read-verifier-doc", + "kind": "verify", + "expected_result": "pass", + "instruction": "Run both original checks after the UI and verification files exist.", + "expected_paths": [], + "verify": [ + "node verify-ui.cjs", + "test -f README.md" + ] + } + ] + } +]