diff --git a/code/cli/CHANGELOG.md b/code/cli/CHANGELOG.md index 21cf7439..de142fa0 100644 --- a/code/cli/CHANGELOG.md +++ b/code/cli/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format loosely follows [Keep a Changelog](https://keepachangelog.com/) and the project adheres to [Semantic Versioning](https://semver.org/). +## [0.21.1] +### Fixed +- **The analysis/plan boundary commits blocked every cycle.** Since 0.20.0 git-ignored the whole `cleanrooms/` area, the `commit_analysis_boundary` (ANALYZE→PLAN) and `commit_plan_boundary` (→EXECUTE) policies could never run — `git add -- cleanrooms//…` refuses ignored paths, so the transition failed closed with `ERROR_BOUNDARY_COMMIT_FAILED`. The two decisions contradicted each other: a working area that is intentionally ephemeral has no boundary snapshot to commit. Both transitions are now `commit_policy: none`; their gates still validate `diagnosis.md`/`plan.md`. The gap survived because the FSM tests ran in a temp repo that did **not** git-ignore `cleanrooms/`, so the boundary commit succeeded there while failing in every real repo (where `iq init` ignores it) — the tests now assert no commit is made. Found by dogfooding a real cycle (cacsi-dev/impulsa #40). + ## [0.21.0] ### Added - **`iq implementation start --issue ` — one explicit command opens a cycle.** It is the whole mechanical bootstrap that used to be handed to the model step by step in `inquiry-start.md` (derive the slug, `git checkout -b`, `mkdir` the cleanroom, hand-write `index.md`, then transition): now a single deterministic command. It resolves the project root, **initializes the workspace itself if `.inquiry/` is missing** (no separate `iq init`), reads the issue title from GitHub, derives the `-` branch and checks it out, and fires `start_analyze` — whose existing effect scaffolds `cleanrooms//analyze/`. The result: `main` → one command → linked branch + cleanroom + ANALYZE. This is the first module built to the **module-per-stage** direction (`iq `, every argument explicit and named) recorded in `docs/roadmap.md`. diff --git a/code/cli/assets/fsm/transition_contract.yaml b/code/cli/assets/fsm/transition_contract.yaml index c63eda7d..1408117c 100644 --- a/code/cli/assets/fsm/transition_contract.yaml +++ b/code/cli/assets/fsm/transition_contract.yaml @@ -113,7 +113,10 @@ transitions: prechecks: [issue_selected_or_created, diagnosis_exists, diagnosis_structured, diagnosis_evidence_present, diagnosis_evidence_verifiable, index_exists, confirmations_exists] effects: [generate_plan] artifacts: [plan.md] - commit_policy: commit_analysis_boundary + # cleanrooms/ is intentionally git-ignored (ephemeral working area; durable + # artifacts are the issue, code, and tests). A boundary commit of it can + # never succeed and blocks the transition — so there is nothing to commit. + commit_policy: none prompt_fragment_id: analyze_to_plan - from: ANALYZE @@ -188,7 +191,9 @@ transitions: prechecks: [issue_selected, feature_branch_selected, plan_approved, plan_executable_checks] effects: [prepare_execute] artifacts: [execution_log.md] - commit_policy: commit_plan_boundary + # See complete_analysis: cleanrooms/ is git-ignored, so a plan boundary + # commit can never succeed. The plan gate still validates plan.md. + commit_policy: none prompt_fragment_id: plan_to_execute - from: PLAN diff --git a/code/cli/lib/src/version.dart b/code/cli/lib/src/version.dart index 2f38cc04..39374490 100644 --- a/code/cli/lib/src/version.dart +++ b/code/cli/lib/src/version.dart @@ -2,4 +2,4 @@ /// /// Update this constant when bumping version in pubspec.yaml. /// Both are kept in sync manually to avoid build-time complexity. -const String inquiryVersion = '0.21.0'; +const String inquiryVersion = '0.21.1'; diff --git a/code/cli/pubspec.yaml b/code/cli/pubspec.yaml index 429b4272..a5a2f523 100644 --- a/code/cli/pubspec.yaml +++ b/code/cli/pubspec.yaml @@ -1,7 +1,7 @@ name: inquiry_cli description: > CLI for Inquiry — structured development through the APE methodology. -version: 0.21.0 +version: 0.21.1 environment: sdk: ^3.8.1 diff --git a/code/cli/test/fsm_contract_test.dart b/code/cli/test/fsm_contract_test.dart index 084f87fe..cfcd6bad 100644 --- a/code/cli/test/fsm_contract_test.dart +++ b/code/cli/test/fsm_contract_test.dart @@ -132,20 +132,15 @@ void main() { analyzeTransition.operations!.prechecks, containsAll(['diagnosis_exists', 'index_exists', 'confirmations_exists']), ); - expect( - analyzeTransition.operations!.commitPolicy, - 'commit_analysis_boundary', - ); + // cleanrooms/ is git-ignored, so no boundary commit is attempted. + expect(analyzeTransition.operations!.commitPolicy, 'none'); expect(analyzeTransition.operations!.promptFragmentId, 'analyze_to_plan'); expect(planTransition.allowed, isTrue); expect(planTransition.to, FsmState.execute); expect(planTransition.operations, isNotNull); expect(planTransition.operations!.prechecks, contains('plan_approved')); - expect( - planTransition.operations!.commitPolicy, - 'commit_plan_boundary', - ); + expect(planTransition.operations!.commitPolicy, 'none'); expect(planTransition.operations!.promptFragmentId, 'plan_to_execute'); }); diff --git a/code/cli/test/fsm_transition_integration_test.dart b/code/cli/test/fsm_transition_integration_test.dart index f53e4343..120d8198 100644 --- a/code/cli/test/fsm_transition_integration_test.dart +++ b/code/cli/test/fsm_transition_integration_test.dart @@ -161,13 +161,16 @@ void main() { expect(t1.promptFragmentId, isNotNull); current = t1.nextState!; + // cleanrooms/ is ephemeral and git-ignored: the analysis and plan + // boundaries no longer create commits (a boundary commit would fail on a + // real repo, where cleanrooms/ is ignored). final analysisCommitsBefore = _commitCount(tempDir.path); _writeDiagnosis(tempDir.path, branch, 'diagnosis ready'); final t2 = await transition('complete_analysis'); expect(t2.allowed, isTrue); expect(t2.nextState, 'PLAN'); expect(t2.promptFragmentId, isNotNull); - expect(_commitCount(tempDir.path), analysisCommitsBefore + 1); + expect(_commitCount(tempDir.path), analysisCommitsBefore); current = t2.nextState!; final planCommitsBefore = _commitCount(tempDir.path); @@ -176,7 +179,7 @@ void main() { expect(t3.allowed, isTrue); expect(t3.nextState, 'EXECUTE'); expect(t3.promptFragmentId, isNotNull); - expect(_commitCount(tempDir.path), planCommitsBefore + 1); + expect(_commitCount(tempDir.path), planCommitsBefore); current = t3.nextState!; final t4 = await transition('finish_execute'); diff --git a/code/cli/test/fsm_transition_test.dart b/code/cli/test/fsm_transition_test.dart index 2ce79a74..d9b42abc 100644 --- a/code/cli/test/fsm_transition_test.dart +++ b/code/cli/test/fsm_transition_test.dart @@ -42,7 +42,7 @@ void main() { }); test( - 'returns prompt descriptor for ANALYZE -> PLAN after boundary commit', + 'returns prompt descriptor for ANALYZE -> PLAN without a boundary commit', () async { const branch = '51-idle-execution-guardrails'; _initGitRepo(tempDir.path, branch: branch); @@ -76,7 +76,9 @@ void main() { output.toText(), contains('Write inside the CLI-created template and keep frontmatter unchanged.'), ); - expect(_commitCount(tempDir.path), commitsBefore + 1); + // cleanrooms/ is ephemeral and git-ignored: the transition must NOT + // create a commit (a boundary commit would fail on a real repo). + expect(_commitCount(tempDir.path), commitsBefore); // Verify state was actually updated final stateContent = File( @@ -91,18 +93,9 @@ void main() { expect(runTrace.existsSync(), isTrue); final traceContent = runTrace.readAsStringSync(); expect(traceContent, contains('event_class: sensor_run')); - expect(traceContent, contains('event_class: tool_activity')); - expect(traceContent, contains('tool_class: git')); - expect(traceContent, contains('command_family: add')); - expect(traceContent, contains('command_family: commit')); - expect(traceContent, contains('gate: commit_analysis_boundary')); - expect(traceContent, contains('verdict: APPROVED')); - expect( - traceContent, - contains( - 'authority: "cleanrooms/51-idle-execution-guardrails/analyze"', - ), - ); + // No boundary-commit machinery runs anymore. + expect(traceContent, isNot(contains('gate: commit_analysis_boundary'))); + expect(traceContent, isNot(contains('command_family: commit'))); }, ); @@ -372,67 +365,6 @@ void main() { ); }); - test( - 'fails closed when ANALYZE -> PLAN cannot create boundary commit', - () async { - const branch = '51-idle-execution-guardrails'; - - _initGitRepo(tempDir.path, branch: branch); - _writeAnalyzeIndex(tempDir.path, branch); - _writeConfirmations(tempDir.path, branch); - _writeDiagnosis(tempDir.path, branch, 'diagnosis already committed'); - _git(tempDir.path, [ - 'add', - '--', - p.posix.join('cleanrooms', branch, 'analyze'), - ]); - _git(tempDir.path, [ - 'commit', - '-m', - 'analysis ready', - '--only', - '--', - p.posix.join('cleanrooms', branch, 'analyze'), - ]); - _writeState(tempDir.path, 'ANALYZE', issue: '51'); - final commitsBefore = _commitCount(tempDir.path); - - final output = await StateTransitionCommand( - StateTransitionInput( - currentState: null, - event: 'complete_analysis', - workingDirectory: tempDir.path, - ), - branchProvider: (_) async => branch, - ).execute(); - - expect(output.allowed, isFalse); - expect(output.nextState, isNull); - expect(output.message, contains('commit')); - expect(_commitCount(tempDir.path), commitsBefore); - - final stateContent = File( - _cycleStatePath(tempDir.path, branch), - ).readAsStringSync(); - expect(stateContent, contains('state: ANALYZE')); - - final runTrace = File( - p.join(tempDir.path, 'cleanrooms', branch, 'run_trace.yaml'), - ); - expect(runTrace.existsSync(), isTrue); - final traceContent = runTrace.readAsStringSync(); - expect(traceContent, contains('event_class: sensor_run')); - expect(traceContent, contains('gate: commit_analysis_boundary')); - expect(traceContent, contains('verdict: FAILED')); - expect( - traceContent, - contains( - 'authority: "cleanrooms/51-idle-execution-guardrails/analyze"', - ), - ); - }, - ); - test( 'fails precheck when commitment needs issue/branch and issue missing', () async { @@ -668,7 +600,7 @@ void main() { ); test( - 'transitions PLAN -> EXECUTE only after plan boundary commit', + 'transitions PLAN -> EXECUTE without a boundary commit', () async { const branch = '51-idle-execution-guardrails'; _initGitRepo(tempDir.path, branch: branch); @@ -690,7 +622,8 @@ void main() { expect(output.promptFragmentId, 'plan_to_execute'); expect(output.requiredInstructions, ['coding-manifesto-review']); expect(output.toText(), startsWith(output.message)); - expect(_commitCount(tempDir.path), commitsBefore + 1); + // cleanrooms/ is ephemeral and git-ignored: no boundary commit. + expect(_commitCount(tempDir.path), commitsBefore); final stateContent = File( _cycleStatePath(tempDir.path, branch), @@ -705,89 +638,14 @@ void main() { final traceContent = runTrace.readAsStringSync(); expect(traceContent, contains('task_id: "51"')); expect(traceContent, contains('event_class: sensor_run')); - expect(traceContent, contains('event_class: tool_activity')); - expect(traceContent, contains('tool_class: git')); - expect(traceContent, contains('command_family: add')); - expect(traceContent, contains('command_family: commit')); expect(traceContent, contains('gate: plan_approved')); - expect(traceContent, contains('gate: commit_plan_boundary')); expect(traceContent, contains('sensor_category: pre_transition')); expect(traceContent, contains('verdict: APPROVED')); - expect( - traceContent, - contains( - 'authority: "cleanrooms/51-idle-execution-guardrails/plan.md"', - ), - ); expect(traceContent, contains('transition_event: approve_plan')); expect(traceContent, contains('outcome: allowed')); - }, - ); - - test( - 'fails closed when PLAN -> EXECUTE cannot create boundary commit', - () async { - const branch = '51-idle-execution-guardrails'; - final planPath = p.posix.join('cleanrooms', branch, 'plan.md'); - - _initGitRepo(tempDir.path, branch: branch); - _writePlan(tempDir.path, branch, '# committed plan\n'); - _git(tempDir.path, ['add', '--', planPath]); - _git(tempDir.path, [ - 'commit', - '-m', - 'plan ready', - '--only', - '--', - planPath, - ]); - _writeState(tempDir.path, 'PLAN', issue: '51'); - final commitsBefore = _commitCount(tempDir.path); - - final output = await StateTransitionCommand( - StateTransitionInput( - currentState: null, - event: 'approve_plan', - workingDirectory: tempDir.path, - ), - branchProvider: (_) async => branch, - ).execute(); - - expect(output.allowed, isFalse); - expect(output.nextState, isNull); - expect(output.message, contains('commit')); - expect(_commitCount(tempDir.path), commitsBefore); - - final stateContent = File( - _cycleStatePath(tempDir.path, branch), - ).readAsStringSync(); - expect(stateContent, contains('state: PLAN')); - - final runTrace = File( - p.join(tempDir.path, 'cleanrooms', branch, 'run_trace.yaml'), - ); - expect(runTrace.existsSync(), isTrue); - final traceContent = runTrace.readAsStringSync(); - expect(traceContent, contains('event_class: transition')); - expect(traceContent, contains('event_class: sensor_run')); - expect(traceContent, contains('event_class: tool_activity')); - expect(traceContent, contains('tool_class: git')); - expect(traceContent, contains('command_family: add')); - expect(traceContent, contains('command_family: commit')); - expect(traceContent, contains('outcome: failed')); - expect(traceContent, contains('transition_event: approve_plan')); - expect(traceContent, contains('gate: commit_plan_boundary')); - expect(traceContent, contains('verdict: FAILED')); - expect(traceContent, contains('to_state: EXECUTE')); - expect(traceContent, contains('outcome: blocked')); - expect(traceContent, contains('event_class: block')); - expect(traceContent, contains('blocking_boundary: boundary_commit')); - expect( - traceContent, - contains( - 'authoritative_surface: "cleanrooms/51-idle-execution-guardrails/plan.md"', - ), - ); + // No boundary-commit machinery runs anymore. + expect(traceContent, isNot(contains('gate: commit_plan_boundary'))); + expect(traceContent, isNot(contains('command_family: commit'))); }, ); diff --git a/code/site/index.html b/code/site/index.html index badc6eea..009708aa 100644 --- a/code/site/index.html +++ b/code/site/index.html @@ -31,7 +31,7 @@

Inquiry

Analyze. Plan. Execute.

Available for OpenCode and GitHub Copilot. More hosts coming soon.

- v0.21.0 + v0.21.1

Public entry surface. For the canonical documentation map, start in the repository docs.