@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
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/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PR Analyzer
|
|
4
|
+
|
|
5
|
+
Analyzes pull request changes for review complexity, risk assessment,
|
|
6
|
+
and generates review priorities.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python pr_analyzer.py /path/to/repo
|
|
10
|
+
python pr_analyzer.py . --base main --head feature-branch
|
|
11
|
+
python pr_analyzer.py /path/to/repo --json
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# File categories for review prioritization
|
|
25
|
+
FILE_CATEGORIES = {
|
|
26
|
+
"critical": {
|
|
27
|
+
"patterns": [
|
|
28
|
+
r"auth", r"security", r"password", r"token", r"secret",
|
|
29
|
+
r"payment", r"billing", r"crypto", r"encrypt"
|
|
30
|
+
],
|
|
31
|
+
"weight": 5,
|
|
32
|
+
"description": "Security-sensitive files requiring careful review"
|
|
33
|
+
},
|
|
34
|
+
"high": {
|
|
35
|
+
"patterns": [
|
|
36
|
+
r"api", r"database", r"migration", r"schema", r"model",
|
|
37
|
+
r"config", r"env", r"middleware"
|
|
38
|
+
],
|
|
39
|
+
"weight": 4,
|
|
40
|
+
"description": "Core infrastructure files"
|
|
41
|
+
},
|
|
42
|
+
"medium": {
|
|
43
|
+
"patterns": [
|
|
44
|
+
r"service", r"controller", r"handler", r"util", r"helper"
|
|
45
|
+
],
|
|
46
|
+
"weight": 3,
|
|
47
|
+
"description": "Business logic files"
|
|
48
|
+
},
|
|
49
|
+
"low": {
|
|
50
|
+
"patterns": [
|
|
51
|
+
r"test", r"spec", r"mock", r"fixture", r"story",
|
|
52
|
+
r"readme", r"docs", r"\.md$"
|
|
53
|
+
],
|
|
54
|
+
"weight": 1,
|
|
55
|
+
"description": "Tests and documentation"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Risky patterns to flag
|
|
60
|
+
RISK_PATTERNS = [
|
|
61
|
+
{
|
|
62
|
+
"name": "hardcoded_secrets",
|
|
63
|
+
# comments: a committed secret is a secret wherever it sits, including commented-out code.
|
|
64
|
+
"comments": True,
|
|
65
|
+
# pattern is written lowercase and must catch Password:, API_KEY, Token=.
|
|
66
|
+
"ignorecase": True,
|
|
67
|
+
"pattern": r"(password|secret|api_key|token|connection_?string)\s*[=:]\s*['\"][^'\"]+['\"]",
|
|
68
|
+
"severity": "critical",
|
|
69
|
+
"message": "Potential hardcoded secret or connection string detected"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "todo_fixme",
|
|
73
|
+
# comments: TODO/FIXME/HACK markers are written as comments; skipping comments would disable this rule entirely.
|
|
74
|
+
"comments": True,
|
|
75
|
+
# pattern is written uppercase and must catch todo:, Fixme:.
|
|
76
|
+
"ignorecase": True,
|
|
77
|
+
"pattern": r"(TODO|FIXME|HACK|XXX):",
|
|
78
|
+
"severity": "low",
|
|
79
|
+
"message": "TODO/FIXME comment found"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "console_log",
|
|
83
|
+
# comments: a commented-out console.log is not debug output left in production.
|
|
84
|
+
"comments": False,
|
|
85
|
+
# console.log, Debug.WriteLine, System.out.println are exact-case API names.
|
|
86
|
+
"ignorecase": False,
|
|
87
|
+
"pattern": (
|
|
88
|
+
r"console\.(log|debug|info|warn|error)\(|\bDebug\.WriteLine\(|"
|
|
89
|
+
r"\bSystem\.out\.print(?:ln)?\(|\.printStackTrace\("
|
|
90
|
+
),
|
|
91
|
+
"severity": "medium",
|
|
92
|
+
"message": (
|
|
93
|
+
"Debug output statement found "
|
|
94
|
+
"(console.* / Debug.WriteLine / System.out / printStackTrace)"
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "debugger",
|
|
99
|
+
# comments: a commented-out debugger statement is inert.
|
|
100
|
+
"comments": False,
|
|
101
|
+
# the JavaScript `debugger` keyword is lowercase.
|
|
102
|
+
"ignorecase": False,
|
|
103
|
+
"pattern": r"\bdebugger\b",
|
|
104
|
+
"severity": "high",
|
|
105
|
+
"message": "Debugger statement found"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "analyzer_disable",
|
|
109
|
+
# comments: eslint-disable and @SuppressWarnings are comment syntax.
|
|
110
|
+
"comments": True,
|
|
111
|
+
# eslint-disable, [SuppressMessage, @SuppressWarnings are exact-case tokens.
|
|
112
|
+
"ignorecase": False,
|
|
113
|
+
"pattern": (
|
|
114
|
+
r"eslint-disable|#pragma\s+warning\s+disable|\[SuppressMessage|"
|
|
115
|
+
r"@SuppressWarnings"
|
|
116
|
+
),
|
|
117
|
+
"severity": "medium",
|
|
118
|
+
"message": (
|
|
119
|
+
"Static-analyzer rule disabled "
|
|
120
|
+
"(ESLint / Roslyn / SuppressMessage / @SuppressWarnings)"
|
|
121
|
+
)
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "loose_type",
|
|
125
|
+
# comments: a type annotation inside a comment declares nothing.
|
|
126
|
+
"comments": False,
|
|
127
|
+
# the TypeScript `any` and C# `dynamic` keywords are lowercase.
|
|
128
|
+
"ignorecase": False,
|
|
129
|
+
"pattern": r":\s*any\b|\bdynamic\s+\w+\s*[=;]",
|
|
130
|
+
"severity": "medium",
|
|
131
|
+
"message": "Loose type used (TypeScript 'any' or C# 'dynamic')"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "sql_concatenation",
|
|
135
|
+
# comments: commented-out SQL executes nothing.
|
|
136
|
+
"comments": False,
|
|
137
|
+
# SQL is written either case; the pattern spells the keywords uppercase.
|
|
138
|
+
"ignorecase": True,
|
|
139
|
+
"pattern": r"(SELECT|INSERT|UPDATE|DELETE).*\+.*['\"]|(?:FromSql|ExecuteSql)\w*\([^)]*\$\"",
|
|
140
|
+
"severity": "critical",
|
|
141
|
+
"message": "Potential SQL injection (string concatenation or interpolation in query)"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "csharp_unsafe_block",
|
|
145
|
+
# comments: commented-out code compiles nothing.
|
|
146
|
+
"comments": False,
|
|
147
|
+
# C# keywords are lowercase and exact.
|
|
148
|
+
"ignorecase": False,
|
|
149
|
+
"pattern": (
|
|
150
|
+
r"\bunsafe\s+(?:\{|public|private|protected|internal|static|sealed|"
|
|
151
|
+
r"partial|class|struct|void|int|string|long|short|byte|double|float|"
|
|
152
|
+
r"bool|char|ref|out|fixed)\b"
|
|
153
|
+
),
|
|
154
|
+
"severity": "high",
|
|
155
|
+
"message": "C# 'unsafe' code — requires memory-safety review"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "csharp_null_forgiving",
|
|
159
|
+
# comments: commented-out code compiles nothing.
|
|
160
|
+
"comments": False,
|
|
161
|
+
# punctuation only, so case cannot apply.
|
|
162
|
+
"ignorecase": False,
|
|
163
|
+
"pattern": r"(?:\)\s*!\.|\w+!\.\w+)",
|
|
164
|
+
"severity": "medium",
|
|
165
|
+
"message": "Null-forgiving operator (!) used — verify the value is truly non-null"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "csharp_async_void",
|
|
169
|
+
# comments: commented-out code compiles nothing.
|
|
170
|
+
"comments": False,
|
|
171
|
+
# C# keywords are lowercase and exact.
|
|
172
|
+
"ignorecase": False,
|
|
173
|
+
"pattern": r"\basync\s+void\s+\w+\s*\(",
|
|
174
|
+
"severity": "high",
|
|
175
|
+
"message": "'async void' method — use only for event handlers"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "csharp_blocking_async",
|
|
179
|
+
# comments: commented-out code runs nothing — and the comments documenting this rule name .Result, which is how the rule flagged its own documentation.
|
|
180
|
+
"comments": False,
|
|
181
|
+
# `.Result`/`.Wait()`/`.GetAwaiter()` are PascalCase C# members; matching them case-insensitively made JavaScript's `...result` spread a high-severity ASP.NET deadlock finding.
|
|
182
|
+
"ignorecase": False,
|
|
183
|
+
"pattern": r"\.(?:Result\b|Wait\(\)|GetAwaiter\(\)\.GetResult\(\))",
|
|
184
|
+
"severity": "high",
|
|
185
|
+
"message": "Blocking call on async operation — can deadlock in ASP.NET contexts"
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def run_git_command(cmd: List[str], cwd: Path) -> Tuple[bool, str]:
|
|
191
|
+
"""Run a git command and return success status and output."""
|
|
192
|
+
try:
|
|
193
|
+
result = subprocess.run(
|
|
194
|
+
cmd,
|
|
195
|
+
cwd=cwd,
|
|
196
|
+
capture_output=True,
|
|
197
|
+
text=True,
|
|
198
|
+
timeout=30
|
|
199
|
+
)
|
|
200
|
+
return result.returncode == 0, result.stdout.strip()
|
|
201
|
+
except subprocess.TimeoutExpired:
|
|
202
|
+
return False, "Command timed out"
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return False, str(e)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_changed_files(repo_path: Path, base: str, head: str) -> List[Dict]:
|
|
208
|
+
"""Get list of changed files between two refs."""
|
|
209
|
+
success, output = run_git_command(
|
|
210
|
+
["git", "diff", "--name-status", f"{base}...{head}"],
|
|
211
|
+
repo_path
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if not success:
|
|
215
|
+
# Try without the triple dot (for uncommitted changes)
|
|
216
|
+
success, output = run_git_command(
|
|
217
|
+
["git", "diff", "--name-status", base, head],
|
|
218
|
+
repo_path
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if not success or not output:
|
|
222
|
+
# Fall back to staged changes
|
|
223
|
+
success, output = run_git_command(
|
|
224
|
+
["git", "diff", "--name-status", "--cached"],
|
|
225
|
+
repo_path
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
files = []
|
|
229
|
+
for line in output.split("\n"):
|
|
230
|
+
if not line.strip():
|
|
231
|
+
continue
|
|
232
|
+
parts = line.split("\t")
|
|
233
|
+
if len(parts) >= 2:
|
|
234
|
+
status = parts[0][0] # First character of status
|
|
235
|
+
filepath = parts[-1] # Handle renames (R100\told\tnew)
|
|
236
|
+
status_map = {
|
|
237
|
+
"A": "added",
|
|
238
|
+
"M": "modified",
|
|
239
|
+
"D": "deleted",
|
|
240
|
+
"R": "renamed",
|
|
241
|
+
"C": "copied"
|
|
242
|
+
}
|
|
243
|
+
files.append({
|
|
244
|
+
"path": filepath,
|
|
245
|
+
"status": status_map.get(status, "modified")
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
return files
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_file_diff(repo_path: Path, filepath: str, base: str, head: str) -> str:
|
|
252
|
+
"""Get diff content for a specific file."""
|
|
253
|
+
success, output = run_git_command(
|
|
254
|
+
["git", "diff", f"{base}...{head}", "--", filepath],
|
|
255
|
+
repo_path
|
|
256
|
+
)
|
|
257
|
+
if not success:
|
|
258
|
+
success, output = run_git_command(
|
|
259
|
+
["git", "diff", "--cached", "--", filepath],
|
|
260
|
+
repo_path
|
|
261
|
+
)
|
|
262
|
+
return output if success else ""
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def categorize_file(filepath: str) -> Tuple[str, int]:
|
|
266
|
+
"""Categorize a file based on its path and name."""
|
|
267
|
+
filepath_lower = filepath.lower()
|
|
268
|
+
|
|
269
|
+
for category, info in FILE_CATEGORIES.items():
|
|
270
|
+
for pattern in info["patterns"]:
|
|
271
|
+
if re.search(pattern, filepath_lower):
|
|
272
|
+
return category, info["weight"]
|
|
273
|
+
|
|
274
|
+
return "medium", 2 # Default category
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# Case sensitivity is per pattern and has no default: a language-specific pattern matched
|
|
278
|
+
# case-insensitively reports findings it has not earned, and a prose-shaped one matched
|
|
279
|
+
# case-sensitively misses findings it should have. Declaring it once per pattern is the only way
|
|
280
|
+
# both stay right, so an undeclared pattern is refused here rather than silently taking a default.
|
|
281
|
+
def strip_comments(content: str) -> str:
|
|
282
|
+
"""
|
|
283
|
+
Blank comment text, preserving newlines so line-based reasoning still lines up.
|
|
284
|
+
|
|
285
|
+
Most rules here describe things a compiler or runtime acts on, and a commented-out one acts on
|
|
286
|
+
nothing. Scanning comments made this analyser report its own documentation: each comment
|
|
287
|
+
explaining why a rule exists contained the very token that rule matches, so the explanations
|
|
288
|
+
were reported as findings against the file that held them.
|
|
289
|
+
|
|
290
|
+
This docstring deliberately names no rule's literal token. A docstring is a string, not a
|
|
291
|
+
comment, so nothing below strips it — and quoting the tokens here reintroduced two high-severity
|
|
292
|
+
self-matches after the comment fix had removed them. Describing the tokens is enough; the rule
|
|
293
|
+
table below is where they belong.
|
|
294
|
+
|
|
295
|
+
Which rules skip comments is declared per rule below, not decided here. Three of them must read
|
|
296
|
+
comments to work at all — TODO markers, eslint-disable and @SuppressWarnings are comment syntax,
|
|
297
|
+
and a secret is a secret wherever it sits. Stripping globally would have silently disabled them.
|
|
298
|
+
"""
|
|
299
|
+
content = re.sub(r"/\*[\s\S]*?\*/", lambda m: "\n" * m.group(0).count("\n"), content)
|
|
300
|
+
out = []
|
|
301
|
+
for line in content.split("\n"):
|
|
302
|
+
stripped = line.lstrip()
|
|
303
|
+
out.append("" if stripped.startswith(("//", "#", "*")) else re.sub(r"\s(?://|#).*$", "", line))
|
|
304
|
+
return "\n".join(out)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
_undeclared = [r["name"] for r in RISK_PATTERNS if "ignorecase" not in r or "comments" not in r]
|
|
308
|
+
if _undeclared:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"every RISK_PATTERNS entry must declare both 'ignorecase' and 'comments'; missing on: " + ", ".join(_undeclared)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def analyze_diff_for_risks(diff_content: str, filepath: str) -> List[Dict]:
|
|
315
|
+
"""Analyze diff content for risky patterns."""
|
|
316
|
+
risks = []
|
|
317
|
+
|
|
318
|
+
# Only analyze added lines (starting with +)
|
|
319
|
+
added_lines = [
|
|
320
|
+
line[1:] for line in diff_content.split("\n")
|
|
321
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
content = "\n".join(added_lines)
|
|
325
|
+
|
|
326
|
+
code_only = strip_comments(content)
|
|
327
|
+
|
|
328
|
+
for risk in RISK_PATTERNS:
|
|
329
|
+
flags = re.IGNORECASE if risk["ignorecase"] else 0
|
|
330
|
+
haystack = content if risk["comments"] else code_only
|
|
331
|
+
matches = re.findall(risk["pattern"], haystack, flags)
|
|
332
|
+
if matches:
|
|
333
|
+
risks.append({
|
|
334
|
+
"name": risk["name"],
|
|
335
|
+
"severity": risk["severity"],
|
|
336
|
+
"message": risk["message"],
|
|
337
|
+
"file": filepath,
|
|
338
|
+
"count": len(matches)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
return risks
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def count_changes(diff_content: str) -> Dict[str, int]:
|
|
345
|
+
"""Count additions and deletions in diff."""
|
|
346
|
+
additions = 0
|
|
347
|
+
deletions = 0
|
|
348
|
+
|
|
349
|
+
for line in diff_content.split("\n"):
|
|
350
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
351
|
+
additions += 1
|
|
352
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
353
|
+
deletions += 1
|
|
354
|
+
|
|
355
|
+
return {"additions": additions, "deletions": deletions}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def calculate_complexity_score(files: List[Dict], all_risks: List[Dict]) -> int:
|
|
359
|
+
"""Calculate overall PR complexity score (1-10)."""
|
|
360
|
+
score = 0
|
|
361
|
+
|
|
362
|
+
# File count contribution (max 3 points)
|
|
363
|
+
file_count = len(files)
|
|
364
|
+
if file_count > 20:
|
|
365
|
+
score += 3
|
|
366
|
+
elif file_count > 10:
|
|
367
|
+
score += 2
|
|
368
|
+
elif file_count > 5:
|
|
369
|
+
score += 1
|
|
370
|
+
|
|
371
|
+
# Total changes contribution (max 3 points)
|
|
372
|
+
total_changes = sum(f.get("additions", 0) + f.get("deletions", 0) for f in files)
|
|
373
|
+
if total_changes > 500:
|
|
374
|
+
score += 3
|
|
375
|
+
elif total_changes > 200:
|
|
376
|
+
score += 2
|
|
377
|
+
elif total_changes > 50:
|
|
378
|
+
score += 1
|
|
379
|
+
|
|
380
|
+
# Risk severity contribution (max 4 points)
|
|
381
|
+
critical_risks = sum(1 for r in all_risks if r["severity"] == "critical")
|
|
382
|
+
high_risks = sum(1 for r in all_risks if r["severity"] == "high")
|
|
383
|
+
|
|
384
|
+
score += min(2, critical_risks)
|
|
385
|
+
score += min(2, high_risks)
|
|
386
|
+
|
|
387
|
+
return min(10, max(1, score))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def analyze_commit_messages(repo_path: Path, base: str, head: str) -> Dict:
|
|
391
|
+
"""Analyze commit messages in the PR."""
|
|
392
|
+
success, output = run_git_command(
|
|
393
|
+
["git", "log", "--oneline", f"{base}...{head}"],
|
|
394
|
+
repo_path
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if not success or not output:
|
|
398
|
+
return {"commits": 0, "issues": []}
|
|
399
|
+
|
|
400
|
+
commits = output.strip().split("\n")
|
|
401
|
+
issues = []
|
|
402
|
+
|
|
403
|
+
for commit in commits:
|
|
404
|
+
if len(commit) < 10:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
# Check for conventional commit format
|
|
408
|
+
message = commit[8:] if len(commit) > 8 else commit # Skip hash
|
|
409
|
+
|
|
410
|
+
if not re.match(r"^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:", message):
|
|
411
|
+
issues.append({
|
|
412
|
+
"commit": commit[:7],
|
|
413
|
+
"issue": "Does not follow conventional commit format"
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
if len(message) > 72:
|
|
417
|
+
issues.append({
|
|
418
|
+
"commit": commit[:7],
|
|
419
|
+
"issue": "Commit message exceeds 72 characters"
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
"commits": len(commits),
|
|
424
|
+
"issues": issues
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def analyze_pr(
|
|
429
|
+
repo_path: Path,
|
|
430
|
+
base: str = "main",
|
|
431
|
+
head: str = "HEAD"
|
|
432
|
+
) -> Dict:
|
|
433
|
+
"""Perform complete PR analysis."""
|
|
434
|
+
# Get changed files
|
|
435
|
+
changed_files = get_changed_files(repo_path, base, head)
|
|
436
|
+
|
|
437
|
+
if not changed_files:
|
|
438
|
+
return {
|
|
439
|
+
"status": "no_changes",
|
|
440
|
+
"message": "No changes detected between branches"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Analyze each file
|
|
444
|
+
all_risks = []
|
|
445
|
+
file_analyses = []
|
|
446
|
+
|
|
447
|
+
for file_info in changed_files:
|
|
448
|
+
filepath = file_info["path"]
|
|
449
|
+
category, weight = categorize_file(filepath)
|
|
450
|
+
|
|
451
|
+
# Get diff for the file
|
|
452
|
+
diff = get_file_diff(repo_path, filepath, base, head)
|
|
453
|
+
changes = count_changes(diff)
|
|
454
|
+
risks = analyze_diff_for_risks(diff, filepath)
|
|
455
|
+
|
|
456
|
+
all_risks.extend(risks)
|
|
457
|
+
|
|
458
|
+
file_analyses.append({
|
|
459
|
+
"path": filepath,
|
|
460
|
+
"status": file_info["status"],
|
|
461
|
+
"category": category,
|
|
462
|
+
"priority_weight": weight,
|
|
463
|
+
"additions": changes["additions"],
|
|
464
|
+
"deletions": changes["deletions"],
|
|
465
|
+
"risks": risks
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
# Sort by priority (highest first)
|
|
469
|
+
file_analyses.sort(key=lambda x: (-x["priority_weight"], x["path"]))
|
|
470
|
+
|
|
471
|
+
# Analyze commits
|
|
472
|
+
commit_analysis = analyze_commit_messages(repo_path, base, head)
|
|
473
|
+
|
|
474
|
+
# Calculate metrics
|
|
475
|
+
complexity = calculate_complexity_score(file_analyses, all_risks)
|
|
476
|
+
|
|
477
|
+
total_additions = sum(f["additions"] for f in file_analyses)
|
|
478
|
+
total_deletions = sum(f["deletions"] for f in file_analyses)
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
"status": "analyzed",
|
|
482
|
+
"summary": {
|
|
483
|
+
"files_changed": len(file_analyses),
|
|
484
|
+
"total_additions": total_additions,
|
|
485
|
+
"total_deletions": total_deletions,
|
|
486
|
+
"complexity_score": complexity,
|
|
487
|
+
"complexity_label": get_complexity_label(complexity),
|
|
488
|
+
"commits": commit_analysis["commits"]
|
|
489
|
+
},
|
|
490
|
+
"risks": {
|
|
491
|
+
"critical": [r for r in all_risks if r["severity"] == "critical"],
|
|
492
|
+
"high": [r for r in all_risks if r["severity"] == "high"],
|
|
493
|
+
"medium": [r for r in all_risks if r["severity"] == "medium"],
|
|
494
|
+
"low": [r for r in all_risks if r["severity"] == "low"]
|
|
495
|
+
},
|
|
496
|
+
"files": file_analyses,
|
|
497
|
+
"commit_issues": commit_analysis["issues"],
|
|
498
|
+
"review_order": [f["path"] for f in file_analyses[:10]] # Top 10 priority files
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def get_complexity_label(score: int) -> str:
|
|
503
|
+
"""Get human-readable complexity label."""
|
|
504
|
+
if score <= 2:
|
|
505
|
+
return "Simple"
|
|
506
|
+
elif score <= 4:
|
|
507
|
+
return "Moderate"
|
|
508
|
+
elif score <= 6:
|
|
509
|
+
return "Complex"
|
|
510
|
+
elif score <= 8:
|
|
511
|
+
return "Very Complex"
|
|
512
|
+
else:
|
|
513
|
+
return "Critical"
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def print_report(analysis: Dict) -> None:
|
|
517
|
+
"""Print human-readable analysis report."""
|
|
518
|
+
if analysis["status"] == "no_changes":
|
|
519
|
+
print("No changes detected.")
|
|
520
|
+
return
|
|
521
|
+
|
|
522
|
+
summary = analysis["summary"]
|
|
523
|
+
risks = analysis["risks"]
|
|
524
|
+
|
|
525
|
+
print("=" * 60)
|
|
526
|
+
print("PR ANALYSIS REPORT")
|
|
527
|
+
print("=" * 60)
|
|
528
|
+
|
|
529
|
+
print(f"\nComplexity: {summary['complexity_score']}/10 ({summary['complexity_label']})")
|
|
530
|
+
print(f"Files Changed: {summary['files_changed']}")
|
|
531
|
+
print(f"Lines: +{summary['total_additions']} / -{summary['total_deletions']}")
|
|
532
|
+
print(f"Commits: {summary['commits']}")
|
|
533
|
+
|
|
534
|
+
# Risk summary
|
|
535
|
+
print("\n--- RISK SUMMARY ---")
|
|
536
|
+
print(f"Critical: {len(risks['critical'])}")
|
|
537
|
+
print(f"High: {len(risks['high'])}")
|
|
538
|
+
print(f"Medium: {len(risks['medium'])}")
|
|
539
|
+
print(f"Low: {len(risks['low'])}")
|
|
540
|
+
|
|
541
|
+
# Critical and high risks details
|
|
542
|
+
if risks["critical"]:
|
|
543
|
+
print("\n--- CRITICAL RISKS ---")
|
|
544
|
+
for risk in risks["critical"]:
|
|
545
|
+
print(f" [{risk['file']}] {risk['message']} (x{risk['count']})")
|
|
546
|
+
|
|
547
|
+
if risks["high"]:
|
|
548
|
+
print("\n--- HIGH RISKS ---")
|
|
549
|
+
for risk in risks["high"]:
|
|
550
|
+
print(f" [{risk['file']}] {risk['message']} (x{risk['count']})")
|
|
551
|
+
|
|
552
|
+
# Commit message issues
|
|
553
|
+
if analysis["commit_issues"]:
|
|
554
|
+
print("\n--- COMMIT MESSAGE ISSUES ---")
|
|
555
|
+
for issue in analysis["commit_issues"][:5]:
|
|
556
|
+
print(f" {issue['commit']}: {issue['issue']}")
|
|
557
|
+
|
|
558
|
+
# Review order
|
|
559
|
+
print("\n--- SUGGESTED REVIEW ORDER ---")
|
|
560
|
+
for i, filepath in enumerate(analysis["review_order"], 1):
|
|
561
|
+
file_info = next(f for f in analysis["files"] if f["path"] == filepath)
|
|
562
|
+
print(f" {i}. [{file_info['category'].upper()}] {filepath}")
|
|
563
|
+
|
|
564
|
+
print("\n" + "=" * 60)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def main():
|
|
568
|
+
parser = argparse.ArgumentParser(
|
|
569
|
+
description="Analyze pull request for review complexity and risks"
|
|
570
|
+
)
|
|
571
|
+
parser.add_argument(
|
|
572
|
+
"repo_path",
|
|
573
|
+
nargs="?",
|
|
574
|
+
default=".",
|
|
575
|
+
help="Path to git repository (default: current directory)"
|
|
576
|
+
)
|
|
577
|
+
parser.add_argument(
|
|
578
|
+
"--base", "-b",
|
|
579
|
+
default="main",
|
|
580
|
+
help="Base branch for comparison (default: main)"
|
|
581
|
+
)
|
|
582
|
+
parser.add_argument(
|
|
583
|
+
"--head",
|
|
584
|
+
default="HEAD",
|
|
585
|
+
help="Head branch/commit for comparison (default: HEAD)"
|
|
586
|
+
)
|
|
587
|
+
parser.add_argument(
|
|
588
|
+
"--json",
|
|
589
|
+
action="store_true",
|
|
590
|
+
help="Output in JSON format"
|
|
591
|
+
)
|
|
592
|
+
parser.add_argument(
|
|
593
|
+
"--output", "-o",
|
|
594
|
+
help="Write output to file"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
args = parser.parse_args()
|
|
598
|
+
|
|
599
|
+
repo_path = Path(args.repo_path).resolve()
|
|
600
|
+
|
|
601
|
+
if not (repo_path / ".git").exists():
|
|
602
|
+
print(f"Error: {repo_path} is not a git repository", file=sys.stderr)
|
|
603
|
+
sys.exit(1)
|
|
604
|
+
|
|
605
|
+
analysis = analyze_pr(repo_path, args.base, args.head)
|
|
606
|
+
|
|
607
|
+
if args.json:
|
|
608
|
+
output = json.dumps(analysis, indent=2)
|
|
609
|
+
if args.output:
|
|
610
|
+
with open(args.output, "w") as f:
|
|
611
|
+
f.write(output)
|
|
612
|
+
print(f"Results written to {args.output}")
|
|
613
|
+
else:
|
|
614
|
+
print(output)
|
|
615
|
+
else:
|
|
616
|
+
print_report(analysis)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
if __name__ == "__main__":
|
|
620
|
+
main()
|