Skip to content

feat(cli): add --plan-file and --track-plan-file options with comprehensive validation#8

Merged
SihaoLiu merged 12 commits into
mainfrom
add-commit-plan-file-cli
Jan 16, 2026
Merged

feat(cli): add --plan-file and --track-plan-file options with comprehensive validation#8
SihaoLiu merged 12 commits into
mainfrom
add-commit-plan-file-cli

Conversation

@SihaoLiu
Copy link
Copy Markdown
Contributor

Summary

  • Add --plan-file CLI option as alternative to positional argument
  • Add --track-plan-file flag for git-tracked plan files
  • Implement comprehensive plan file validation (path, git status, integrity)
  • Add UserPromptSubmit hook for runtime validation
  • Enhance stop hook with plan file integrity checks
  • Add state.md rename on exit (complete-state.md, cancel-state.md, etc.)

Key Features

CLI Options

  • --plan-file <path>: Explicit plan file path (alternative to positional arg)
  • --track-plan-file: Flag indicating plan file should be tracked in git

Validation

  • Git repo with at least one commit required
  • Plan file must be relative path, within project, not symlink, not in submodule
  • Path validation rejects spaces and shell metacharacters
  • Branch name validation rejects YAML-unsafe characters (`: # " ' ``)
  • Tracking validation enforces gitignored (default) or tracked+clean status

Security Fixes

  • Fixed bash validator regex bypass for direct .humanize-loop.local/plan.md
  • Fixed YAML injection via branch names
  • Fixed race condition in stop hook for tracked files
  • Added explicit error handling for path resolution failures
  • Changed schema validation to return JSON block instead of silent exit

State Management

  • Plan file backup copied to loop directory as plan.md
  • New state fields: plan_tracked, start_branch
  • State.md renamed on exit: complete-state.md, cancel-state.md, maxiter-state.md, unexpected-state.md

Test Plan

  • Run ./tests/test-plan-file-validation.sh - 19 tests pass
  • Run ./tests/test-plan-file-hooks.sh - 24 tests pass
  • Run ./tests/test-state-exit-naming.sh - 10 tests pass
  • All 53 tests pass with comprehensive coverage for security fixes

Files Changed

Category Files
Scripts scripts/setup-rlcr-loop.sh
Hooks loop-plan-file-validator.sh (new), loop-codex-stop-hook.sh, loop-write-validator.sh, loop-edit-validator.sh, loop-bash-validator.sh, lib/loop-common.sh
Commands cancel-rlcr-loop.md, start-rlcr-loop.md
Templates block/plan-backup-protected.md, block/plan-file-modified.md, block/schema-outdated.md
Tests test-plan-file-validation.sh, test-plan-file-hooks.sh, test-state-exit-naming.sh
CI .github/workflows/plan-file-test.yml

- Add end_loop() function to loop-common.sh for standardized state file
  renaming on exit (complete, cancel, maxiter, stop, unexpected)
- Refactor loop-codex-stop-hook.sh to use end_loop() instead of inline mv
- Fix bash validator to use command_modifies_file() helper with rm pattern
  for more robust plan.md protection
- Fix DRY violation in loop-plan-file-validator.sh schema validation
- Use schema-outdated.md template instead of hardcoded JSON
- Add parent directory existence check in setup-rlcr-loop.sh for clearer
  error messages when path contains typos
- Add negative tests for end_loop() function and path validation
Add tests for plan file modification detection in stop hook:
- Test 9: Block when plan file is modified during loop
- Test 10: Block when plan file is deleted during loop
- Test 11: Block when plan backup is missing

Also revert version to 1.1.2 (single bump on feature branch)
- Use find_active_loop() in loop-plan-file-validator.sh instead of duplicating logic
- Update cancel-rlcr-loop.md to find newest directory first, then check state.md
- Document rename-on-exit behavior (state.md -> <reason>-state.md) in README
- Add Exit State Files documentation section
- Remove unused PLAN_IS_IGNORED variable from setup-rlcr-loop.sh
- Consolidate redundant state file parsing in stop hook into single section
- Update plan-file-modified check to use load_and_render_safe with template
- Add || true to grep pipelines for pipefail compatibility with missing fields
- Quote plan_file value in state.md YAML to prevent injection with
  special characters like colons and hashes
- Add validation to reject plan file paths containing spaces at setup
- Make space trimming consistent across all hooks (use tr -d ' ')
- Update hook parsing to strip YAML quotes from plan_file value
- Add tests for space validation and YAML-quoted plan_file parsing
- Document no-spaces restriction in help text
- Fix command injection bypass in bash validator by using broader
  pattern matching that catches shell expansion/substitution attempts
- Quote start_branch in state.md to prevent YAML injection (completing
  the fix from 76982ea which only quoted plan_file)
- Fix inconsistent quote handling in state parsers by removing tr -d ' '
  from PLAN_FILE and adding quote removal to START_BRANCH parsing

Tests added:
- Command injection bypass prevention (5 tests): command substitution,
  glob expansion, brace expansion, piped commands, backtick substitution
- YAML quote parsing (4 tests): quote stripping, branch mismatch
  detection, stop hook parsing, hyphenated paths
- Fix race condition: skip diff check for tracked plan files (rely on
  git status in UserPromptSubmit hook instead)
- Add shell metacharacter validation to prevent injection in paths
- Use command_modifies_file helper for plan.md bash validation
- Add missing bash modification patterns (truncate, printf, exec)
- Standardize YAML: remove unnecessary quotes from state file values
- Standardize template fallback formatting with consistent blank lines
- Optimize git submodule check by checking .gitmodules first
- Add test for shell metacharacter rejection
Fix 5 issues identified in code review:

1. Bash validator regex bypass (Critical)
   - Pattern now matches .humanize-loop.local/plan.md directly
   - Changed [^/]+/plan\.md to (/[^/]+)?/plan\.md

2. YAML injection via branch names (Critical)
   - Added validation for YAML-unsafe characters in branch names
   - Rejects branches containing : # " ' ` or newlines

3. Race condition in stop hook (Critical)
   - Added git status check for tracked files in stop hook
   - Catches modifications between UserPromptSubmit and stop hook

4. Path resolution edge case (Important)
   - Added explicit error handling for cd failures
   - Provides clear error message for permission/symlink issues

5. Schema version handling (Important)
   - Changed from silent exit to JSON block response
   - Users now get clear instructions to cancel or update

Added comprehensive tests for all fixes:
- Test 8a: Direct .humanize-loop.local/plan.md bypass
- Test 3.5: Path resolution error handling
- Tests 9.5-9.7: YAML-unsafe branch name rejection
- Test 12: Tracked file race condition detection
- Test 13: Schema validation JSON block response
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b49dad6c79

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +180 to +183
if [[ "$PLAN_TRACKED" == "true" ]]; then
# Tracked file: check git status (final safety check for race condition)
PLAN_GIT_STATUS=$(git -C "$PROJECT_ROOT" status --porcelain "$PLAN_FILE" 2>/dev/null || echo "")
if [[ -n "$PLAN_GIT_STATUS" ]]; then
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce plan integrity for tracked plan files

When plan_tracked is true, the stop hook only checks git status and allows exit if the working tree is clean. This means a user can edit the plan file and commit the change during the loop, making the status clean while the content no longer matches the original plan backup, so the loop will not detect the modification even though the templates and README state plan changes are forbidden. This bypasses the new integrity guarantee for tracked plans; consider comparing against the backup (or recording a hash in state) even in tracked mode so committed changes are still blocked.

Useful? React with 👍 / 👎.

Tests now work on both systems with 'main' and 'master' as default branch.
- setup_test_loop() captures DEFAULT_BRANCH after git init
- All state.md files use the actual branch name
- Branch validation tests use BRANCH_TEST_DEFAULT variable
@claude
Copy link
Copy Markdown

claude Bot commented Jan 16, 2026


Code review

No issues found. Checked for bugs and CLAUDE.md compliance.


When plan_tracked is true, previously only git status was checked.
This allowed bypassing integrity verification by committing plan changes
(git status would be clean but content differs from backup).

Now the stop hook always verifies content matches backup using diff,
regardless of tracking mode. This catches both uncommitted and
committed changes to the plan file.

Added Test 14 to verify committed changes to tracked files are blocked.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 16, 2026

No issues found. Checked for bugs and CLAUDE.md compliance.

@SihaoLiu
Copy link
Copy Markdown
Contributor Author

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90e7c52ec9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +101 to +102
PLAN_IS_TRACKED=$(git -C "$PROJECT_ROOT" ls-files --error-unmatch "$PLAN_FILE" &>/dev/null && echo "true" || echo "false")
PLAN_GIT_STATUS=$(git -C "$PROJECT_ROOT" status --porcelain "$PLAN_FILE" 2>/dev/null || echo "")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard git pathspecs against leading '-' in plan paths

The plan-path validation allows filenames beginning with -, but the git checks here pass the path as a bare argument (git ... ls-files/status "$PLAN_FILE"). If the plan file is named like -plan.md or plans/-plan.md, git interprets it as an option rather than a pathspec and the hook will misclassify the file as untracked/clean, or error out and block the loop even though the file is valid. To avoid this edge-case regression, pass -- before the pathspec (e.g., git ... -- "$PLAN_FILE") or explicitly reject plan filenames that start with -.

Useful? React with 👍 / 👎.

@SihaoLiu SihaoLiu merged commit a7532e6 into main Jan 16, 2026
10 checks passed
SihaoLiu added a commit that referenced this pull request Jan 17, 2026
Fixes based on Round 0 Codex review:

HIGH - Haiku sub-agent invocation (AC-2.3):
- Changed from generic "general-purpose" subagent to explicit
  "draft-relevance-checker" agent reference
- Added clear Task tool parameters documentation
- Referenced the custom agent in agents/draft-relevance-checker.md

HIGH - Missing positive/negative test descriptions (AC-6):
- Added TDD-style test format to Acceptance Criteria template
- Each AC now includes Positive Tests (expected to PASS) and
  Negative Tests (expected to FAIL) sections
- Added Generation Rule #8 enforcing TDD-style tests

MEDIUM - Path boundary phrasing (AC-6.3):
- Changed from negative phrasing ("What should NOT be done") to
  affirmative descriptions ("Maximum Acceptable Scope")
- Added examples showing affirmative descriptions
- Added Generation Rule #9 for affirmative path boundaries

MEDIUM - Narrow path rule for deterministic drafts (AC-6.3):
- Added "Note on Deterministic Designs" explaining when upper and
  lower bounds should converge
- Added Generation Rule #10 for respecting fixed approaches
SihaoLiu added a commit that referenced this pull request Jan 17, 2026
…ns (v1.3.0) (#17)

* Feature: Add gen-plan command to transform drafts into structured plans (v1.2.4)

Add a new slash command /humanize:gen-plan that transforms user draft documents
into well-structured implementation plans with acceptance criteria (AC-X format).

New files:
- commands/gen-plan.md: Slash command entry point
- skills/gen-plan/SKILL.md: Main skill implementation with opus model
- scripts/validate-gen-plan-io.sh: IO path validation script
- agents/draft-relevance-checker.md: Haiku-based relevance checker

Workflow:
1. Validates input/output paths with clear error messages
2. Uses haiku sub-agent to check draft relevance to repository
3. Analyzes draft for clarity, consistency, completeness, functionality
4. Engages user via AskUserQuestion to resolve issues
5. Generates plan.md with goals, ACs, path boundaries, and suggestions

Additional changes:
- Unified model names to use short aliases (opus, sonnet, haiku) across all skills
- Updated test validation to accept short model aliases

Updated version to 1.2.4 in plugin.json, marketplace.json, and README.md.

* Fix: Address Codex review findings for gen-plan skill

Fixes based on Round 0 Codex review:

HIGH - Haiku sub-agent invocation (AC-2.3):
- Changed from generic "general-purpose" subagent to explicit
  "draft-relevance-checker" agent reference
- Added clear Task tool parameters documentation
- Referenced the custom agent in agents/draft-relevance-checker.md

HIGH - Missing positive/negative test descriptions (AC-6):
- Added TDD-style test format to Acceptance Criteria template
- Each AC now includes Positive Tests (expected to PASS) and
  Negative Tests (expected to FAIL) sections
- Added Generation Rule #8 enforcing TDD-style tests

MEDIUM - Path boundary phrasing (AC-6.3):
- Changed from negative phrasing ("What should NOT be done") to
  affirmative descriptions ("Maximum Acceptable Scope")
- Added examples showing affirmative descriptions
- Added Generation Rule #9 for affirmative path boundaries

MEDIUM - Narrow path rule for deterministic drafts (AC-6.3):
- Added "Note on Deterministic Designs" explaining when upper and
  lower bounds should converge
- Added Generation Rule #10 for respecting fixed approaches

* Simplify: Clean up gen-plan implementation (Finalize Phase)

Code simplification after Codex review approval:

commands/gen-plan.md:
- Simplified trailing description to single sentence
- Consistent with other command files in the project

scripts/validate-gen-plan-io.sh:
- Removed unnecessary empty echo statements
- Consolidated error messages for conciseness
- 11 lines removed, functionality unchanged

skills/gen-plan/SKILL.md:
- Simplified Phase 6 from numbered list to concise paragraph
- 4 lines removed, same information preserved

All 49 tests pass. Validation script tested and works correctly.

* Bump version to 1.3.0 for gen-plan feature release

Version bump from 1.2.4 to 1.3.0 for the new gen-plan command feature.

* Fix: Validate argument values before shift to ensure correct exit codes

Bug fix:
- When --input or --output is passed without a value, shift 2 would fail
  under set -e and exit with code 1 (mapped to "input not found") instead
  of exit code 6 ("invalid arguments")
- Added checks for missing argument values before shift 2
- Also catches flags followed by another flag (e.g., --input --output)

Test enhancement:
- Added 10 comprehensive tests for validate-gen-plan-io.sh exit codes
- Tests cover: missing values, unknown options, all validation exit codes
- Total tests now: 59 (was 49)

* Fix: Model validation requires exact match for short aliases

The regex was treating opus/sonnet/haiku as prefixes, accepting invalid
values like opus-v2, haiku123, or sonnet-fast.

Fix:
- Short aliases (opus, sonnet, haiku) now require exact match via ^...$
- Full model IDs (claude-*, gpt-*, etc.) still use prefix matching

Added tests:
- NT-6d: Rejects opus-v2 (partial match)
- NT-6e: Rejects haiku123 (partial match)
- NT-6f: Rejects sonnet-fast (partial match)

Total tests: 62

* Fix: Reject directory paths for --output parameter

Bug fix:
- If --output pointed to an existing directory (e.g., /tmp/ instead of
  /tmp/plan.md), validation passed but Write would fail at runtime
- Now rejects any existing path at output location (file, directory, symlink)
- Added specific error message when output is a directory with suggestion

Changes:
- Check 4 now uses -d to detect directories with clear error message
- Check 4 uses -e to catch any existing path (not just regular files)

Added test:
- validate-gen-plan-io: output is directory exits 4

Total tests: 63
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant