aiwcli 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- package/package.json +109 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CC-Native Gemini Reviewer Module.
|
|
3
|
+
|
|
4
|
+
Runs Gemini CLI to review plans.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
# Import from parent lib
|
|
15
|
+
_lib_dir = Path(__file__).resolve().parent.parent
|
|
16
|
+
sys.path.insert(0, str(_lib_dir))
|
|
17
|
+
|
|
18
|
+
from utils import ReviewerResult, eprint, parse_json_maybe, coerce_to_review
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_gemini_review(
|
|
22
|
+
plan: str,
|
|
23
|
+
schema: Dict[str, Any],
|
|
24
|
+
settings: Dict[str, Any],
|
|
25
|
+
) -> ReviewerResult:
|
|
26
|
+
"""Run Gemini CLI to review the plan.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
plan: The plan content to review
|
|
30
|
+
schema: JSON schema for the review output
|
|
31
|
+
settings: Gemini reviewer settings (timeout, model)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
ReviewerResult with the review output
|
|
35
|
+
"""
|
|
36
|
+
gemini_settings = settings.get("reviewers", {}).get("gemini", {})
|
|
37
|
+
timeout = gemini_settings.get("timeout", 120)
|
|
38
|
+
model = gemini_settings.get("model", "")
|
|
39
|
+
|
|
40
|
+
gemini_path = shutil.which("gemini")
|
|
41
|
+
if gemini_path is None:
|
|
42
|
+
eprint("[gemini] CLI not found on PATH")
|
|
43
|
+
return ReviewerResult(
|
|
44
|
+
name="gemini",
|
|
45
|
+
ok=False,
|
|
46
|
+
verdict="skip",
|
|
47
|
+
data={},
|
|
48
|
+
raw="",
|
|
49
|
+
err="gemini CLI not found on PATH",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
eprint(f"[gemini] Found CLI at: {gemini_path}")
|
|
53
|
+
|
|
54
|
+
instruction = f"""
|
|
55
|
+
|
|
56
|
+
Review the PLAN above as a senior staff software engineer. Focus on:
|
|
57
|
+
- missing steps, unclear assumptions, edge cases
|
|
58
|
+
- security/privacy concerns
|
|
59
|
+
- testing/rollout/rollback completeness
|
|
60
|
+
- operational concerns (observability, failure modes)
|
|
61
|
+
|
|
62
|
+
Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fences):
|
|
63
|
+
{json.dumps(schema, ensure_ascii=False)}
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
cmd = [
|
|
67
|
+
gemini_path,
|
|
68
|
+
"-y", # YOLO mode - auto-approve all actions
|
|
69
|
+
"-p",
|
|
70
|
+
instruction,
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
if model:
|
|
74
|
+
cmd.extend(["--model", model])
|
|
75
|
+
|
|
76
|
+
eprint("[gemini] Running command: gemini -y -p <instruction>")
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
p = subprocess.run(
|
|
80
|
+
cmd,
|
|
81
|
+
input=plan,
|
|
82
|
+
text=True,
|
|
83
|
+
capture_output=True,
|
|
84
|
+
timeout=timeout,
|
|
85
|
+
encoding='utf-8',
|
|
86
|
+
errors='replace',
|
|
87
|
+
)
|
|
88
|
+
except subprocess.TimeoutExpired:
|
|
89
|
+
eprint(f"[gemini] TIMEOUT after {timeout}s")
|
|
90
|
+
return ReviewerResult("gemini", False, "error", {}, "", f"gemini timed out after {timeout}s")
|
|
91
|
+
except Exception as ex:
|
|
92
|
+
eprint(f"[gemini] EXCEPTION: {ex}")
|
|
93
|
+
return ReviewerResult("gemini", False, "error", {}, "", f"gemini failed to run: {ex}")
|
|
94
|
+
|
|
95
|
+
eprint(f"[gemini] Exit code: {p.returncode}")
|
|
96
|
+
|
|
97
|
+
raw = (p.stdout or "").strip()
|
|
98
|
+
err = (p.stderr or "").strip()
|
|
99
|
+
|
|
100
|
+
obj = parse_json_maybe(raw)
|
|
101
|
+
ok, verdict, norm = coerce_to_review(obj, "Retry or check CLI auth/config.")
|
|
102
|
+
|
|
103
|
+
return ReviewerResult("gemini", ok, verdict, norm, raw, err)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CC-Native State Management Module.
|
|
3
|
+
|
|
4
|
+
Handles plan state file operations and iteration tracking for the review process.
|
|
5
|
+
|
|
6
|
+
State files are stored adjacent to plan files (e.g., ~/.claude/plans/foo.state.json)
|
|
7
|
+
to prevent state loss when session IDs change or temp files are cleaned up.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from .constants import validate_plan_path, PLANS_DIR
|
|
18
|
+
from .atomic_write import atomic_write
|
|
19
|
+
except ImportError:
|
|
20
|
+
# When imported directly via sys.path (not as a package)
|
|
21
|
+
from constants import validate_plan_path, PLANS_DIR
|
|
22
|
+
from atomic_write import atomic_write
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------
|
|
26
|
+
# Constants
|
|
27
|
+
# ---------------------------
|
|
28
|
+
|
|
29
|
+
STATE_SCHEMA_VERSION = "1.0.0"
|
|
30
|
+
|
|
31
|
+
DEFAULT_REVIEW_ITERATIONS: Dict[str, int] = {
|
|
32
|
+
"simple": 1,
|
|
33
|
+
"medium": 1,
|
|
34
|
+
"high": 2,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------
|
|
39
|
+
# Utilities
|
|
40
|
+
# ---------------------------
|
|
41
|
+
|
|
42
|
+
def eprint(*args: Any) -> None:
|
|
43
|
+
"""Print to stderr."""
|
|
44
|
+
print(*args, file=sys.stderr)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------
|
|
48
|
+
# State File Management
|
|
49
|
+
# ---------------------------
|
|
50
|
+
|
|
51
|
+
def get_state_file_path(plan_path: str) -> Path:
|
|
52
|
+
"""Derive state file path from plan file path with security validation.
|
|
53
|
+
|
|
54
|
+
The state file is stored adjacent to the plan file with a .state.json extension.
|
|
55
|
+
This prevents state loss when session IDs change or temp files are cleaned up.
|
|
56
|
+
|
|
57
|
+
Example: ~/.claude/plans/foo.md -> ~/.claude/plans/foo.state.json
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If plan_path is invalid or insecure
|
|
61
|
+
"""
|
|
62
|
+
# SECURITY: Validate path before any operations
|
|
63
|
+
validated_path = validate_plan_path(plan_path)
|
|
64
|
+
|
|
65
|
+
# State file is always adjacent to plan file
|
|
66
|
+
return validated_path.with_suffix('.state.json')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def load_state(plan_path: str) -> Optional[Dict[str, Any]]:
|
|
70
|
+
"""Load state file with schema validation and migration."""
|
|
71
|
+
try:
|
|
72
|
+
state_file = get_state_file_path(plan_path) # Validates path
|
|
73
|
+
|
|
74
|
+
if not state_file.exists():
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
state = json.loads(state_file.read_text(encoding="utf-8"))
|
|
78
|
+
|
|
79
|
+
# Handle schema version (backward compatible)
|
|
80
|
+
schema_version = state.get("schema_version")
|
|
81
|
+
|
|
82
|
+
if schema_version is None:
|
|
83
|
+
# Existing state files without version - auto-migrate
|
|
84
|
+
state["schema_version"] = STATE_SCHEMA_VERSION
|
|
85
|
+
eprint(f"[state] Migrated state file to schema v{STATE_SCHEMA_VERSION}")
|
|
86
|
+
elif schema_version != STATE_SCHEMA_VERSION:
|
|
87
|
+
eprint(f"[state] WARNING: Schema mismatch (expected {STATE_SCHEMA_VERSION}, got {schema_version})")
|
|
88
|
+
# For now, accept with warning - add migration logic here if schema changes
|
|
89
|
+
|
|
90
|
+
return state
|
|
91
|
+
|
|
92
|
+
except ValueError as e:
|
|
93
|
+
eprint(f"[state] SECURITY: Invalid plan path: {e}")
|
|
94
|
+
return None
|
|
95
|
+
except Exception as e:
|
|
96
|
+
eprint(f"[state] ERROR: Failed to load state: {e}")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def save_state(plan_path: str, state: Dict[str, Any]) -> bool:
|
|
101
|
+
"""Save state file with schema version and validation.
|
|
102
|
+
|
|
103
|
+
Returns True on success, False on failure.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
state_file = get_state_file_path(plan_path) # Validates path
|
|
107
|
+
|
|
108
|
+
state_with_version = {
|
|
109
|
+
"schema_version": STATE_SCHEMA_VERSION,
|
|
110
|
+
**state
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Use atomic write
|
|
114
|
+
success, error = atomic_write(
|
|
115
|
+
state_file,
|
|
116
|
+
json.dumps(state_with_version, indent=2)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if not success:
|
|
120
|
+
eprint(f"[state] Failed to save state: {error}")
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
except ValueError as e:
|
|
126
|
+
eprint(f"[state] SECURITY: Invalid plan path: {e}")
|
|
127
|
+
return False
|
|
128
|
+
except Exception as e:
|
|
129
|
+
eprint(f"[state] ERROR: {e}")
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def delete_state(plan_path: str) -> bool:
|
|
134
|
+
"""Delete state file after successful archive.
|
|
135
|
+
|
|
136
|
+
Returns True if deleted or didn't exist, False on error.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
state_file = get_state_file_path(plan_path)
|
|
140
|
+
if state_file.exists():
|
|
141
|
+
state_file.unlink()
|
|
142
|
+
eprint(f"[state] Deleted state file: {state_file}")
|
|
143
|
+
return True
|
|
144
|
+
except ValueError as e:
|
|
145
|
+
eprint(f"[state] SECURITY: Invalid plan path in delete: {e}")
|
|
146
|
+
return False
|
|
147
|
+
except Exception as e:
|
|
148
|
+
eprint(f"[state] Warning: failed to delete state file: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------
|
|
153
|
+
# Iteration State Management
|
|
154
|
+
# ---------------------------
|
|
155
|
+
|
|
156
|
+
def get_iteration_state(
|
|
157
|
+
state: Dict[str, Any],
|
|
158
|
+
complexity: str,
|
|
159
|
+
config: Optional[Dict[str, Any]] = None,
|
|
160
|
+
) -> Dict[str, Any]:
|
|
161
|
+
"""Get or initialize iteration state based on complexity.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
state: The current plan state dict
|
|
165
|
+
complexity: Plan complexity level (simple/medium/high)
|
|
166
|
+
config: Optional config dict with reviewIterations settings
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Iteration dict with: current, max, complexity, history
|
|
170
|
+
"""
|
|
171
|
+
if "iteration" in state:
|
|
172
|
+
# Return existing iteration state
|
|
173
|
+
return state["iteration"]
|
|
174
|
+
|
|
175
|
+
# Initialize new iteration state
|
|
176
|
+
review_iterations = DEFAULT_REVIEW_ITERATIONS.copy()
|
|
177
|
+
if config:
|
|
178
|
+
review_iterations.update(config.get("reviewIterations", {}))
|
|
179
|
+
max_iterations = review_iterations.get(complexity, 1)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"current": 1,
|
|
183
|
+
"max": max_iterations,
|
|
184
|
+
"complexity": complexity,
|
|
185
|
+
"history": [],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def update_iteration_state(
|
|
190
|
+
state: Dict[str, Any],
|
|
191
|
+
iteration: Dict[str, Any],
|
|
192
|
+
plan_hash: str,
|
|
193
|
+
verdict: str,
|
|
194
|
+
) -> Dict[str, Any]:
|
|
195
|
+
"""Record review result in iteration history and update state.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
state: The current plan state dict
|
|
199
|
+
iteration: The iteration state dict
|
|
200
|
+
plan_hash: Hash of the current plan content
|
|
201
|
+
verdict: Review verdict (pass/warn/fail)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Updated state dict with iteration data
|
|
205
|
+
"""
|
|
206
|
+
# Add this review to history
|
|
207
|
+
iteration["history"].append({
|
|
208
|
+
"hash": plan_hash,
|
|
209
|
+
"verdict": verdict,
|
|
210
|
+
"timestamp": datetime.now().isoformat(),
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
# Update state with iteration data
|
|
214
|
+
state["iteration"] = iteration
|
|
215
|
+
return state
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def should_continue_iterating(
|
|
219
|
+
iteration: Dict[str, Any],
|
|
220
|
+
verdict: str,
|
|
221
|
+
config: Optional[Dict[str, Any]] = None,
|
|
222
|
+
) -> bool:
|
|
223
|
+
"""Determine if more review iterations are needed.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
iteration: The iteration state dict
|
|
227
|
+
verdict: Current review verdict
|
|
228
|
+
config: Optional config dict with earlyExitOnAllPass setting
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if more iterations needed, False otherwise
|
|
232
|
+
"""
|
|
233
|
+
current = iteration.get("current", 1)
|
|
234
|
+
max_iter = iteration.get("max", 1)
|
|
235
|
+
|
|
236
|
+
# At or past max iterations - no more iterations
|
|
237
|
+
if current >= max_iter:
|
|
238
|
+
eprint(f"[state] At max iterations ({current}/{max_iter}), no more iterations")
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
# Check early exit on all pass
|
|
242
|
+
early_exit = True
|
|
243
|
+
if config:
|
|
244
|
+
early_exit = config.get("earlyExitOnAllPass", True)
|
|
245
|
+
if early_exit and verdict == "pass":
|
|
246
|
+
eprint(f"[state] All reviewers passed and earlyExitOnAllPass=true, exiting early")
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
# More iterations available and verdict is not pass (or early exit disabled)
|
|
250
|
+
eprint(f"[state] Continuing to next iteration ({current + 1}/{max_iter}), verdict={verdict}")
|
|
251
|
+
return True
|