boomtick-cli 0.4.0__tar.gz → 0.5.1__tar.gz

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.
Files changed (142) hide show
  1. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/PKG-INFO +1 -1
  2. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/PKG-INFO +1 -1
  3. boomtick_cli-0.5.1/dev_tools/resources/__pycache__/__init__.cpython-312.pyc +0 -0
  4. boomtick_cli-0.5.1/dev_tools/resources/prompt_constants.json +5 -0
  5. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/dependency_graph.py +58 -12
  6. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/pyproject.toml +1 -1
  7. boomtick_cli-0.5.1/tests/test_dependency_graph.py +90 -0
  8. boomtick_cli-0.4.0/dev_tools/resources/__pycache__/__init__.cpython-312.pyc +0 -0
  9. boomtick_cli-0.4.0/dev_tools/resources/prompt_constants.json +0 -5
  10. boomtick_cli-0.4.0/tests/test_dependency_graph.py +0 -32
  11. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/MANIFEST.in +0 -0
  12. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/README.md +0 -0
  13. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/SOURCES.txt +0 -0
  14. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/dependency_links.txt +0 -0
  15. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/entry_points.txt +0 -0
  16. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/requires.txt +0 -0
  17. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/boomtick_cli.egg-info/top_level.txt +0 -0
  18. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/__init__.py +0 -0
  19. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/cli-schema.json +0 -0
  20. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/cli.py +0 -0
  21. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/config.py +0 -0
  22. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/constants.py +0 -0
  23. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/daemon.py +0 -0
  24. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/config.js +0 -0
  25. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/evals/run-evals.js +0 -0
  26. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/index.js +0 -0
  27. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/error_utils.js +0 -0
  28. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/git.js +0 -0
  29. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/result.js +0 -0
  30. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/shell.js +0 -0
  31. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/shell.test.js +0 -0
  32. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/td-cli.js +0 -0
  33. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/lib/test-utils.js +0 -0
  34. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/mcp/definitions.js +0 -0
  35. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/mcp/server.js +0 -0
  36. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/mcp/tools.js +0 -0
  37. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/contract.js +0 -0
  38. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/ddgs.search.js +0 -0
  39. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/ddgs.search.test.js +0 -0
  40. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/ddgs_search.py +0 -0
  41. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.checkout_branch.js +0 -0
  42. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.comment_triage_summary.js +0 -0
  43. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.create_issue.js +0 -0
  44. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.create_issue.test.js +0 -0
  45. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.create_pull_request.js +0 -0
  46. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.get_merge_conflict_files.js +0 -0
  47. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.get_pr.js +0 -0
  48. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.get_pr_diff.js +0 -0
  49. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.issue_comment.js +0 -0
  50. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.issue_update.js +0 -0
  51. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.issue_view.js +0 -0
  52. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.open_replacement_pr.js +0 -0
  53. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.search_open_prs.js +0 -0
  54. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/github.search_open_prs.test.js +0 -0
  55. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/cancel-session.js +0 -0
  56. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/create-session.js +0 -0
  57. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/create-session.test.js +0 -0
  58. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/get-messages.js +0 -0
  59. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/get-messages.test.js +0 -0
  60. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/get-pr.js +0 -0
  61. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/get-pr.test.js +0 -0
  62. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/get-session.js +0 -0
  63. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/list-sessions.js +0 -0
  64. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/send-message.js +0 -0
  65. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/send-message.test.js +0 -0
  66. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/shared.js +0 -0
  67. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/trigger-feedback.js +0 -0
  68. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/jules/trigger-feedback.test.js +0 -0
  69. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.commit_patch.js +0 -0
  70. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.create_branch.js +0 -0
  71. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.create_branch.test.js +0 -0
  72. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.create_repair_branch.js +0 -0
  73. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.get_changed_files.js +0 -0
  74. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.get_command_schema.js +0 -0
  75. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.get_package_scripts.js +0 -0
  76. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.get_package_scripts.test.js +0 -0
  77. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.get_route_map.js +0 -0
  78. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.logs.js +0 -0
  79. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.read_agent_context.js +0 -0
  80. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.read_ci_logs.js +0 -0
  81. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.run_lighthouse.js +0 -0
  82. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.run_playwright.js +0 -0
  83. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/repo.run_tests.js +0 -0
  84. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/dist/tools/types.js +0 -0
  85. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/get_ai_context.py +0 -0
  86. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/handlers/__init__.py +0 -0
  87. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/handlers/command_handler.py +0 -0
  88. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/mcp_server.py +0 -0
  89. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/models.py +0 -0
  90. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/orchestrator.py +0 -0
  91. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/pr_overlap.py +0 -0
  92. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/resources/__init__.py +0 -0
  93. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/resources/build-repo-context.py +0 -0
  94. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/resources/review_template.md +0 -0
  95. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/resources/ux-audit.config.json +0 -0
  96. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/resources/visual_guidelines.json +0 -0
  97. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/review_read_pass.py +0 -0
  98. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/schema_gen.py +0 -0
  99. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/schema_utils.py +0 -0
  100. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/scope_check.py +0 -0
  101. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/__init__.py +0 -0
  102. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/ai_service.py +0 -0
  103. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/context_builder.py +0 -0
  104. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/github.py +0 -0
  105. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/jules.py +0 -0
  106. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/repair_service.py +0 -0
  107. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/vector_store.py +0 -0
  108. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/services/vision_service.py +0 -0
  109. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/td_cli.py +0 -0
  110. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/utils/__init__.py +0 -0
  111. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/utils/git.py +0 -0
  112. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/ux_report.py +0 -0
  113. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/verify_infra.py +0 -0
  114. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/verify_versions.py +0 -0
  115. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/version_utils.py +0 -0
  116. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/__init__.py +0 -0
  117. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/context.py +0 -0
  118. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/graph.py +0 -0
  119. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/node.py +0 -0
  120. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/nodes.py +0 -0
  121. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/dev_tools/workflows/runner.py +0 -0
  122. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/setup.cfg +0 -0
  123. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_config.py +0 -0
  124. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_fail_fast.py +0 -0
  125. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_github_diff_parser.py +0 -0
  126. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_github_no_gh.py +0 -0
  127. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_github_parser.py +0 -0
  128. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_install_workflows.py +0 -0
  129. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_issue_validation_local.py +0 -0
  130. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_jules_batch_send.py +0 -0
  131. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_labels.py +0 -0
  132. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_latency_mitigation.py +0 -0
  133. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_orchestrator_aggregation.py +0 -0
  134. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_orchestrator_heuristics.py +0 -0
  135. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_parse_comment.py +0 -0
  136. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_plan_issue_audit.py +0 -0
  137. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_pr_overlap.py +0 -0
  138. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_review_template_validation.py +0 -0
  139. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_schema_utils.py +0 -0
  140. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_verify_ci_metrics.py +0 -0
  141. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_workflow_audit.py +0 -0
  142. {boomtick_cli-0.4.0 → boomtick_cli-0.5.1}/tests/test_workflow_orchestration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boomtick-cli
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: BoomTick CLI — agentic dev-tools for GitHub + AI workflows
5
5
  Author: Ariel Anders
