anvil-dev-framework 0.1.6 → 0.1.8
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 +33 -13
- package/VERSION +1 -1
- package/docs/ANV-263-hook-logging-investigation.md +116 -0
- package/docs/INSTALLATION.md +18 -0
- package/docs/command-reference.md +302 -2
- package/docs/session-workflow.md +62 -9
- package/docs/system-architecture.md +569 -0
- package/global/commands/anvil-settings.md +3 -1
- package/global/commands/audit.md +163 -0
- package/global/commands/checklist.md +180 -0
- package/global/commands/efficiency.md +356 -0
- package/global/commands/evidence.md +99 -32
- package/global/commands/insights.md +101 -3
- package/global/commands/orient.md +29 -0
- package/global/commands/patterns.md +115 -0
- package/global/commands/ralph.md +47 -1
- package/global/commands/token-budget.md +214 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/context_optimizer.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/git_utils.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/handoff_generator.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/optimization_applier.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/orient_fast.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_prompt_generator.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/token_analyzer.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/token_metrics.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/context_optimizer.py +323 -0
- package/global/lib/git_utils.py +267 -0
- package/global/lib/issue_models.py +28 -0
- package/global/lib/linear_provider.py +217 -16
- package/global/lib/optimization_applier.py +582 -0
- package/global/lib/orient_fast.py +24 -1
- package/global/lib/ralph_state.py +264 -24
- package/global/lib/token_analyzer.py +1357 -0
- package/global/lib/token_metrics.py +873 -0
- package/global/tests/__pycache__/test_context_optimizer.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_issue_models.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_linear_filtering.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_linear_provider.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_local_provider.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_optimization_applier.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_analyzer.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_analyzer_phase6.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_metrics.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/test_context_optimizer.py +321 -0
- package/global/tests/test_git_utils.py +160 -0
- package/global/tests/test_issue_models.py +40 -0
- package/global/tests/test_linear_filtering.py +319 -0
- package/global/tests/test_linear_provider.py +125 -0
- package/global/tests/test_optimization_applier.py +508 -0
- package/global/tests/test_token_analyzer.py +735 -0
- package/global/tests/test_token_analyzer_phase6.py +537 -0
- package/global/tests/test_token_metrics.py +791 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +238 -1
- package/global/tools/anvil-memory/src/commands/ralph-iteration.ts +249 -0
- package/global/tools/anvil-memory/src/index.ts +2 -8
- package/package.json +1 -1
- package/scripts/anvil +7 -2
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +0 -535
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +0 -645
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +0 -363
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +0 -8
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +0 -417
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +0 -571
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +0 -440
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +0 -252
|
@@ -580,9 +580,92 @@ To resume later, run: /ralph start "{self.objective}"
|
|
|
580
580
|
|
|
581
581
|
|
|
582
582
|
# =========================================================================
|
|
583
|
-
# Linear Integration (ANV-211)
|
|
583
|
+
# Linear Integration (ANV-211, ANV-214)
|
|
584
584
|
# =========================================================================
|
|
585
585
|
|
|
586
|
+
@classmethod
|
|
587
|
+
def initialize_from_project(
|
|
588
|
+
cls,
|
|
589
|
+
project_name: str,
|
|
590
|
+
no_sync: bool = False,
|
|
591
|
+
include_done: bool = False,
|
|
592
|
+
state_file: str = DEFAULT_STATE_FILE,
|
|
593
|
+
) -> "RalphState":
|
|
594
|
+
"""Initialize Ralph session from a Linear project (ANV-214).
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
project_name: Linear project name (e.g., "HUD Development")
|
|
598
|
+
no_sync: If True, don't sync status updates back to Linear
|
|
599
|
+
include_done: If True, include already-completed issues
|
|
600
|
+
state_file: Path to state file
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Initialized RalphState with Linear integration enabled in project mode
|
|
604
|
+
"""
|
|
605
|
+
provider = _create_linear_provider()
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
issues = provider.get_project_issues(
|
|
609
|
+
project_name=project_name, include_done=include_done
|
|
610
|
+
)
|
|
611
|
+
except (KeyError, AttributeError):
|
|
612
|
+
raise ValueError(f"Project '{project_name}' not found in Linear") from None
|
|
613
|
+
|
|
614
|
+
if not issues:
|
|
615
|
+
raise ValueError(f"Project '{project_name}' has no issues to process")
|
|
616
|
+
|
|
617
|
+
# Build LinearSubtask objects from Issue instances
|
|
618
|
+
subtasks = []
|
|
619
|
+
todo_items = []
|
|
620
|
+
for issue in issues:
|
|
621
|
+
issue_status = issue.status.value.lower() if issue.status else ""
|
|
622
|
+
status = "completed" if issue_status in ("done", "completed", "closed") else "todo"
|
|
623
|
+
subtask = LinearSubtask(
|
|
624
|
+
id=issue.id,
|
|
625
|
+
identifier=issue.identifier,
|
|
626
|
+
title=issue.title,
|
|
627
|
+
status=status,
|
|
628
|
+
)
|
|
629
|
+
subtasks.append(subtask)
|
|
630
|
+
if status == "todo":
|
|
631
|
+
todo_items.append(f"[{issue.identifier}] {issue.title}")
|
|
632
|
+
|
|
633
|
+
# Create LinearIntegration in project mode
|
|
634
|
+
linear_integration = LinearIntegration(
|
|
635
|
+
enabled=True,
|
|
636
|
+
mode="project",
|
|
637
|
+
parent_issue=None,
|
|
638
|
+
parent_id=None,
|
|
639
|
+
project_name=project_name,
|
|
640
|
+
subtasks=subtasks,
|
|
641
|
+
last_sync=datetime.now(timezone.utc).isoformat(),
|
|
642
|
+
no_sync=no_sync,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Initialize state
|
|
646
|
+
state = cls(
|
|
647
|
+
task_name=f"Project: {project_name}",
|
|
648
|
+
objective=f"Complete all issues in project '{project_name}'",
|
|
649
|
+
iteration=0,
|
|
650
|
+
started_at=datetime.now(timezone.utc).isoformat(),
|
|
651
|
+
status="running",
|
|
652
|
+
no_change_count=0,
|
|
653
|
+
last_diff_hash="",
|
|
654
|
+
error_hashes=[],
|
|
655
|
+
max_iterations=DEFAULT_MAX_ITERATIONS,
|
|
656
|
+
completion_promise=DEFAULT_COMPLETION_PROMISE,
|
|
657
|
+
todo_items=todo_items,
|
|
658
|
+
completed_items=[],
|
|
659
|
+
context_checkpoint=None,
|
|
660
|
+
context_history=[],
|
|
661
|
+
linear_integration=linear_integration,
|
|
662
|
+
state_file=state_file,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
Path(state_file).parent.mkdir(parents=True, exist_ok=True)
|
|
666
|
+
state.save()
|
|
667
|
+
return state
|
|
668
|
+
|
|
586
669
|
@classmethod
|
|
587
670
|
def initialize_from_linear(
|
|
588
671
|
cls,
|
|
@@ -761,43 +844,102 @@ To resume later, run: /ralph start "{self.objective}"
|
|
|
761
844
|
return True
|
|
762
845
|
return False
|
|
763
846
|
|
|
764
|
-
def sync_to_linear(
|
|
765
|
-
|
|
847
|
+
def sync_to_linear(
|
|
848
|
+
self,
|
|
849
|
+
identifier: str,
|
|
850
|
+
state_name: str,
|
|
851
|
+
max_retries: int = 3,
|
|
852
|
+
retry_delay: float = 1.0,
|
|
853
|
+
) -> bool:
|
|
854
|
+
"""Sync subtask status to Linear with retry logic (ANV-215).
|
|
766
855
|
|
|
767
856
|
Args:
|
|
768
857
|
identifier: Linear issue identifier
|
|
769
858
|
state_name: Target state name ("done", "in_progress", etc.)
|
|
859
|
+
max_retries: Maximum number of retry attempts
|
|
860
|
+
retry_delay: Initial delay between retries (exponential backoff)
|
|
770
861
|
|
|
771
862
|
Returns:
|
|
772
863
|
True if sync succeeded
|
|
773
864
|
"""
|
|
865
|
+
import time
|
|
866
|
+
|
|
774
867
|
if not self.linear_integration or self.linear_integration.no_sync:
|
|
775
868
|
return False
|
|
776
869
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
provider = _create_linear_provider()
|
|
780
|
-
|
|
781
|
-
# Import IssueStatus for state conversion
|
|
870
|
+
last_error = None
|
|
871
|
+
for attempt in range(max_retries):
|
|
782
872
|
try:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
from issue_models import IssueStatus
|
|
873
|
+
# ANV-120: Use _create_linear_provider() to ensure proper team config
|
|
874
|
+
provider = _create_linear_provider()
|
|
786
875
|
|
|
787
|
-
|
|
788
|
-
|
|
876
|
+
# Import IssueStatus for state conversion
|
|
877
|
+
try:
|
|
878
|
+
from .issue_models import IssueStatus
|
|
879
|
+
except ImportError:
|
|
880
|
+
from issue_models import IssueStatus
|
|
789
881
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
882
|
+
# Convert state name to IssueStatus enum
|
|
883
|
+
target_status = IssueStatus.from_linear_state(state_name)
|
|
884
|
+
|
|
885
|
+
# Update issue with the converted status
|
|
886
|
+
provider.update_issue(identifier, status=target_status)
|
|
887
|
+
self.linear_integration.last_sync = datetime.now(timezone.utc).isoformat()
|
|
888
|
+
self.save()
|
|
889
|
+
return True
|
|
890
|
+
except (ImportError, AttributeError, ValueError) as e:
|
|
891
|
+
# Non-retryable errors - fail immediately
|
|
892
|
+
print(f"Warning: Failed to sync {identifier} to Linear: {e}")
|
|
893
|
+
return False
|
|
894
|
+
except (OSError, Exception) as e:
|
|
895
|
+
# Retryable error (network/timeout/rate limit/API error)
|
|
896
|
+
# LinearProvider raises generic Exception for HTTP errors
|
|
897
|
+
last_error = e
|
|
898
|
+
if attempt < max_retries - 1:
|
|
899
|
+
delay = retry_delay * (2 ** attempt) # Exponential backoff
|
|
900
|
+
print(f"Warning: Sync failed, retrying in {delay}s: {e}")
|
|
901
|
+
time.sleep(delay)
|
|
902
|
+
|
|
903
|
+
# All retries exhausted
|
|
904
|
+
print(f"Warning: Failed to sync {identifier} after {max_retries} attempts: {last_error}")
|
|
905
|
+
return False
|
|
906
|
+
|
|
907
|
+
def mark_subtask_skipped(self, identifier: str, reason: str) -> bool:
|
|
908
|
+
"""Mark a Linear subtask as skipped (ANV-215).
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
identifier: Linear issue identifier (e.g., "ANV-215")
|
|
912
|
+
reason: Reason for skipping
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
True if subtask was found and updated
|
|
916
|
+
"""
|
|
917
|
+
return self.mark_subtask_complete(identifier, skip_reason=reason)
|
|
918
|
+
|
|
919
|
+
def handle_missing_subtask(self, identifier: str) -> None:
|
|
920
|
+
"""Handle a missing subtask gracefully (ANV-215).
|
|
921
|
+
|
|
922
|
+
Logs a warning and marks the subtask as skipped in local state
|
|
923
|
+
without attempting to sync to Linear.
|
|
924
|
+
|
|
925
|
+
Args:
|
|
926
|
+
identifier: Linear issue identifier
|
|
927
|
+
"""
|
|
928
|
+
print(f"Warning: Subtask {identifier} not found in Linear, marking as skipped")
|
|
929
|
+
if self.linear_integration and self.linear_integration.enabled:
|
|
930
|
+
for subtask in self.linear_integration.subtasks:
|
|
931
|
+
if subtask.identifier == identifier:
|
|
932
|
+
subtask.status = "skipped"
|
|
933
|
+
subtask.skip_reason = "Not found in Linear"
|
|
934
|
+
subtask.completed_at = datetime.now(timezone.utc).isoformat()
|
|
935
|
+
|
|
936
|
+
# Remove from todo_items
|
|
937
|
+
item_prefix = f"[{identifier}]"
|
|
938
|
+
self.todo_items = [t for t in self.todo_items if not t.startswith(item_prefix)]
|
|
939
|
+
self.save()
|
|
940
|
+
return
|
|
941
|
+
# Subtask not found in local state
|
|
942
|
+
print(f"Warning: {identifier} not found in local state, no action taken")
|
|
801
943
|
|
|
802
944
|
def get_linear_progress(self) -> Tuple[int, int, int]:
|
|
803
945
|
"""Get Linear subtask progress counts.
|
|
@@ -813,6 +955,49 @@ To resume later, run: /ralph start "{self.objective}"
|
|
|
813
955
|
remaining = sum(1 for s in self.linear_integration.subtasks if s.status == "todo")
|
|
814
956
|
return (completed, skipped, remaining)
|
|
815
957
|
|
|
958
|
+
def is_project_complete(self) -> bool:
|
|
959
|
+
"""Check if all issues in project are complete (ANV-214).
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
True if Linear integration is enabled in project mode and all
|
|
963
|
+
issues are either completed or skipped.
|
|
964
|
+
"""
|
|
965
|
+
if not self.linear_integration or not self.linear_integration.enabled:
|
|
966
|
+
return False
|
|
967
|
+
if self.linear_integration.mode != "project":
|
|
968
|
+
return False
|
|
969
|
+
if not self.linear_integration.subtasks:
|
|
970
|
+
return False
|
|
971
|
+
return all(s.status != "todo" for s in self.linear_integration.subtasks)
|
|
972
|
+
|
|
973
|
+
def get_current_issue_context(self) -> Optional[Dict[str, Any]]:
|
|
974
|
+
"""Get context for the current issue being worked on (ANV-214).
|
|
975
|
+
|
|
976
|
+
Returns:
|
|
977
|
+
Dictionary with current issue details, or None if no current issue.
|
|
978
|
+
"""
|
|
979
|
+
next_subtask = self.get_next_subtask()
|
|
980
|
+
if not next_subtask:
|
|
981
|
+
return None
|
|
982
|
+
|
|
983
|
+
return {
|
|
984
|
+
"id": next_subtask.id,
|
|
985
|
+
"identifier": next_subtask.identifier,
|
|
986
|
+
"title": next_subtask.title,
|
|
987
|
+
"status": next_subtask.status,
|
|
988
|
+
"mode": self.linear_integration.mode if self.linear_integration else "issue",
|
|
989
|
+
"project_name": (
|
|
990
|
+
self.linear_integration.project_name
|
|
991
|
+
if self.linear_integration
|
|
992
|
+
else None
|
|
993
|
+
),
|
|
994
|
+
"parent_issue": (
|
|
995
|
+
self.linear_integration.parent_issue
|
|
996
|
+
if self.linear_integration
|
|
997
|
+
else None
|
|
998
|
+
),
|
|
999
|
+
}
|
|
1000
|
+
|
|
816
1001
|
|
|
817
1002
|
# =============================================================================
|
|
818
1003
|
# File Generation
|
|
@@ -1056,6 +1241,20 @@ def main():
|
|
|
1056
1241
|
"--no-sync", action="store_true", help="Don't sync status back to Linear"
|
|
1057
1242
|
)
|
|
1058
1243
|
|
|
1244
|
+
# Init-project command (ANV-214)
|
|
1245
|
+
init_project_parser = subparsers.add_parser(
|
|
1246
|
+
"init-project", help="Initialize Ralph from Linear project"
|
|
1247
|
+
)
|
|
1248
|
+
init_project_parser.add_argument(
|
|
1249
|
+
"--project", required=True, help="Linear project name (e.g., 'HUD Development')"
|
|
1250
|
+
)
|
|
1251
|
+
init_project_parser.add_argument(
|
|
1252
|
+
"--no-sync", action="store_true", help="Don't sync status back to Linear"
|
|
1253
|
+
)
|
|
1254
|
+
init_project_parser.add_argument(
|
|
1255
|
+
"--include-done", action="store_true", help="Include already-completed issues"
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1059
1258
|
# Sync command (ANV-211)
|
|
1060
1259
|
sync_parser = subparsers.add_parser("sync", help="Sync status with Linear")
|
|
1061
1260
|
# --complete and --skip are mutually exclusive
|
|
@@ -1161,6 +1360,47 @@ def main():
|
|
|
1161
1360
|
except ImportError as e:
|
|
1162
1361
|
print(f"Error: Could not load Linear provider - {e}")
|
|
1163
1362
|
|
|
1363
|
+
elif args.command == "init-project":
|
|
1364
|
+
try:
|
|
1365
|
+
state = RalphState.initialize_from_project(
|
|
1366
|
+
project_name=args.project,
|
|
1367
|
+
no_sync=args.no_sync,
|
|
1368
|
+
include_done=args.include_done,
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
# Create supporting files
|
|
1372
|
+
create_fix_plan(
|
|
1373
|
+
state.task_name,
|
|
1374
|
+
state.objective,
|
|
1375
|
+
state.todo_items,
|
|
1376
|
+
)
|
|
1377
|
+
create_progress_file(
|
|
1378
|
+
state.task_name,
|
|
1379
|
+
state.objective,
|
|
1380
|
+
len(state.todo_items),
|
|
1381
|
+
)
|
|
1382
|
+
create_prompt_file(
|
|
1383
|
+
state.task_name,
|
|
1384
|
+
state.objective,
|
|
1385
|
+
remaining_count=len(state.todo_items),
|
|
1386
|
+
total_items=len(state.todo_items),
|
|
1387
|
+
started_at=state.started_at,
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1390
|
+
completed, skipped, remaining = state.get_linear_progress()
|
|
1391
|
+
print(f"Ralph session initialized from project: {args.project}")
|
|
1392
|
+
print(f" State file: {state.state_file}")
|
|
1393
|
+
print(f" Issues: {remaining} todo, {completed} already done")
|
|
1394
|
+
if args.include_done:
|
|
1395
|
+
print(" Include done: YES (processing all issues)")
|
|
1396
|
+
if args.no_sync:
|
|
1397
|
+
print(" Sync: DISABLED (changes won't update Linear)")
|
|
1398
|
+
|
|
1399
|
+
except ValueError as e:
|
|
1400
|
+
print(f"Error: {e}")
|
|
1401
|
+
except ImportError as e:
|
|
1402
|
+
print(f"Error: Could not load Linear provider - {e}")
|
|
1403
|
+
|
|
1164
1404
|
elif args.command == "sync":
|
|
1165
1405
|
state = RalphState.load()
|
|
1166
1406
|
if not state:
|