6
6
  Project-URL: Homepage, https://github.com/arii/boomtick
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boomtick-cli
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: BoomTick CLI — agentic dev-tools for GitHub + AI workflows
5
5
  Author: Ariel Anders
6
6
  Project-URL: Homepage, https://github.com/arii/boomtick
@@ -0,0 +1,5 @@
1
+ {
2
+ "STRICT_JSON_VERIFICATION": "Strict JSON Verification:\n- You MUST self-verify the completeness and validity of the JSON block before finishing your response.\n- Every finding MUST have an `id`, `file`, `issue`, and `status`.\n- Ensure the JSON is well-formed and contained entirely within the `<findings>` tags.\n- Ensure 'snippet' is a unique string from the diff that identifies the issue.",
3
+ "SNIPPET_AND_VERIFICATION_RULES": "Snippet and verification rules:\n- STRICT SNIPPET RULE: When citing an error or anti-pattern, you MUST quote the entire, exact line from the diff in the \"snippet\" field. Do not truncate the line.\n- Before flagging a \"syntax error\" or \"missing property/method\", re-read the diff to confirm the code isn't simply continued on the next line or truncated in the diff chunk. Hallucinating errors due to chunk truncation is a severe failure.\n- If a line appears truncated in the diff (e.g. at the edge of a chunk), DO NOT assume it is a syntax error. Assume it is valid code that continues outside the visible context.",
4
+ "COMMON_REVIEW_GUIDELINES": "Review ONLY PR changes. Assume original code worked.\nEVIDENCE RULE: A blocking concern must point to a concrete contradiction in the diff itself (e.g., wrong types, nonexistent function/class, wrong arity, or failing test). Cite exact lines and explain runtime consequence.\nFALSE POSITIVE FILTER: No speculation. Concerns phrased with speculative hedging language (e.g., \"could\", \"might\", \"unless\", \"if not handled properly\") are NOT blocking. Reviewers must not raise concerns they cannot verify against the code provided; instead, they should state what would be needed to verify, avoiding assumptions of the worst case.\n\nTIERED SCOPE:\n- For App/UI (src/): Flag redundant wrappers. BANNED: Raw Tailwind layout (flex/grid/px-*) in TSX (use Stack/Grid/Box).\n- For Infra/Tooling (scripts/, cli/, .github/): Focus on portability, idempotency, and error handling. Avoid UI-specific feedback for low-level scripts.\n\nREPO RULES: Prefer removal.\nANTI-SLOP: DO NOT recommend overly complex error handling, defensive guards, extra unit tests for simple internal scripts, or boilerplate documentation/comments.\n\n- FILE NECESSITY: Question any added, moved, or removed files that look like temporary artifacts (e.g. .tmp, standalone .py in root, audit-*.md, .json dumps) or seem unrelated to the PR intent. Flag them for removal if they pollute the review context."
5
+ }
@@ -1,4 +1,5 @@
1
- # pylint: disable=missing-docstring,raise-missing-from,subprocess-run-check
1
+ # pylint: disable=missing-docstring,raise-missing-from,subprocess-run-check,too-many-branches,too-many-nested-blocks
2
+ from collections import deque
2
3
  import json
3
4
  import os
4
5
  import subprocess
@@ -7,6 +8,41 @@ from typing import Dict, List, Set
7
8
  from dev_tools.utils import CLIError, log_error, log_warn
8
9
 
9
10
 
11
+ def validate_and_sanitize_graph_data(data: dict) -> dict:
12
+ """Validates and sanitizes the dependency graph data structure."""
13
+ if not isinstance(data, dict):
14
+ raise ValueError("Graph data must be a dictionary")
15
+
16
+ sanitized = {}
17
+ if "modules" in data:
18
+ if not isinstance(data["modules"], list):
19
+ raise ValueError("modules must be a list")
20
+ sanitized["modules"] = []
21
+ for mod in data["modules"]:
22
+ if not isinstance(mod, dict):
23
+ raise ValueError("Module must be a dictionary")
24
+ sanitized_mod = {}
25
+ if "source" in mod:
26
+ if not isinstance(mod["source"], str):
27
+ raise ValueError("source must be a string")
28
+ sanitized_mod["source"] = mod["source"]
29
+ if "dependencies" in mod:
30
+ if not isinstance(mod["dependencies"], list):
31
+ raise ValueError("dependencies must be a list")
32
+ sanitized_mod["dependencies"] = []
33
+ for dep in mod["dependencies"]:
34
+ if not isinstance(dep, dict):
35
+ raise ValueError("Dependency must be a dictionary")
36
+ sanitized_dep = {}
37
+ if "resolved" in dep:
38
+ if not isinstance(dep["resolved"], str):
39
+ raise ValueError("resolved must be a string")
40
+ sanitized_dep["resolved"] = dep["resolved"]
41
+ sanitized_mod["dependencies"].append(sanitized_dep)
42
+ sanitized["modules"].append(sanitized_mod)
43
+ return sanitized
44
+
45
+
10
46
  class DependencyGraph:
11
47
  def __init__(self, root_dir: str = "."):
12
48
  self.root_dir = root_dir
@@ -64,15 +100,22 @@ class DependencyGraph:
64
100
  log_error(f"pnpm or depcruise not found or failed: {e}")
65
101
  raise CLIError("pnpm or depcruise not found. Ensure dependencies are installed.")
66
102
 
67
- if data and data.get("modules"):
68
- # Cache it
103
+ # Validate the parsed data before caching
104
+ validated_data = validate_and_sanitize_graph_data(data)
105
+
106
+ if validated_data and validated_data.get("modules"):
107
+ # Cache the validated data
69
108
  os.makedirs(os.path.dirname(cache_path), exist_ok=True)
70
109
  with open(cache_path, "w", encoding="utf-8") as f:
71
- json.dump(data, f)
72
-
73
- self._parse_modules(data.get("modules", []))
74
- except CLIError:
75
- raise
110
+ json.dump(validated_data, f)
111
+ data = validated_data
112
+
113
+ # Ensure cached data is also validated and sanitized
114
+ validated_data = validate_and_sanitize_graph_data(data)
115
+ self._parse_modules(validated_data.get("modules", []))
116
+ except (CLIError, ValueError) as e:
117
+ log_error(f"Failed loading/validating dependency graph: {e}")
118
+ raise CLIError(f"Dependency graph loading or validation failed: {e}")
76
119
  except Exception as e:
77
120
  log_error(f"loading dependency graph: {e}")
78
121
  self.graph = {}
@@ -104,11 +147,12 @@ class DependencyGraph:
104
147
  def find_affected_files(self, changed_files: List[str], depth: int = 2) -> Set[str]:
105
148
  """Recursively finds files affected by the changes."""
106
149
  affected = set()
107
- queue = [(f, 0) for f in changed_files]
150
+ visited = set(changed_files)
151
+ queue = deque([(f, 0) for f in changed_files])
108
152
 
109
153
  while queue:
110
- file, current_depth = queue.pop(0)
111
- if file in affected or current_depth > depth:
154
+ file, current_depth = queue.popleft()
155
+ if current_depth > depth:
112
156
  continue
113
157
 
114
158
  if current_depth > 0: # Don't add the changed files themselves if we want only 'affected'
@@ -116,7 +160,9 @@ class DependencyGraph:
116
160
 
117
161
  dependents = self.get_dependents(file)
118
162
  for dep in dependents:
119
- queue.append((dep, current_depth + 1))
163
+ if dep not in visited:
164
+ visited.add(dep)
165
+ queue.append((dep, current_depth + 1))
120
166
 
121
167
  return affected
122
168
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "boomtick-cli"
7
- version = "0.4.0"
7
+ version = "0.5.1"
8
8
  description = "BoomTick CLI — agentic dev-tools for GitHub + AI workflows"
9
9
  authors = [{name = "Ariel Anders"}]
10
10
  readme = "README.md"
@@ -0,0 +1,90 @@
1
+ # pylint: disable=missing-docstring,too-many-branches,too-many-nested-blocks,line-too-long
2
+ import json
3
+
4
+ from dev_tools.services.dependency_graph import DependencyGraph, validate_and_sanitize_graph_data
5
+
6
+
7
+ def test_dependency_graph_parsing(tmp_path):
8
+ graph_data = {
9
+ "modules": [
10
+ {"source": "src/a.ts", "dependencies": [{"resolved": "src/b.ts"}]},
11
+ {"source": "src/b.ts", "dependencies": []},
12
+ ]
13
+ }
14
+ validated_data = validate_and_sanitize_graph_data(graph_data)
15
+
16
+ # Create a dummy artifact
17
+ artifact_dir = tmp_path / "artifacts"
18
+ artifact_dir.mkdir()
19
+ graph_file = artifact_dir / "dependency-graph.json"
20
+ graph_file.write_text(json.dumps(validated_data))
21
+
22
+ # Instantiate with tmp_path
23
+ dg = DependencyGraph(root_dir=str(tmp_path))
24
+
25
+ assert dg.get_dependencies("src/a.ts") == ["src/b.ts"]
26
+ assert dg.get_dependents("src/b.ts") == ["src/a.ts"]
27
+
28
+ # Create the file so it exists
29
+ src_dir = tmp_path / "src"
30
+ src_dir.mkdir(exist_ok=True)
31
+ (src_dir / "b.ts").touch()
32
+
33
+ assert dg.get_context_files(["src/a.ts"]) == ["src/b.ts"]
34
+
35
+
36
+ def test_find_affected_files_complex(tmp_path):
37
+ # D depends on C and B, C depends on A, B depends on A
38
+ # Wait, reverse graph: dependents of A are B and C, dependents of B is D, dependents of C is D
39
+ # A is changed. Dependents of A: B, C.
40
+ # Dependents of B: D. Dependents of C: D.
41
+ # Multiple paths to D: A -> B -> D, and A -> C -> D.
42
+ graph_data = {
43
+ "modules": [
44
+ {"source": "src/d.ts", "dependencies": [{"resolved": "src/b.ts"}, {"resolved": "src/c.ts"}]},
45
+ {"source": "src/b.ts", "dependencies": [{"resolved": "src/a.ts"}]},
46
+ {"source": "src/c.ts", "dependencies": [{"resolved": "src/a.ts"}]},
47
+ {"source": "src/a.ts", "dependencies": []},
48
+ ]
49
+ }
50
+ validated_data = validate_and_sanitize_graph_data(graph_data)
51
+
52
+ artifact_dir = tmp_path / "artifacts"
53
+ artifact_dir.mkdir(exist_ok=True)
54
+ graph_file = artifact_dir / "dependency-graph.json"
55
+ graph_file.write_text(json.dumps(validated_data))
56
+
57
+ dg = DependencyGraph(root_dir=str(tmp_path))
58
+
59
+ # A changed, depth=2.
60
+ # Level 1: B, C (depth 1)
61
+ # Level 2: D, D (depth 2) -> if we did not have visited/affected check before push, D would be added twice!
62
+ affected = dg.find_affected_files(["src/a.ts"], depth=2)
63
+ assert affected == {"src/b.ts", "src/c.ts", "src/d.ts"}
64
+
65
+ # With depth=1, only B and C should be affected
66
+ affected_depth_1 = dg.find_affected_files(["src/a.ts"], depth=1)
67
+ assert affected_depth_1 == {"src/b.ts", "src/c.ts"}
68
+
69
+
70
+ def test_find_affected_files_cycle(tmp_path):
71
+ # Cyclic dependencies: A depends on B, B depends on A (so dependents are mutual)
72
+ graph_data = {
73
+ "modules": [
74
+ {"source": "src/a.ts", "dependencies": [{"resolved": "src/b.ts"}]},
75
+ {"source": "src/b.ts", "dependencies": [{"resolved": "src/a.ts"}]},
76
+ ]
77
+ }
78
+ validated_data = validate_and_sanitize_graph_data(graph_data)
79
+
80
+ artifact_dir = tmp_path / "artifacts"
81
+ artifact_dir.mkdir(exist_ok=True)
82
+ graph_file = artifact_dir / "dependency-graph.json"
83
+ graph_file.write_text(json.dumps(validated_data))
84
+
85
+ dg = DependencyGraph(root_dir=str(tmp_path))
86
+
87
+ affected = dg.find_affected_files(["src/a.ts"], depth=2)
88
+ # Changed file itself is excluded from affected since current_depth > 0.
89
+ # Dependents of A: B. Dependents of B: A (but A was in changed_files, hence visited initially, so it won't be traversed again).
90
+ assert affected == {"src/b.ts"}
@@ -1,5 +0,0 @@
1
- {
2
- "STRICT_JSON_VERIFICATION": "Strict JSON Verification:\n- You MUST self-verify the completeness and validity of the JSON block before finishing your response.\n- Every finding MUST have an `id`, `file`, `issue`, and `status`.\n- Ensure the JSON is well-formed and contained entirely within the `<findings>` tags.\n- Ensure 'snippet' is a unique string from the diff that identifies the issue.",
3
- "SNIPPET_AND_VERIFICATION_RULES": "Snippet and verification rules:\n- STRICT SNIPPET RULE: When citing an error or anti-pattern, you MUST quote the entire, exact line from the diff in the \"snippet\" field. Do not truncate the line.\n- Before flagging a \"syntax error\" or \"missing property/method\", re-read the diff to confirm the code isn't simply continued on the next line or truncated in the diff chunk. Hallucinating errors due to chunk truncation is a severe failure.\n- If a line appears truncated in the diff (e.g. at the edge of a chunk), DO NOT assume it is a syntax error. Assume it is valid code that continues outside the visible context.",
4
- "COMMON_REVIEW_GUIDELINES": "Review ONLY PR changes. Assume original code worked.\nEVIDENCE RULE: Issue must point to exact line + explain runtime consequence.\nFALSE POSITIVE FILTER: No speculation. Design choices are NOT bugs.\n\nTIERED SCOPE:\n- For App/UI (src/): Flag redundant wrappers. BANNED: Raw Tailwind layout (flex/grid/px-*) in TSX (use Stack/Grid/Box).\n- For Infra/Tooling (scripts/, cli/, .github/): Focus on portability, idempotency, and error handling. Avoid UI-specific feedback for low-level scripts.\n\nREPO RULES: Prefer removal.\nANTI-SLOP: DO NOT recommend overly complex error handling, defensive guards, extra unit tests for simple internal scripts, or boilerplate documentation/comments.\n\n- FILE NECESSITY: Question any added, moved, or removed files that look like temporary artifacts (e.g. .tmp, standalone .py in root, audit-*.md, .json dumps) or seem unrelated to the PR intent. Flag them for removal if they pollute the review context."
5
- }
@@ -1,32 +0,0 @@
1
- # pylint: disable=missing-docstring
2
- import json
3
-
4
- from dev_tools.services.dependency_graph import DependencyGraph
5
-
6
-
7
- def test_dependency_graph_parsing(tmp_path):
8
- graph_data = {
9
- "modules": [
10
- {"source": "src/a.ts", "dependencies": [{"resolved": "src/b.ts"}]},
11
- {"source": "src/b.ts", "dependencies": []},
12
- ]
13
- }
14
-
15
- # Create a dummy artifact
16
- artifact_dir = tmp_path / "artifacts"
17
- artifact_dir.mkdir()
18
- graph_file = artifact_dir / "dependency-graph.json"
19
- graph_file.write_text(json.dumps(graph_data))
20
-
21
- # Instantiate with tmp_path
22
- dg = DependencyGraph(root_dir=str(tmp_path))
23
-
24
- assert dg.get_dependencies("src/a.ts") == ["src/b.ts"]
25
- assert dg.get_dependents("src/b.ts") == ["src/a.ts"]
26
-
27
- # Create the file so it exists
28
- src_dir = tmp_path / "src"
29
- src_dir.mkdir(exist_ok=True)
30
- (src_dir / "b.ts").touch()
31
-
32
- assert dg.get_context_files(["src/a.ts"]) == ["src/b.ts"]
File without changes
File without changes
File without changes