pi-dev-team 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/PORTING.md +134 -0
- package/README.md +207 -0
- package/UPSTREAM.json +64 -0
- package/agents/Explore.md +15 -0
- package/agents/a11y-review.md +118 -0
- package/agents/adr-author.md +70 -0
- package/agents/ai-provenance-review.md +120 -0
- package/agents/angular-reactivity-review.md +95 -0
- package/agents/arch-review.md +135 -0
- package/agents/architect.md +78 -0
- package/agents/autoship-batch-proposer.md +69 -0
- package/agents/claude-setup-review.md +136 -0
- package/agents/codebase-recon.md +184 -0
- package/agents/component-architecture-review.md +119 -0
- package/agents/concurrency-review.md +109 -0
- package/agents/correctness-review.md +290 -0
- package/agents/data-flow-tracer.md +120 -0
- package/agents/doc-review.md +165 -0
- package/agents/domain-review.md +136 -0
- package/agents/general-purpose.md +10 -0
- package/agents/gherkin-quality-critic.md +113 -0
- package/agents/js-fp-review.md +114 -0
- package/agents/mutation-kill.md +684 -0
- package/agents/naming-review.md +142 -0
- package/agents/orchestrator.md +339 -0
- package/agents/performance-review.md +105 -0
- package/agents/plan-review-acceptance.md +115 -0
- package/agents/plan-review-design.md +90 -0
- package/agents/plan-review-parallelization.md +84 -0
- package/agents/plan-review-strategic.md +96 -0
- package/agents/plan-review-ux.md +110 -0
- package/agents/platform-engineer.md +64 -0
- package/agents/product-manager.md +68 -0
- package/agents/progress-guardian.md +79 -0
- package/agents/qa-engineer.md +289 -0
- package/agents/quality-reviewer.md +132 -0
- package/agents/react-reactivity-review.md +102 -0
- package/agents/refactor-opportunity-review.md +128 -0
- package/agents/security-engineer.md +60 -0
- package/agents/security-review.md +218 -0
- package/agents/session-analysis.md +95 -0
- package/agents/software-engineer.md +105 -0
- package/agents/spec-compliance-review.md +100 -0
- package/agents/spec-reviewer.md +114 -0
- package/agents/structure-review.md +146 -0
- package/agents/tech-writer.md +84 -0
- package/agents/test-review.md +246 -0
- package/agents/test-smell-review.md +188 -0
- package/agents/token-efficiency-review.md +139 -0
- package/agents/ui-ux-designer.md +54 -0
- package/agents/vue-reactivity-review.md +95 -0
- package/bin/__pycache__/claudecpython-314.pyc +0 -0
- package/bin/claude +258 -0
- package/docs/upstream/.pages +1 -0
- package/docs/upstream/CHANGELOG.md +2586 -0
- package/docs/upstream/README.md +155 -0
- package/docs/upstream/agent-architecture.md +214 -0
- package/docs/upstream/agent_info.md +187 -0
- package/docs/upstream/artifact-migration.md +124 -0
- package/docs/upstream/code-intelligence-nudge.md +149 -0
- package/docs/upstream/code-review-process.md +294 -0
- package/docs/upstream/concurrent-use.md +73 -0
- package/docs/upstream/context-management.md +111 -0
- package/docs/upstream/developer-notes.md +280 -0
- package/docs/upstream/diagrams/architecture-overview.svg +101 -0
- package/docs/upstream/diagrams/review-dispatch.svg +139 -0
- package/docs/upstream/diagrams/team-agents.svg +128 -0
- package/docs/upstream/diagrams/test-improve-flow.svg +166 -0
- package/docs/upstream/diagrams/workflow-linear.svg +66 -0
- package/docs/upstream/diagrams/workflow-three-phase.svg +200 -0
- package/docs/upstream/eval-maintenance.md +95 -0
- package/docs/upstream/eval-running-guide.md +147 -0
- package/docs/upstream/eval-system.md +291 -0
- package/docs/upstream/session-review-oss-complements.md +75 -0
- package/docs/upstream/session-review.md +212 -0
- package/docs/upstream/skills.md +188 -0
- package/docs/upstream/team-structure.md +21 -0
- package/docs/upstream/telemetry-ci-access.md +129 -0
- package/docs/upstream/telemetry-repo-security.md +120 -0
- package/docs/upstream/test-evaluation.md +277 -0
- package/docs/upstream/test-improve.md +154 -0
- package/docs/upstream/triage-workflow.md +282 -0
- package/docs/upstream/workflows.md +289 -0
- package/extensions/dev-team/index.ts +539 -0
- package/extensions/dev-team/lib/agents.ts +272 -0
- package/extensions/dev-team/lib/ai-credits.ts +92 -0
- package/extensions/dev-team/lib/autocompact.ts +81 -0
- package/extensions/dev-team/lib/child-run.ts +102 -0
- package/extensions/dev-team/lib/config.ts +236 -0
- package/extensions/dev-team/lib/gh-command.ts +103 -0
- package/extensions/dev-team/lib/github-style.ts +307 -0
- package/extensions/dev-team/lib/hooks.ts +350 -0
- package/extensions/dev-team/lib/metrics.ts +115 -0
- package/extensions/dev-team/lib/safe-read.ts +49 -0
- package/extensions/dev-team/lib/session-files.ts +57 -0
- package/extensions/dev-team/lib/session-spend.ts +123 -0
- package/extensions/dev-team/lib/shell-scan.ts +205 -0
- package/extensions/dev-team/lib/skills.ts +213 -0
- package/extensions/dev-team/lib/subagent-render.ts +245 -0
- package/extensions/dev-team/lib/subagent-types.ts +164 -0
- package/extensions/dev-team/lib/subagent.ts +596 -0
- package/extensions/dev-team/lib/terminal-text.ts +54 -0
- package/extensions/dev-team/lib/tools-misc.ts +152 -0
- package/extensions/dev-team/lib/transcript.ts +110 -0
- package/extensions/dev-team/lib/trust.ts +52 -0
- package/extensions/dev-team/lib/usage-breakdown.ts +176 -0
- package/extensions/dev-team/lib/usage-chart.ts +153 -0
- package/extensions/dev-team/lib/usage-command.ts +107 -0
- package/extensions/dev-team/lib/usage-history.ts +203 -0
- package/extensions/dev-team/lib/usage-render.ts +225 -0
- package/extensions/dev-team/lib/usage-split-bar.ts +127 -0
- package/extensions/dev-team/lib/usage-state.ts +116 -0
- package/extensions/dev-team/lib/usage-text.ts +159 -0
- package/extensions/dev-team/lib/usage-view.ts +109 -0
- package/hooks/__pycache__/refactor_test_freeze_guard.cpython-314.pyc +0 -0
- package/hooks/agent_dispatch_ledger.py +190 -0
- package/hooks/autocompact_setup_nudge.py +99 -0
- package/hooks/bash_retry_guard.py +228 -0
- package/hooks/boundary_events_write_guard.py +352 -0
- package/hooks/code_intelligence_nudge.py +293 -0
- package/hooks/code_intelligence_turn_mark.py +317 -0
- package/hooks/codegraph_bootstrap.py +139 -0
- package/hooks/contract_version_guard.py +362 -0
- package/hooks/cost_meter.py +106 -0
- package/hooks/destructive-commands.json +62 -0
- package/hooks/destructive_guard.py +477 -0
- package/hooks/eval_compliance_check.py +440 -0
- package/hooks/guards.json +17 -0
- package/hooks/hooks.json +323 -0
- package/hooks/internal_double_gate.py +296 -0
- package/hooks/js_fp_review.py +212 -0
- package/hooks/knowledge_index.py +119 -0
- package/hooks/lib/__pycache__/artifact_paths.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/atomic_state.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/autocompact_config.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/boundary_events.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/doc_classification.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/gh_pr_create_detect.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/git_safe_diff.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/instrument_log.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/metrics_query.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/plugin_version.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/pre_commit_doc_classifier.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_agent_registry.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_gate_corroboration.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_gate_hash.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/review_verdicts.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/stdin_json.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/stryker_invocation.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/telemetry_consent.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/test_file_classify.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/token_efficiency_limits.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/verify_guard_state.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/xunit_v3_operator_gate.cpython-314.pyc +0 -0
- package/hooks/lib/agent_skill_hints.py +74 -0
- package/hooks/lib/artifact_paths.py +263 -0
- package/hooks/lib/atomic_state.py +557 -0
- package/hooks/lib/autocompact_config.py +103 -0
- package/hooks/lib/autoship_log.py +106 -0
- package/hooks/lib/banned_scripts_policy.py +51 -0
- package/hooks/lib/boundary_events.py +436 -0
- package/hooks/lib/build_knowledge_index.py +504 -0
- package/hooks/lib/build_skills_index.py +361 -0
- package/hooks/lib/build_state.py +116 -0
- package/hooks/lib/classify_ship_outcome.py +126 -0
- package/hooks/lib/config_changelog_schema.py +115 -0
- package/hooks/lib/cost_meter.py +955 -0
- package/hooks/lib/doc_classification.py +116 -0
- package/hooks/lib/gh_pr_create_detect.py +136 -0
- package/hooks/lib/git_safe_diff.py +123 -0
- package/hooks/lib/instrument_log.py +66 -0
- package/hooks/lib/iteration_journal_gate.py +197 -0
- package/hooks/lib/knowledge_index_paths.py +88 -0
- package/hooks/lib/mcp_json_repowise.py +177 -0
- package/hooks/lib/metrics_query.py +202 -0
- package/hooks/lib/minimal_yaml.py +434 -0
- package/hooks/lib/plugin_version.py +142 -0
- package/hooks/lib/pre_commit_detect.py +537 -0
- package/hooks/lib/pre_commit_doc_classifier.py +126 -0
- package/hooks/lib/pricing.py +118 -0
- package/hooks/lib/report_pdf.py +371 -0
- package/hooks/lib/review_agent_registry.py +142 -0
- package/hooks/lib/review_dispatch_ledger.py +101 -0
- package/hooks/lib/review_gate_corroboration.py +521 -0
- package/hooks/lib/review_gate_hash.py +252 -0
- package/hooks/lib/review_gate_normalized_hash.py +1115 -0
- package/hooks/lib/review_verdicts.py +301 -0
- package/hooks/lib/run_report.py +160 -0
- package/hooks/lib/skill_categories.yaml +125 -0
- package/hooks/lib/stdin_json.py +57 -0
- package/hooks/lib/stryker_invocation.py +102 -0
- package/hooks/lib/telemetry_consent.py +41 -0
- package/hooks/lib/telemetry_report.py +108 -0
- package/hooks/lib/test_file_classify.py +160 -0
- package/hooks/lib/token_efficiency_limits.py +51 -0
- package/hooks/lib/turn_identity.py +77 -0
- package/hooks/lib/verify_guard_state.py +110 -0
- package/hooks/lib/workflow_state.py +206 -0
- package/hooks/lib/xunit_v3_operator_gate.py +596 -0
- package/hooks/mcp_json_repowise_nudge.py +74 -0
- package/hooks/mutation_adapters/__init__.py +7 -0
- package/hooks/mutation_adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/lib.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/mutmut.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/pitest.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/stryker.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/__pycache__/stryker_net.cpython-314.pyc +0 -0
- package/hooks/mutation_adapters/lib.py +478 -0
- package/hooks/mutation_adapters/mutmut.py +188 -0
- package/hooks/mutation_adapters/pitest.py +266 -0
- package/hooks/mutation_adapters/stryker.py +157 -0
- package/hooks/mutation_adapters/stryker_net.py +264 -0
- package/hooks/mutation_gate.py +193 -0
- package/hooks/mutation_testing_smoke_gate.py +371 -0
- package/hooks/pending_review_notify.py +121 -0
- package/hooks/phase_marker.py +138 -0
- package/hooks/post_compact_state_reinject.py +180 -0
- package/hooks/post_format.py +115 -0
- package/hooks/pre_commit_knowledge_index.py +128 -0
- package/hooks/pre_commit_review.py +66 -0
- package/hooks/pre_pr_review.py +694 -0
- package/hooks/pre_tool_guard.py +405 -0
- package/hooks/py.sh +73 -0
- package/hooks/refactor-bash-write-patterns.json +29 -0
- package/hooks/refactor_test_bash_guard.py +253 -0
- package/hooks/refactor_test_freeze_guard.py +139 -0
- package/hooks/refactor_test_revert_guard.py +186 -0
- package/hooks/repo_review_nudge.py +287 -0
- package/hooks/review_verdict_recorder.py +464 -0
- package/hooks/scan_bash_command_for_banned_scripts.py +428 -0
- package/hooks/scan_worktree_for_banned_scripts.py +238 -0
- package/hooks/session_learning_trigger.py +248 -0
- package/hooks/skills_index.py +126 -0
- package/hooks/stryker_xunit_shim_guard.py +571 -0
- package/hooks/subagent_completion_guard.py +309 -0
- package/hooks/subagent_skill_context.py +139 -0
- package/hooks/task_completion_metrics.py +216 -0
- package/hooks/tdd_guard.py +229 -0
- package/hooks/telemetry.py +341 -0
- package/hooks/token_efficiency_review.py +194 -0
- package/hooks/verify_guard.py +183 -0
- package/hooks/verify_guard_edit_marker.py +73 -0
- package/hooks/version_check.py +173 -0
- package/knowledge/accepted-risks-schema.md +98 -0
- package/knowledge/adr-decision-criteria.md +64 -0
- package/knowledge/adversarial-review-protocol.md +139 -0
- package/knowledge/agent-registry.md +228 -0
- package/knowledge/agent-review-methodology.md +80 -0
- package/knowledge/ai-friendly-repo-guidelines.md +67 -0
- package/knowledge/architecture-assessment.md +96 -0
- package/knowledge/artifact-lifecycle.md +57 -0
- package/knowledge/cd-maturity-model.md +82 -0
- package/knowledge/cd-test-architecture.md +190 -0
- package/knowledge/ci-cd-file-scope.md +24 -0
- package/knowledge/codegraph-vs-graphify.md +192 -0
- package/knowledge/component-test-patterns.md +139 -0
- package/knowledge/database-change-management.md +80 -0
- package/knowledge/database-test-patterns.md +79 -0
- package/knowledge/decision-defaults.md +88 -0
- package/knowledge/dependency-breaking-techniques.md +116 -0
- package/knowledge/deployment-pipeline.md +86 -0
- package/knowledge/design-smells.md +122 -0
- package/knowledge/directory-enumeration.md +38 -0
- package/knowledge/domain-modeling.md +123 -0
- package/knowledge/evidence-bundle.md +90 -0
- package/knowledge/exploratory-testing-field-guide.md +122 -0
- package/knowledge/failure-routing.md +28 -0
- package/knowledge/fixture-construction.md +56 -0
- package/knowledge/frontend-component-architecture.md +139 -0
- package/knowledge/gherkin-quality-review-dispatch.md +135 -0
- package/knowledge/index.json +6766 -0
- package/knowledge/internal-collaborator-doubling.md +101 -0
- package/knowledge/legacy-test-strategy.md +71 -0
- package/knowledge/long-run-waiting.md +66 -0
- package/knowledge/microservice-testing.md +71 -0
- package/knowledge/model-pricing.json +23 -0
- package/knowledge/mutation-score-formulas.md +60 -0
- package/knowledge/object-calisthenics.md +147 -0
- package/knowledge/oracle-provenance.md +94 -0
- package/knowledge/orchestrator-script-implementation.md +185 -0
- package/knowledge/owasp-detection.md +148 -0
- package/knowledge/plan-review-rubric.md +56 -0
- package/knowledge/proxy-connectivity.md +62 -0
- package/knowledge/reactive-effect-patterns.md +73 -0
- package/knowledge/recon-inventory-excludes.txt +32 -0
- package/knowledge/references/bdd-value-guide.md +61 -0
- package/knowledge/references/csharp-http-client-testing.md +264 -0
- package/knowledge/release-strategies.md +74 -0
- package/knowledge/report-output-location.md +117 -0
- package/knowledge/report-pdf-integration.md +63 -0
- package/knowledge/report-print.css +129 -0
- package/knowledge/report-template.md +114 -0
- package/knowledge/report-to-pdf.md +69 -0
- package/knowledge/request-processing-flow.md +63 -0
- package/knowledge/result-verification.md +52 -0
- package/knowledge/review-agent-output-contract.md +121 -0
- package/knowledge/review-lens-classification.md +113 -0
- package/knowledge/review-rubric.md +62 -0
- package/knowledge/review-template.md +104 -0
- package/knowledge/rule-fixtures/A02.insecure-random-js/negative.js +1 -0
- package/knowledge/rule-fixtures/A02.insecure-random-js/positive.js +1 -0
- package/knowledge/rule-fixtures/A02.weak-hashing-md5/negative.py +1 -0
- package/knowledge/rule-fixtures/A02.weak-hashing-md5/positive.py +1 -0
- package/knowledge/rule-fixtures/A03.command-injection/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.command-injection/positive.js +1 -0
- package/knowledge/rule-fixtures/A03.sql-injection/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.sql-injection/positive.js +1 -0
- package/knowledge/rule-fixtures/A03.xss-innerhtml/negative.js +1 -0
- package/knowledge/rule-fixtures/A03.xss-innerhtml/positive.js +1 -0
- package/knowledge/rule-fixtures/A05.cors-wildcard/negative.js +1 -0
- package/knowledge/rule-fixtures/A05.cors-wildcard/positive.js +1 -0
- package/knowledge/rule-fixtures/A05.default-credentials/negative.js +1 -0
- package/knowledge/rule-fixtures/A05.default-credentials/positive.js +1 -0
- package/knowledge/rule-fixtures/A07.jwt-alg-none/negative.js +1 -0
- package/knowledge/rule-fixtures/A07.jwt-alg-none/positive.js +1 -0
- package/knowledge/rule-fixtures/A08.binary-formatter/negative.cs +1 -0
- package/knowledge/rule-fixtures/A08.binary-formatter/positive.cs +1 -0
- package/knowledge/rule-fixtures/A08.js-eval/negative.js +1 -0
- package/knowledge/rule-fixtures/A08.js-eval/positive.js +1 -0
- package/knowledge/rule-fixtures/A08.object-input-stream/negative.java +1 -0
- package/knowledge/rule-fixtures/A08.object-input-stream/positive.java +1 -0
- package/knowledge/schemas/disposition-register-v1.json +65 -0
- package/knowledge/schemas/recon-envelope-v1.json +198 -0
- package/knowledge/schemas/unified-finding-v1.json +72 -0
- package/knowledge/security-primitives-contract.md +301 -0
- package/knowledge/security-review-rule-map.yaml +107 -0
- package/knowledge/skills-registry.md +72 -0
- package/knowledge/task-size-classifier.md +103 -0
- package/knowledge/telemetry-schema.md +881 -0
- package/knowledge/test-automation-maturity.md +56 -0
- package/knowledge/test-automation-principles.md +71 -0
- package/knowledge/test-cadence-tradeoffs.md +68 -0
- package/knowledge/test-doubles.md +105 -0
- package/knowledge/test-file-indicators.md +22 -0
- package/knowledge/test-layer-gates.md +35 -0
- package/knowledge/test-matrix-examples/django-batch.md +24 -0
- package/knowledge/test-matrix-examples/dotnet-grpc-fronting-api.md +90 -0
- package/knowledge/test-matrix-examples/dotnet-http-consumer.md +131 -0
- package/knowledge/test-matrix-examples/react-node-spa.md +24 -0
- package/knowledge/test-matrix-examples/spring-boot-service.md +25 -0
- package/knowledge/test-matrix-examples/ssr-htmx.md +24 -0
- package/knowledge/test-organization.md +70 -0
- package/knowledge/test-pyramid.md +84 -0
- package/knowledge/test-refactoring.md +67 -0
- package/knowledge/test-review-division-of-labor.md +85 -0
- package/knowledge/test-smells.md +80 -0
- package/knowledge/test-stack-profiles/bdd-frameworks.md +235 -0
- package/knowledge/test-stack-profiles/django.md +13 -0
- package/knowledge/test-stack-profiles/dotnet.md +18 -0
- package/knowledge/test-stack-profiles/go.md +16 -0
- package/knowledge/test-stack-profiles/node.md +16 -0
- package/knowledge/test-stack-profiles/react.md +12 -0
- package/knowledge/test-stack-profiles/spring-boot.md +16 -0
- package/knowledge/test-stack-profiles/ssr-htmx.md +14 -0
- package/knowledge/test-stack-profiles/vue.md +12 -0
- package/knowledge/test-strategy.md +70 -0
- package/knowledge/testability-patterns.md +240 -0
- package/knowledge/testing-quadrants.md +44 -0
- package/knowledge/testing-techniques/approval.md +15 -0
- package/knowledge/testing-techniques/chaos.md +17 -0
- package/knowledge/testing-techniques/fuzz.md +15 -0
- package/knowledge/testing-techniques/property-based.md +15 -0
- package/knowledge/testing-techniques/schema-validation.md +15 -0
- package/knowledge/testing-techniques/screenshot.md +15 -0
- package/knowledge/three-phase-workflow.md +198 -0
- package/knowledge/value-patterns.md +55 -0
- package/knowledge/verification-mode.md +116 -0
- package/knowledge/virtual-service-libraries.md +75 -0
- package/knowledge/wave-consolidation-guidance.md +21 -0
- package/overrides/agents/Explore.md +15 -0
- package/overrides/agents/general-purpose.md +10 -0
- package/overrides/notes/autoship.md +6 -0
- package/overrides/notes/issues-from-assessment.md +3 -0
- package/overrides/notes/issues-from-plan.md +3 -0
- package/overrides/notes/mutation-night-watch.md +3 -0
- package/overrides/notes/mutation-testing.md +3 -0
- package/overrides/notes/pr.md +7 -0
- package/overrides/notes/project-init.md +6 -0
- package/overrides/notes/setup.md +13 -0
- package/overrides/notes/specs.md +3 -0
- package/overrides/skills/headless-run/SKILL.md +45 -0
- package/overrides/skills/upgrade/SKILL.md +30 -0
- package/overrides/skills/version/SKILL.md +25 -0
- package/package.json +36 -0
- package/scripts/authoring_digest.py +93 -0
- package/scripts/autoship_discover.py +121 -0
- package/scripts/autoship_group.py +409 -0
- package/scripts/autoship_proposals.py +494 -0
- package/scripts/autoship_queue.py +291 -0
- package/scripts/autoship_reclaim.py +495 -0
- package/scripts/build_jobs.py +108 -0
- package/scripts/build_rollback_point.py +240 -0
- package/scripts/build_slice_scope.py +157 -0
- package/scripts/build_wave.py +109 -0
- package/scripts/build_wave_reconcile.py +252 -0
- package/scripts/build_worktree_baseref.py +113 -0
- package/scripts/check_agent_scope.py +117 -0
- package/scripts/check_agent_tool_mapping.py +213 -0
- package/scripts/check_review_agent_mcp_tools.py +317 -0
- package/scripts/check_security_assessment_mcp_tools.py +165 -0
- package/scripts/checkpoint_abort.py +502 -0
- package/scripts/claude_setup_review.py +438 -0
- package/scripts/codebase_recon.py +556 -0
- package/scripts/coverage_config.py +623 -0
- package/scripts/coverage_delta_steering.py +330 -0
- package/scripts/coverage_discovery_dotnet.py +315 -0
- package/scripts/coverage_discovery_java.py +742 -0
- package/scripts/coverage_discovery_js.py +546 -0
- package/scripts/coverage_gap_ranking.py +556 -0
- package/scripts/coverage_readiness.py +455 -0
- package/scripts/coverage_report_parse.py +521 -0
- package/scripts/detect_bdd_convention.py +252 -0
- package/scripts/eval_ablation.py +376 -0
- package/scripts/gherkin_analysis_coverage_gate.py +306 -0
- package/scripts/gherkin_cross_feature_duplicate_titles_gate.py +173 -0
- package/scripts/gherkin_effectiveness_rollup.py +238 -0
- package/scripts/gherkin_failure_path_gate.py +206 -0
- package/scripts/gherkin_feature_merge.py +720 -0
- package/scripts/gherkin_stub_gate.py +163 -0
- package/scripts/gherkin_stub_merge.py +479 -0
- package/scripts/git_origin_host.py +88 -0
- package/scripts/install-java-static-analysis.py +110 -0
- package/scripts/issue_deps.py +74 -0
- package/scripts/lib/_bdd_markers.py +28 -0
- package/scripts/lib/_gherkin_text.py +93 -0
- package/scripts/lib/_vendored_tree.py +70 -0
- package/scripts/lib/autoship_state.py +397 -0
- package/scripts/lib/claude_md_guard.py +226 -0
- package/scripts/lib/deterministic_recon.py +446 -0
- package/scripts/lib/mcp_tool_grants.py +211 -0
- package/scripts/lib/plan_parse.py +386 -0
- package/scripts/lib/review_result.py +84 -0
- package/scripts/lib/review_roster.py +86 -0
- package/scripts/lib/session_log/__init__.py +34 -0
- package/scripts/lib/session_log/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/lib/session_log/__pycache__/records.cpython-314.pyc +0 -0
- package/scripts/lib/session_log/classify.py +231 -0
- package/scripts/lib/session_log/corrections.py +194 -0
- package/scripts/lib/session_log/discovery.py +108 -0
- package/scripts/lib/session_log/records.py +218 -0
- package/scripts/lib/session_log/redact.py +76 -0
- package/scripts/lib/session_log/signals.py +373 -0
- package/scripts/lib/session_report_downstream.py +614 -0
- package/scripts/lib/session_report_maintainer.py +1273 -0
- package/scripts/lib/session_report_shared.py +262 -0
- package/scripts/lib/settings_hook_guard.py +157 -0
- package/scripts/lib/slug.py +33 -0
- package/scripts/lib/stub_extractors/__init__.py +82 -0
- package/scripts/lib/stub_extractors/_common.py +328 -0
- package/scripts/lib/stub_extractors/csharp.py +19 -0
- package/scripts/lib/stub_extractors/go.py +173 -0
- package/scripts/lib/stub_extractors/java.py +18 -0
- package/scripts/lib/stub_extractors/jsts.py +126 -0
- package/scripts/mutation_stack_sections.py +149 -0
- package/scripts/mutation_yield_steering.py +345 -0
- package/scripts/orchestrator.py +895 -0
- package/scripts/plan_gherkin_export.py +227 -0
- package/scripts/plan_waves.py +208 -0
- package/scripts/pr_close_keyword_lint.py +108 -0
- package/scripts/progress_guardian.py +888 -0
- package/scripts/recon_inventory.py +273 -0
- package/scripts/review_findings_log.py +93 -0
- package/scripts/run_invariants.py +124 -0
- package/scripts/select_lenses.py +640 -0
- package/scripts/session_report.py +486 -0
- package/scripts/set_autocompact_env.py +221 -0
- package/scripts/ship_resume_guard.py +135 -0
- package/scripts/ship_review_gate.py +63 -0
- package/scripts/specs_convention_marker.py +103 -0
- package/scripts/test_improve_resume.py +277 -0
- package/scripts/test_review_mechanics.py +958 -0
- package/scripts/token_efficiency_review.py +322 -0
- package/scripts/verdict_scope.py +285 -0
- package/scripts/verify_gherkin_quality_critic_isolation.py +296 -0
- package/scripts/verify_tier.py +157 -0
- package/skills/adr-tools/SKILL.md +118 -0
- package/skills/agent-readiness/SKILL.md +105 -0
- package/skills/agent-readiness/ai_friendly_analyzers.py +326 -0
- package/skills/agent-readiness/scanner.py +441 -0
- package/skills/agent-readiness/scorecard.yaml +88 -0
- package/skills/api-design/SKILL.md +115 -0
- package/skills/apply-fixes/SKILL.md +171 -0
- package/skills/apply-test-doubles/SKILL.md +321 -0
- package/skills/artifact-lifecycle/SKILL.md +127 -0
- package/skills/autoship/SKILL.md +1124 -0
- package/skills/benchmark/SKILL.md +105 -0
- package/skills/branch-workflow/SKILL.md +89 -0
- package/skills/browse/SKILL.md +184 -0
- package/skills/browser-testing/SKILL.md +62 -0
- package/skills/browser-testing/references/playwright-patterns.md +216 -0
- package/skills/build/SKILL.md +422 -0
- package/skills/build/references/static-self-heal.md +245 -0
- package/skills/careful/SKILL.md +72 -0
- package/skills/cd-test-architecture/SKILL.md +371 -0
- package/skills/ci-debugging/SKILL.md +105 -0
- package/skills/co-evolution-audit/SKILL.md +269 -0
- package/skills/code-review/SKILL.md +1015 -0
- package/skills/code-review/examples/aggregated-sample.json +56 -0
- package/skills/code-review/examples/sample-report.md +41 -0
- package/skills/code-review/output-format.md +478 -0
- package/skills/code-review/scripts/activation.py +86 -0
- package/skills/code-review/scripts/change_impact.py +357 -0
- package/skills/code-review/scripts/change_shape.py +372 -0
- package/skills/code-review/scripts/change_size.py +212 -0
- package/skills/code-review/scripts/changed_file_list.py +141 -0
- package/skills/code-review/scripts/closing_pass.py +187 -0
- package/skills/code-review/scripts/consolidate.py +277 -0
- package/skills/code-review/scripts/contract_failure_report.py +185 -0
- package/skills/code-review/scripts/dispatch_reconcile.py +66 -0
- package/skills/code-review/scripts/dispatch_waves.py +164 -0
- package/skills/code-review/scripts/finding_signature.py +446 -0
- package/skills/code-review/scripts/ledger.py +283 -0
- package/skills/code-review/scripts/partition.py +169 -0
- package/skills/code-review/scripts/render_tiered_findings.py +274 -0
- package/skills/code-review/scripts/repo_invariants.py +1066 -0
- package/skills/code-review/scripts/review_context_pack.py +306 -0
- package/skills/code-review/scripts/review_round_log.py +345 -0
- package/skills/code-review/scripts/review_value_coverage.py +297 -0
- package/skills/code-review/scripts/validate_review_output.py +467 -0
- package/skills/code-review/sliced-mode.md +205 -0
- package/skills/competitive-analysis/SKILL.md +191 -0
- package/skills/context-loading-protocol/SKILL.md +157 -0
- package/skills/continue/SKILL.md +90 -0
- package/skills/cost-report/SKILL.md +178 -0
- package/skills/coverage-baseline/SKILL.md +335 -0
- package/skills/coverage-baseline/references/multi-project-discovery.md +202 -0
- package/skills/coverage-delta/SKILL.md +181 -0
- package/skills/coverage-delta/references/mutation-gate.md +70 -0
- package/skills/design-doc/SKILL.md +95 -0
- package/skills/design-interrogation/SKILL.md +89 -0
- package/skills/design-it-twice/SKILL.md +91 -0
- package/skills/docker-image-audit/SKILL.md +108 -0
- package/skills/docker-image-audit/references/install-guide.md +64 -0
- package/skills/docker-image-audit/references/report-template.md +73 -0
- package/skills/docker-image-create/SKILL.md +185 -0
- package/skills/domain-analysis/SKILL.md +183 -0
- package/skills/domain-driven-design/SKILL.md +194 -0
- package/skills/exploratory-testing/SKILL.md +108 -0
- package/skills/explore/SKILL.md +51 -0
- package/skills/farley-score/SKILL.md +165 -0
- package/skills/feature-file-validation/SKILL.md +78 -0
- package/skills/feature-file-validation/references/validation-rules.md +115 -0
- package/skills/feedback-learning/SKILL.md +414 -0
- package/skills/fix/SKILL.md +450 -0
- package/skills/freeze/SKILL.md +68 -0
- package/skills/frontend-architecture/SKILL.md +113 -0
- package/skills/gherkin-derive/SKILL.md +630 -0
- package/skills/gherkin-public/SKILL.md +266 -0
- package/skills/governance-compliance/SKILL.md +150 -0
- package/skills/guard/SKILL.md +75 -0
- package/skills/handoff/SKILL.md +139 -0
- package/skills/handoff/references/summary-templates.md +242 -0
- package/skills/harness-audit/SKILL.md +751 -0
- package/skills/harness-audit/scripts/lesson_validate.py +386 -0
- package/skills/harness-audit/scripts/redundancy_criterion.py +188 -0
- package/skills/headless-run/SKILL.md +45 -0
- package/skills/headless-run/scripts/isolated_dispatch.py +381 -0
- package/skills/help/SKILL.md +72 -0
- package/skills/hexagonal-architecture/SKILL.md +85 -0
- package/skills/human-oversight-protocol/SKILL.md +224 -0
- package/skills/issues-from-assessment/SKILL.md +223 -0
- package/skills/issues-from-plan/SKILL.md +133 -0
- package/skills/legacy-code/SKILL.md +132 -0
- package/skills/mermaid-diagramming/SKILL.md +120 -0
- package/skills/mutation-night-watch/SKILL.md +154 -0
- package/skills/mutation-night-watch/references/scheduling.md +135 -0
- package/skills/mutation-testing/SKILL.md +396 -0
- package/skills/mutation-testing/references/languages/csharp-stryker-net.md +676 -0
- package/skills/mutation-testing/references/languages/go-go-mutesting.md +95 -0
- package/skills/mutation-testing/references/languages/java-pitest.md +77 -0
- package/skills/mutation-testing/references/languages/javascript-stryker.md +188 -0
- package/skills/mutation-testing/references/languages/python-mutmut.md +97 -0
- package/skills/mutation-testing/references/time-estimation.md +34 -0
- package/skills/mutation-testing/references/tool-detection.md +15 -0
- package/skills/mutation-testing/references/workflow-callers.md +23 -0
- package/skills/mutation-testing/scripts/__pycache__/xunit_v3_feature_detector.cpython-314.pyc +0 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_slice_runner.py +635 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_status_loop.py +525 -0
- package/skills/mutation-testing/scripts/csharp_stryker_net_wrapper.py +681 -0
- package/skills/mutation-testing/scripts/mutation_baseline_reuse.py +292 -0
- package/skills/mutation-testing/scripts/mutation_exclude_policy.py +268 -0
- package/skills/mutation-testing/scripts/mutation_feasibility_gate.py +463 -0
- package/skills/mutation-testing/scripts/mutation_kill_headless.py +331 -0
- package/skills/mutation-testing/scripts/mutation_kill_insert.py +199 -0
- package/skills/mutation-testing/scripts/mutation_kill_insert_python.py +150 -0
- package/skills/mutation-testing/scripts/mutation_kill_loop.py +869 -0
- package/skills/mutation-testing/scripts/mutation_kill_loop_python.py +949 -0
- package/skills/mutation-testing/scripts/mutation_kill_retry.py +592 -0
- package/skills/mutation-testing/scripts/mutation_kill_shared.py +620 -0
- package/skills/mutation-testing/scripts/mutation_nightwatch.py +462 -0
- package/skills/mutation-testing/scripts/mutation_nightwatch_stacks.py +425 -0
- package/skills/mutation-testing/scripts/mutation_report.py +743 -0
- package/skills/mutation-testing/scripts/mutation_report_cli.py +175 -0
- package/skills/mutation-testing/scripts/mutation_safety_gate.py +69 -0
- package/skills/mutation-testing/scripts/stryker_shard_pipeline.py +847 -0
- package/skills/mutation-testing/scripts/stryker_shard_setup.py +440 -0
- package/skills/mutation-testing/scripts/stryker_timeout_retry.py +142 -0
- package/skills/mutation-testing/scripts/xunit_v3_feature_detector.py +341 -0
- package/skills/performance-benchmark/SKILL.md +174 -0
- package/skills/performance-benchmark/examples/report-format.md +43 -0
- package/skills/performance-benchmark/references/benchmark-script.md +169 -0
- package/skills/performance-metrics/SKILL.md +265 -0
- package/skills/plan/SKILL.md +199 -0
- package/skills/plan/references/gherkin-persistence.md +43 -0
- package/skills/plan/references/plan-template.md +182 -0
- package/skills/pr/SKILL.md +289 -0
- package/skills/pr/scripts/gate_retry_state.py +368 -0
- package/skills/project-init/README.md +141 -0
- package/skills/project-init/SKILL.md +1197 -0
- package/skills/project-init/evals/evals.json +200 -0
- package/skills/project-init/references/capability-tools.md +55 -0
- package/skills/project-init/references/configs.md +221 -0
- package/skills/property-based-testing/SKILL.md +121 -0
- package/skills/property-based-testing/fixtures/invariant_fixture.py +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/README.md +42 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/LICENSE +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/README.md +263 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.js +12147 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/package.json +3 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/types57/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.js +12011 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/rolldown-runtime-D7D4PA-g.js +13 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/types57/fast-check.d.ts +5165 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/package.json +94 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/LICENSE +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/README.md +168 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/RandomGenerator-DcXj09Ch.d.ts +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.js +38 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.js +18 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.js +22 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.js +134 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/RandomGenerator-DcXj09Ch.d.ts +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.js +37 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.js +17 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.js +21 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.d.ts +15 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.js +133 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.js +44 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.js +90 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.js +80 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.js +78 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/package.json +3 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.d.ts +16 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.d.ts +2 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.js +8 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.d.ts +12 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.js +9 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.js +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.js +46 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.js +92 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.js +82 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.d.ts +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.js +80 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.d.ts +16 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.d.ts +2 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.js +0 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.js +9 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.d.ts +12 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.js +10 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.d.ts +6 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.js +7 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/package.json +133 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/package-lock.json +1179 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/package.json +14 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.js +29 -0
- package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.properties.test.js +16 -0
- package/skills/property-based-testing/fixtures/no_property_fixture.py +10 -0
- package/skills/property-based-testing/fixtures/roundtrip_fixture.py +16 -0
- package/skills/property-based-testing/references/languages/javascript.md +54 -0
- package/skills/property-based-testing/scripts/detect_and_dispatch.py +80 -0
- package/skills/property-based-testing/scripts/hypothesis_scaffold.py +276 -0
- package/skills/proxy-resilience/SKILL.md +84 -0
- package/skills/quality-gate-pipeline/SKILL.md +184 -0
- package/skills/quality-targets-converge/SKILL.md +254 -0
- package/skills/repo-review/SKILL.md +159 -0
- package/skills/report-pdf/SKILL.md +66 -0
- package/skills/review/SKILL.md +47 -0
- package/skills/review-agent/SKILL.md +152 -0
- package/skills/review-summary/SKILL.md +73 -0
- package/skills/run-report/SKILL.md +70 -0
- package/skills/semantic-duplication-scan/SKILL.md +337 -0
- package/skills/semantic-scan/SKILL.md +53 -0
- package/skills/semgrep-analyze/SKILL.md +139 -0
- package/skills/setup/SKILL.md +1122 -0
- package/skills/ship/SKILL.md +240 -0
- package/skills/source-verification/SKILL.md +210 -0
- package/skills/source-verification/scripts/claim_extractor.py +155 -0
- package/skills/specs/.size-baseline.json +4 -0
- package/skills/specs/SKILL.md +243 -0
- package/skills/specs/references/completeness-checklist.md +83 -0
- package/skills/specs/references/extraction.md +58 -0
- package/skills/specs/references/glossary.md +59 -0
- package/skills/specs/references/persistence.md +115 -0
- package/skills/specs/references/predictability-check.md +77 -0
- package/skills/static-analysis-integration/SKILL.md +235 -0
- package/skills/static-analysis-integration/adapters/_envelope.py +26 -0
- package/skills/static-analysis-integration/adapters/jscpd-adapter.py +66 -0
- package/skills/static-analysis-integration/adapters/lizard-adapter.py +81 -0
- package/skills/static-analysis-integration/adapters/mypy-adapter.py +50 -0
- package/skills/static-analysis-integration/adapters/mypy-src-layout.py +93 -0
- package/skills/static-analysis-integration/adapters/security-review-adapter.py +212 -0
- package/skills/static-analysis-integration/maintenance.md +23 -0
- package/skills/static-analysis-integration/references/language-setup.md +228 -0
- package/skills/static-analysis-integration/references/sarif-parser.md +124 -0
- package/skills/static-analysis-integration/references/security-review-adapter.md +118 -0
- package/skills/static-analysis-integration/references/tool-configs.md +617 -0
- package/skills/static-analysis-integration/rulesets/pmd-quickstart.xml +24 -0
- package/skills/stryker-xunit-v2-shim/SKILL.md +274 -0
- package/skills/stryker-xunit-v2-shim/references/shim-howto.md +256 -0
- package/skills/stryker-xunit-v2-shim/scripts/generate_shim.py +143 -0
- package/skills/systematic-debugging/SKILL.md +130 -0
- package/skills/telemetry/SKILL.md +75 -0
- package/skills/test-audit-disable/SKILL.md +129 -0
- package/skills/test-design/SKILL.md +177 -0
- package/skills/test-design/scripts/__pycache__/internal_double_detector.cpython-314.pyc +0 -0
- package/skills/test-design/scripts/internal_double_detector.py +631 -0
- package/skills/test-design-advisor/SKILL.md +166 -0
- package/skills/test-driven-development/SKILL.md +169 -0
- package/skills/test-health/SKILL.md +262 -0
- package/skills/test-improve/SKILL.md +239 -0
- package/skills/test-improve/references/phase-0-approach-contract.md +228 -0
- package/skills/test-improve/references/phase-1-analyze.md +131 -0
- package/skills/test-improve/references/phase-2-baseline.md +121 -0
- package/skills/test-improve/references/phase-3-derive-gherkin.md +53 -0
- package/skills/test-improve/references/phase-4-plan-fixes.md +34 -0
- package/skills/test-improve/references/phase-5-improve.md +215 -0
- package/skills/test-improve/references/phase-6-refactor-decision.md +45 -0
- package/skills/test-improve/references/phase-7-refactor.md +44 -0
- package/skills/test-improve/references/phase-8-validate.md +66 -0
- package/skills/test-improve/references/phase-9-close-out-prompt.md +11 -0
- package/skills/test-improve/references/phase-9-report.md +62 -0
- package/skills/test-improve/references/review-loop.md +92 -0
- package/skills/test-improve/templates/executive-summary.md +123 -0
- package/skills/threat-modeling/SKILL.md +108 -0
- package/skills/triage/SKILL.md +211 -0
- package/skills/ubiquitous-language/SKILL.md +192 -0
- package/skills/ubiquitous-language/scripts/collect_domain_signals.py +300 -0
- package/skills/unfreeze/SKILL.md +37 -0
- package/skills/upgrade/SKILL.md +31 -0
- package/skills/upgrade/scripts/check_version_drift.py +113 -0
- package/skills/upgrade/scripts/enable_autoupdate.py +149 -0
- package/skills/version/SKILL.md +25 -0
- package/sync/__pycache__/sync_upstream.cpython-314.pyc +0 -0
- package/sync/sync_upstream.py +293 -0
- package/templates/ACCEPTED-RISKS.md.tmpl +46 -0
- package/templates/agents/agent-template.md +151 -0
- package/templates/agents/angular-testing.md +66 -0
- package/templates/agents/csharp-quality.md +63 -0
- package/templates/agents/esm-enforcer.md +52 -0
- package/templates/agents/front-end-testing.md +65 -0
- package/templates/agents/go-quality.md +65 -0
- package/templates/agents/python-quality.md +62 -0
- package/templates/agents/react-testing.md +61 -0
- package/templates/agents/ts-enforcer.md +60 -0
- package/templates/agents/twelve-factor-audit.md +49 -0
- package/tools/entropy-check.py +250 -0
- package/tools/model-hash-verify.py +213 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""JSONL record iteration and the ``usage``-block read contract (issue
|
|
2
|
+
#2042, epic #2040).
|
|
3
|
+
|
|
4
|
+
Usage-block contract: today nine modules across this repo read a
|
|
5
|
+
transcript's ``usage`` block with four different idioms —
|
|
6
|
+
|
|
7
|
+
1. ``usage.get(field, 0) or 0`` -> 0 on missing key AND on null
|
|
8
|
+
2. ``usage.get(field) or 0`` -> 0 on missing key AND on null
|
|
9
|
+
3. ``usage.get(field, 0)`` -> 0 on missing key, but a
|
|
10
|
+
PRESENT-and-null value
|
|
11
|
+
survives as `None`
|
|
12
|
+
4. ``usage[field]`` -> raises `KeyError` on a
|
|
13
|
+
missing key
|
|
14
|
+
|
|
15
|
+
``usage_field`` below is idiom 1/2 (they are equivalent) — the idiom already
|
|
16
|
+
used by BOTH extractors' own ``_accumulate_token_signals``/``_cost`` before
|
|
17
|
+
this package existed, so choosing it keeps golden output byte-identical
|
|
18
|
+
(#2042 acceptance criterion).
|
|
19
|
+
|
|
20
|
+
Why this one, not idiom 3 or 4: a real Claude Code transcript legitimately
|
|
21
|
+
omits a usage field two different ways. A `thinking`-only assistant message
|
|
22
|
+
carries no `usage` key at all (the corpus's record #2 in
|
|
23
|
+
`tests/fixtures/session_log/projects/.../99999999….jsonl`). A model turn
|
|
24
|
+
that doesn't support prompt caching has been observed to emit the field
|
|
25
|
+
explicitly as `usage: {"cache_creation_input_tokens": null, ...}` rather
|
|
26
|
+
than omitting the key (the corpus's record #3). Both cases mean the same
|
|
27
|
+
thing — "this field was not populated, count it as zero" — never a
|
|
28
|
+
legitimate non-zero value inferred from its absence. Idiom 3 would leave a
|
|
29
|
+
`None` sitting in an accumulator the moment a null-but-present field is hit,
|
|
30
|
+
raising a `TypeError` the next time arithmetic touches it (`+=`, `/ 1e6`).
|
|
31
|
+
Idiom 4 would abort the WHOLE extraction on the first transcript missing any
|
|
32
|
+
field — observed on real transcripts, not hypothetical. Idiom 1/2 is the
|
|
33
|
+
only one of the four that treats "missing" and "null" identically, which is
|
|
34
|
+
the behavior a `usage` block actually needs.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import json
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
#: The four fields every extractor's token accounting reads from a usage
|
|
43
|
+
#: block, in the order both extractors already declared them.
|
|
44
|
+
USAGE_FIELDS = (
|
|
45
|
+
"input_tokens",
|
|
46
|
+
"output_tokens",
|
|
47
|
+
"cache_creation_input_tokens",
|
|
48
|
+
"cache_read_input_tokens",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def iter_file_records(path: Path):
|
|
53
|
+
"""Yield every decodable JSON record in one transcript file, in order.
|
|
54
|
+
|
|
55
|
+
Streams line by line rather than `read_text().splitlines()`: transcripts
|
|
56
|
+
run to tens of MB and a recursive scan can visit thousands of them, where
|
|
57
|
+
slurping costs ~3x the file's size in peak RSS before yielding anything.
|
|
58
|
+
`ValueError` is caught alongside `OSError` because `UnicodeDecodeError`
|
|
59
|
+
is a `ValueError` — a transcript truncated mid-character by a crashed
|
|
60
|
+
session must not abort the whole run. `extract_session_report.py`'s
|
|
61
|
+
pre-#2042 copy of this function caught only `OSError`; `session_extract.py`'s
|
|
62
|
+
copy already had the wider catch (#1994 review) — this module keeps the
|
|
63
|
+
wider, safer one for both callers."""
|
|
64
|
+
try:
|
|
65
|
+
with path.open(encoding="utf-8", errors="replace") as fh:
|
|
66
|
+
for line in fh:
|
|
67
|
+
line = line.strip()
|
|
68
|
+
if not line:
|
|
69
|
+
continue
|
|
70
|
+
try:
|
|
71
|
+
yield json.loads(line)
|
|
72
|
+
except json.JSONDecodeError:
|
|
73
|
+
continue
|
|
74
|
+
except (OSError, ValueError):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def usage_of(record: dict) -> dict | None:
|
|
79
|
+
"""Resolve one record's usage block: `message.usage` if it's a dict,
|
|
80
|
+
else the record's own top-level `usage` if THAT's a dict, else `None`.
|
|
81
|
+
Both extractors' `extract()` loops used this exact resolution order
|
|
82
|
+
already — moved verbatim, not changed."""
|
|
83
|
+
msg = record.get("message") if isinstance(record.get("message"), dict) else {}
|
|
84
|
+
if isinstance(msg.get("usage"), dict):
|
|
85
|
+
return msg["usage"]
|
|
86
|
+
if isinstance(record.get("usage"), dict):
|
|
87
|
+
return record["usage"]
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def usage_field(usage: dict, field: str) -> int | float:
|
|
92
|
+
"""The one canonical reader for a single usage field. See module
|
|
93
|
+
docstring for the chosen null-handling contract."""
|
|
94
|
+
return usage.get(field, 0) or 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def usage_fields(usage: dict) -> dict[str, int | float]:
|
|
98
|
+
"""Every `USAGE_FIELDS` entry read through `usage_field`, in order."""
|
|
99
|
+
return {f: usage_field(usage, f) for f in USAGE_FIELDS}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def slim_by_name(mapping: dict) -> dict:
|
|
103
|
+
"""Sort a name-keyed dict of Counters into a deterministic, digest-ready
|
|
104
|
+
shape: outer keys sorted, and each inner Counter's keys sorted too.
|
|
105
|
+
Shared by `session_extract.py`'s `by_model`/`by_skill` and
|
|
106
|
+
`extract_session_report.py`'s `by_model` — both built this exact shape
|
|
107
|
+
independently (the former as a dedicated `_slim` helper, the latter
|
|
108
|
+
inline)."""
|
|
109
|
+
return {k: dict(sorted(v.items())) for k, v in sorted(mapping.items())}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# --- issue #2050: sidechain/attribution primitives, made public -----------
|
|
113
|
+
#
|
|
114
|
+
# `hooks/lib/cost_meter.py`, the former context ceiling guard, and
|
|
115
|
+
# `scripts/measure_full_file_duplication.py` each independently read the
|
|
116
|
+
# harness's `isSidechain`/`attributionAgent` fields and (`cost_meter.py`/
|
|
117
|
+
# `measure_full_file_duplication.py`) the Task/Agent-dispatch join. Folded
|
|
118
|
+
# here so `skills/code-review/scripts/repo_invariants.py`'s
|
|
119
|
+
# `check_transcript_parsing_confined_to_session_log` (#2048) has one real
|
|
120
|
+
# home to point at instead of three independent copies.
|
|
121
|
+
|
|
122
|
+
#: Tool names the harness uses for subagent dispatch (both spellings appear
|
|
123
|
+
#: in real transcripts depending on harness version).
|
|
124
|
+
TASK_TOOL_NAMES = ("Task", "Agent")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_sidechain(record: dict) -> bool:
|
|
128
|
+
"""True for a subagent (sidechain) transcript record — the native
|
|
129
|
+
top-level `isSidechain` flag, true on subagent/sidechain turns under
|
|
130
|
+
the older inline-record harness layout."""
|
|
131
|
+
return bool(record.get("isSidechain"))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def attribution_agent_of(record: dict) -> str | None:
|
|
135
|
+
"""The record's own `attributionAgent` value when it's a string
|
|
136
|
+
(possibly empty), else `None`. Deliberately does not filter on
|
|
137
|
+
truthiness here — callers that require a non-empty value check it
|
|
138
|
+
themselves (`agent_type_for` below does; a caller collecting the first
|
|
139
|
+
attribution seen across a file, so it can prefer a later non-empty one,
|
|
140
|
+
does not)."""
|
|
141
|
+
value = record.get("attributionAgent")
|
|
142
|
+
return value if isinstance(value, str) else None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def join_dispatch_agent_ids(
|
|
146
|
+
record: dict, dispatch_types: dict[str, str], agent_types: dict[str, str]
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Fold one record's subagent-dispatch metadata into the two join maps,
|
|
149
|
+
in place.
|
|
150
|
+
|
|
151
|
+
Two harness-recorded halves of the join (#1094):
|
|
152
|
+
* an assistant `tool_use` block named Task/Agent carries
|
|
153
|
+
`input.subagent_type` — keyed here by the block's tool-use id;
|
|
154
|
+
* the paired `tool_result` user record carries top-level
|
|
155
|
+
`toolUseResult.agentId` — completing agentId -> subagent_type.
|
|
156
|
+
|
|
157
|
+
Only the identifiers are read; prompts/descriptions are never touched.
|
|
158
|
+
Previously duplicated independently by `hooks/lib/cost_meter.py`'s
|
|
159
|
+
`_harvest_agent_dispatch` and `scripts/measure_full_file_duplication.py`'s
|
|
160
|
+
`_join_dispatch_agent_ids` — the latter's own docstring already conceded
|
|
161
|
+
the duplication "since that algorithm's own module keeps it private"
|
|
162
|
+
(#2050 makes it public here instead, so both consumers share one
|
|
163
|
+
implementation)."""
|
|
164
|
+
message = record.get("message")
|
|
165
|
+
content = message.get("content") if isinstance(message, dict) else None
|
|
166
|
+
if not isinstance(content, list):
|
|
167
|
+
return
|
|
168
|
+
for block in content:
|
|
169
|
+
if not isinstance(block, dict):
|
|
170
|
+
continue
|
|
171
|
+
block_type = block.get("type")
|
|
172
|
+
if block_type == "tool_use" and block.get("name") in TASK_TOOL_NAMES:
|
|
173
|
+
block_id = block.get("id")
|
|
174
|
+
block_input = block.get("input")
|
|
175
|
+
subagent_type = (
|
|
176
|
+
block_input.get("subagent_type")
|
|
177
|
+
if isinstance(block_input, dict)
|
|
178
|
+
else None
|
|
179
|
+
)
|
|
180
|
+
if (
|
|
181
|
+
isinstance(block_id, str)
|
|
182
|
+
and isinstance(subagent_type, str)
|
|
183
|
+
and subagent_type
|
|
184
|
+
):
|
|
185
|
+
dispatch_types[block_id] = subagent_type
|
|
186
|
+
elif block_type == "tool_result":
|
|
187
|
+
tool_use_id = block.get("tool_use_id")
|
|
188
|
+
tool_use_result = record.get("toolUseResult")
|
|
189
|
+
agent_id = (
|
|
190
|
+
tool_use_result.get("agentId")
|
|
191
|
+
if isinstance(tool_use_result, dict)
|
|
192
|
+
else None
|
|
193
|
+
)
|
|
194
|
+
if (
|
|
195
|
+
isinstance(tool_use_id, str)
|
|
196
|
+
and isinstance(agent_id, str)
|
|
197
|
+
and tool_use_id in dispatch_types
|
|
198
|
+
):
|
|
199
|
+
agent_types[agent_id] = dispatch_types[tool_use_id]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def agent_type_for(record: dict, agent_types: dict[str, str]) -> str:
|
|
203
|
+
"""Agent-type bucket for one usage-bearing record (#1094).
|
|
204
|
+
|
|
205
|
+
`main` for main-loop turns. For sidechain turns: the native
|
|
206
|
+
`attributionAgent` field (primary), else the agentId -> subagent_type
|
|
207
|
+
join built from Task/Agent dispatches via `join_dispatch_agent_ids`
|
|
208
|
+
(fallback), else the honest `unattributed` bucket — never a guess.
|
|
209
|
+
"""
|
|
210
|
+
if not is_sidechain(record):
|
|
211
|
+
return "main"
|
|
212
|
+
attribution_agent = attribution_agent_of(record)
|
|
213
|
+
if attribution_agent:
|
|
214
|
+
return attribution_agent
|
|
215
|
+
agent_id = record.get("agentId")
|
|
216
|
+
if isinstance(agent_id, str) and agent_id in agent_types:
|
|
217
|
+
return agent_types[agent_id]
|
|
218
|
+
return "unattributed"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""The privacy boundary as a function, not a convention (issue #2045, epic
|
|
2
|
+
#2040).
|
|
3
|
+
|
|
4
|
+
Both extractors' own docstrings promise "metrics only — no prompt text,
|
|
5
|
+
code, file contents, or full command strings", and
|
|
6
|
+
``knowledge/telemetry-schema.md`` states the same contract. Before this
|
|
7
|
+
module existed, that promise was enforced only by every call site
|
|
8
|
+
individually remembering to run a name/path through
|
|
9
|
+
``classify.safe_name``/``classify.basename`` — a convention restated at (at
|
|
10
|
+
last count) seventeen call sites across ``scripts/session_extract.py``,
|
|
11
|
+
``plugins/dev-team/scripts/extract_session_report.py``, and
|
|
12
|
+
``session_log/signals.py``. That is exactly the shape that produced this
|
|
13
|
+
epic (#1990/#1991/#1994: the same defect landing independently in both
|
|
14
|
+
forked extractors because nothing forced a shared choke point). This module
|
|
15
|
+
is that choke point: one function, ``redact()``, that every field value
|
|
16
|
+
either extractor writes to its output passes through.
|
|
17
|
+
|
|
18
|
+
``redact()`` does not duplicate ``classify.safe_name``/``classify.basename``
|
|
19
|
+
— it composes them. Those two functions, and their Windows-path/allowlist
|
|
20
|
+
rationale, are unchanged and still individually tested; this module adds
|
|
21
|
+
the privacy-labeled entry point + the two-shape contract below, so a reader
|
|
22
|
+
(and a future call site) reaches for ``redact()`` by name rather than
|
|
23
|
+
re-deriving "should this be safe_name'd, basename'd, or both" from
|
|
24
|
+
scratch.
|
|
25
|
+
|
|
26
|
+
Real defect found while wiring this up (fixed in the same commit, not
|
|
27
|
+
paranoia): ``extract_session_report.py``'s ``_project_label`` used
|
|
28
|
+
``os.path.basename(os.path.normpath(cwd))`` instead of the shared,
|
|
29
|
+
Windows-path-aware ``classify.basename`` — on a POSIX host (where this
|
|
30
|
+
script runs), ``os.path.basename`` splits on ``/`` only, so a Windows-
|
|
31
|
+
authored transcript's backslash-separated ``cwd`` came back whole. It
|
|
32
|
+
did not leak the raw path (``classify.safe_name``'s allowlist has no
|
|
33
|
+
backslash in it, so the value collapsed to ``"other"``), but every such
|
|
34
|
+
project's label lost its real name — this is the exact defect class the
|
|
35
|
+
epic's own "why this is not paranoia" note warns about (``_basename``'s
|
|
36
|
+
Windows-path handling is a privacy fix a hand-port already dropped once),
|
|
37
|
+
found a third time by routing this call site through the shared primitive
|
|
38
|
+
instead of a bespoke one.
|
|
39
|
+
|
|
40
|
+
Stdlib only. See ADR 0014 / ADR 0015.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from . import classify
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def redact(value: str, *, from_path: bool = False) -> str:
|
|
49
|
+
"""The one function every name/label-shaped field value passes through
|
|
50
|
+
before either extractor writes it to its emitted output.
|
|
51
|
+
|
|
52
|
+
- ``from_path=True``: the caller KNOWS ``value`` is a filesystem path (a
|
|
53
|
+
tool's ``file_path`` input, a transcript's ``cwd``) — strip to the
|
|
54
|
+
last path component first (``classify.basename``), then apply the
|
|
55
|
+
strict character allowlist (``classify.safe_name``).
|
|
56
|
+
- ``from_path=False`` (default): apply the strict character allowlist
|
|
57
|
+
directly, with NO path-stripping.
|
|
58
|
+
|
|
59
|
+
Deliberately not "always basename first, regardless of what the value
|
|
60
|
+
is" — a full shell command string like
|
|
61
|
+
``rm -rf /tmp/SENTINEL_CMD_do_not_leak`` would basename down to
|
|
62
|
+
``SENTINEL_CMD_do_not_leak`` (letters/underscores only, no slash), which
|
|
63
|
+
WOULD then pass the allowlist: stripping everything before the last
|
|
64
|
+
``/`` can turn an unsafe string into one that only *looks* safe. The
|
|
65
|
+
two-shape signature keeps that failure mode impossible — a value is
|
|
66
|
+
only ever basenamed when the caller has affirmatively marked it as a
|
|
67
|
+
path.
|
|
68
|
+
|
|
69
|
+
A value that fails the allowlist collapses to ``"other"`` — never
|
|
70
|
+
partial content, never raised. See ``classify.safe_name``'s own
|
|
71
|
+
docstring for the allowlist rationale and ``classify.basename``'s for
|
|
72
|
+
the Windows-path history (#1991/#1994) this composes on top of,
|
|
73
|
+
unchanged."""
|
|
74
|
+
if from_path:
|
|
75
|
+
value = classify.basename(value)
|
|
76
|
+
return classify.safe_name(value)
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Per-record signal accumulation shared by both session-log extractors
|
|
2
|
+
(issue #2044, epic #2040) — the six drifted-furthest accumulator functions
|
|
3
|
+
plus one more that does the same job under a different name (`detect_
|
|
4
|
+
correction_turn`; the issue's own table names 7 functions while its prose
|
|
5
|
+
says "six", the issue is transparent about this discrepancy — the seventh is
|
|
6
|
+
the accuracy signal's "was this a correction turn" classifier, drifted right
|
|
7
|
+
alongside the other six).
|
|
8
|
+
|
|
9
|
+
Unlike session_log.discovery/records/classify (slices #2042/#2043), THIS
|
|
10
|
+
slice is not behavior-preserving on purpose — see the module-level and
|
|
11
|
+
per-function notes below for what changed, in which direction, for which
|
|
12
|
+
extractor, and the historical-comparability consequence. A distilled
|
|
13
|
+
enumeration also lives in this slice's commit body; this docstring is the
|
|
14
|
+
canonical, in-code record of the same decisions.
|
|
15
|
+
|
|
16
|
+
The four signal classes `/session-review`'s contract defines (docstring of
|
|
17
|
+
scripts/session_extract.py, mirrored in
|
|
18
|
+
plugins/dev-team/knowledge/telemetry-schema.md's `session-digest.jsonl`
|
|
19
|
+
section):
|
|
20
|
+
|
|
21
|
+
token per-session / per-skill / per-subagent / per-model token +
|
|
22
|
+
cost, and the cache-hit ratio.
|
|
23
|
+
rework failed edits, repeated file edits, retried bash, repeated
|
|
24
|
+
verify-loop runs, permission denials, compaction events.
|
|
25
|
+
accuracy tool_result is_error counts by tool, failed->retried ratio,
|
|
26
|
+
and user-correction turns.
|
|
27
|
+
utilization which skills/agents were invoked and how often, and which
|
|
28
|
+
registered skills/agents were never observed.
|
|
29
|
+
|
|
30
|
+
## Per-function reconciliation
|
|
31
|
+
|
|
32
|
+
- **accumulate_token_signals** (0.10 similarity). The CORE accumulation —
|
|
33
|
+
sum the 4 usage fields into `tokens_total` and `by_model[model]` — is
|
|
34
|
+
identical in effect between the two copies; kept as the shared function,
|
|
35
|
+
matching `extract_session_report.py`'s existing shape exactly (no cost, no
|
|
36
|
+
skill). Cost computation and per-skill attribution are session_extract.py-
|
|
37
|
+
only EXTENSIONS layered on top by that script's own wrapper (pricing/cost
|
|
38
|
+
stays forked per ADR 0036) — not duplicated here.
|
|
39
|
+
|
|
40
|
+
- **The per-agent CONTEXT_TOKEN bucket (`new_agent_bucket`/
|
|
41
|
+
`merge_agent_buckets`/`finalize_agent_buckets`)** — not one of the 7 named
|
|
42
|
+
functions, but the mechanism #2029 added to `extract_session_report.py`
|
|
43
|
+
ONLY, giving its `by_agent_type` entries real per-agent `context_tokens`/
|
|
44
|
+
`context_per_dispatch` figures. `scripts/session_extract.py` had no
|
|
45
|
+
equivalent — its `by_agent_type` was a bare message-count `Counter`. This
|
|
46
|
+
slice ports the bucket machinery here and switches `session_extract.py`'s
|
|
47
|
+
`by_agent_type` onto it too, so **the maintainer profile gains
|
|
48
|
+
`context_tokens`/`context_per_dispatch`** — the deliberate, expected
|
|
49
|
+
output change issue #2044 calls the "clearest example of why the fork
|
|
50
|
+
costs something."
|
|
51
|
+
|
|
52
|
+
- **accumulate_skill_agent_signals** (0.19 similarity). session_extract.py's
|
|
53
|
+
copy is a strict superset: it also tracks `active` (the #711 sticky
|
|
54
|
+
skill/agent pointer the correction-turn signal attributes against) and
|
|
55
|
+
reads a legacy `attributionSkill` fallback via a `skill` parameter.
|
|
56
|
+
`extract_session_report.py` has neither concern (no by_skill/by_agent
|
|
57
|
+
correction breakdown in its report shape). The superset function is kept
|
|
58
|
+
as canonical; `extract_session_report.py` calls it with `skill=None` and a
|
|
59
|
+
throwaway `active` dict it never reads back — with `skill=None` the
|
|
60
|
+
legacy-fallback branch never fires, so its own `skills_invoked`/
|
|
61
|
+
`agent_dispatches` accumulation is unchanged (verified by golden diff
|
|
62
|
+
absence on that specific field).
|
|
63
|
+
|
|
64
|
+
- **track_tool_call** (0.11 similarity) and **classify_tool_result** (0.50).
|
|
65
|
+
Real difference: session_extract.py guards a `tool_use`/`tool_result`
|
|
66
|
+
block's `id`/`tool_use_id` with `isinstance(..., str)` before using it as
|
|
67
|
+
a dict key; extract_session_report.py's pre-#2044 copies did a bare
|
|
68
|
+
`.get(bid, ...)`/dict assignment, which works for any hashable `bid` but
|
|
69
|
+
raises `TypeError` for an unhashable one (a list or dict — a malformed or
|
|
70
|
+
adversarial transcript field). The safer, guarded form is kept as
|
|
71
|
+
canonical; extract_session_report.py gains the guard. No golden diff: the
|
|
72
|
+
corpus's `tool_use`/`tool_result` blocks all carry string ids.
|
|
73
|
+
|
|
74
|
+
- **track_edit** (0.58) and **track_bash** (0.42), plus the `EDIT_TOOLS`
|
|
75
|
+
constant both need. session_extract.py's copies keyed `verify_edited_since`/
|
|
76
|
+
`last_verify_norm` by `sid` inside a dict that is itself RESET at the top
|
|
77
|
+
of every per-file loop iteration — session-keying inside an already-
|
|
78
|
+
per-file-reset dict is redundant: exactly one `sessionId` ever appears in
|
|
79
|
+
one transcript file (the #1991 bug the sid-keying was originally built to
|
|
80
|
+
prevent — a review panel's siblings sharing their parent's `sessionId` and
|
|
81
|
+
scoring each other's retries — is already fully prevented by the per-file
|
|
82
|
+
reset alone, verified by `tests/repo/test_session_extract_subagents.py::
|
|
83
|
+
test_sibling_agents_running_one_command_are_not_retries`, which stays
|
|
84
|
+
green under this simplification). `extract_session_report.py`'s copies
|
|
85
|
+
already used the simpler flat per-thread dict
|
|
86
|
+
(`{"bash_commands", "last_verify_norm", "edited_since_verify"}`, built by
|
|
87
|
+
`new_thread()` below) with the same per-file-reset discipline. The simpler
|
|
88
|
+
form is kept as canonical (Simplicity First: no observable behavior
|
|
89
|
+
difference on any realistic transcript, confirmed by the golden harness);
|
|
90
|
+
`session_extract.py` drops its `sid`-keyed dicts and adopts `new_thread()`.
|
|
91
|
+
|
|
92
|
+
- **detect_correction_turn** (0.78 — closest of the seven). Logic identical
|
|
93
|
+
between the two copies; moved verbatim.
|
|
94
|
+
|
|
95
|
+
## Historical `session-digest.jsonl` comparability
|
|
96
|
+
|
|
97
|
+
`by_agent_type`'s value shape changes from a bare integer (message count) to
|
|
98
|
+
the same bucket-dict shape `extract_session_report.py` already emitted:
|
|
99
|
+
`{input_tokens, cache_creation_input_tokens, cache_read_input_tokens,
|
|
100
|
+
output_tokens, messages, dispatches, context_tokens, context_per_dispatch}`.
|
|
101
|
+
Any historical `session-digest.jsonl` row is NOT comparable on
|
|
102
|
+
`token.by_agent_type` (and the derived `session-sync` `by_thread` field)
|
|
103
|
+
against a row produced after this change — a consumer doing
|
|
104
|
+
`by_agent_type[x] == N` or arithmetic on the value breaks. This is the same
|
|
105
|
+
shape of jump `session-digest/v2` (#1994) already made once. The formal
|
|
106
|
+
schema-version bump (`session-digest/v3`) and any migration/split-on-schema
|
|
107
|
+
consumer update is explicitly issue #2045's job, not this slice's — this
|
|
108
|
+
slice only flags it, per its own acceptance criteria.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
from __future__ import annotations
|
|
112
|
+
|
|
113
|
+
import re
|
|
114
|
+
from collections import Counter
|
|
115
|
+
|
|
116
|
+
from session_log import classify, redact
|
|
117
|
+
|
|
118
|
+
#: Tools whose `tool_use` counts as an "edit" for rework tracking. Not one
|
|
119
|
+
#: of ADR 0036's 14 classify.py symbols, but tightly coupled to the edit/
|
|
120
|
+
#: bash signal functions below, so it lives here rather than in classify.py.
|
|
121
|
+
EDIT_TOOLS = {"Edit", "Write", "NotebookEdit", "MultiEdit"}
|
|
122
|
+
|
|
123
|
+
#: The usage fields that make up a dispatch's CONTEXT — what it carried in,
|
|
124
|
+
#: as opposed to what it generated. Session telemetry puts ~90% of spend
|
|
125
|
+
#: here (cache read + cache write), which is why per-agent context is the
|
|
126
|
+
#: figure a panel-cost decision needs and `output_tokens` is tracked
|
|
127
|
+
#: separately. Ported verbatim from extract_session_report.py (#2029).
|
|
128
|
+
CONTEXT_TOKEN_FIELDS = (
|
|
129
|
+
"input_tokens",
|
|
130
|
+
"cache_creation_input_tokens",
|
|
131
|
+
"cache_read_input_tokens",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
#: Mirrors `hooks/lib/cost_meter.py`'s `_new_bucket()` shape (#1094) so the
|
|
135
|
+
#: per-agent breakdowns agree on field names across the plugin. They stay
|
|
136
|
+
#: separate implementations — cost_meter runs live per turn, this runs over
|
|
137
|
+
#: a whole transcript tree.
|
|
138
|
+
AGENT_BUCKET_FIELDS = (*CONTEXT_TOKEN_FIELDS, "output_tokens")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def new_agent_bucket() -> dict:
|
|
142
|
+
bucket = {f: 0 for f in AGENT_BUCKET_FIELDS}
|
|
143
|
+
bucket["messages"] = 0
|
|
144
|
+
bucket["dispatches"] = 0
|
|
145
|
+
return bucket
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def merge_agent_buckets(dest: dict, src: dict) -> None:
|
|
149
|
+
"""Fold one project's per-agent buckets into cross-project totals.
|
|
150
|
+
|
|
151
|
+
Re-sums the raw fields rather than the derived ones: adding two
|
|
152
|
+
projects' `context_per_dispatch` values would produce a number that is
|
|
153
|
+
not a mean of anything. The derived figures are recomputed once, after
|
|
154
|
+
the merge."""
|
|
155
|
+
for label, bucket in (src or {}).items():
|
|
156
|
+
if not isinstance(bucket, dict):
|
|
157
|
+
# A digest written before this port (or before #2010 downstream)
|
|
158
|
+
# carries an int (a message count). Merging it as tokens would
|
|
159
|
+
# silently corrupt the total, so the label is preserved at zero
|
|
160
|
+
# rather than guessed at.
|
|
161
|
+
dest.setdefault(label, new_agent_bucket())
|
|
162
|
+
continue
|
|
163
|
+
into = dest.setdefault(label, new_agent_bucket())
|
|
164
|
+
for field in (*AGENT_BUCKET_FIELDS, "messages", "dispatches"):
|
|
165
|
+
into[field] += bucket.get(field, 0) or 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def finalize_agent_buckets(by_agent_type: dict) -> dict:
|
|
169
|
+
"""Add the derived per-dispatch figure each bucket exists to answer.
|
|
170
|
+
|
|
171
|
+
`context_tokens` is the sum a dispatch is charged for carrying;
|
|
172
|
+
`context_per_dispatch` divides it by real dispatch count, which is why
|
|
173
|
+
dispatches are counted from subagent transcripts rather than messages.
|
|
174
|
+
It is `None` for `main` and for any agent with no counted dispatch — a
|
|
175
|
+
division that has no meaning must read as absent, not as 0, which would
|
|
176
|
+
rank a never-dispatched agent as the cheapest in the table."""
|
|
177
|
+
out = {}
|
|
178
|
+
for label, b in sorted(by_agent_type.items()):
|
|
179
|
+
context = sum(b[f] for f in CONTEXT_TOKEN_FIELDS)
|
|
180
|
+
entry = dict(b)
|
|
181
|
+
entry["context_tokens"] = context
|
|
182
|
+
entry["context_per_dispatch"] = (
|
|
183
|
+
round(context / b["dispatches"]) if b["dispatches"] else None
|
|
184
|
+
)
|
|
185
|
+
out[label] = entry
|
|
186
|
+
return out
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def accumulate_token_signals(usage_fields: dict, model, tokens_total, by_model) -> None:
|
|
190
|
+
"""Token-accounting CORE: sum `usage_fields` (already read through
|
|
191
|
+
`session_log.records.usage_fields`) into `tokens_total` and
|
|
192
|
+
`by_model[model]`. No cost, no skill attribution — those stay
|
|
193
|
+
session_extract.py-only extensions (pricing/cost stays forked, ADR
|
|
194
|
+
0036); see this module's docstring."""
|
|
195
|
+
for f, v in usage_fields.items():
|
|
196
|
+
tokens_total[f] += v
|
|
197
|
+
if model:
|
|
198
|
+
by_model[model][f] += v
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def accumulate_skill_agent_signals(
|
|
202
|
+
skill,
|
|
203
|
+
content,
|
|
204
|
+
skills_invoked: Counter,
|
|
205
|
+
agent_dispatches: Counter,
|
|
206
|
+
active: dict[str, str | tuple[str, str] | None],
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Skill/agent-detection concern. `skill` is the legacy attributionSkill
|
|
209
|
+
tag (kept as a fallback — real transcripts don't emit it, #182);
|
|
210
|
+
`content`'s tool_use blocks are the primary signal: the Skill tool and
|
|
211
|
+
the Agent/Task tool that actually invoke them (#182). `active` tracks
|
|
212
|
+
the most-recently-invoked skill/agent (#711), sticky until superseded,
|
|
213
|
+
for the correction-turn concern to attribute against. `active["last"]`
|
|
214
|
+
(#2013) is the same pointer collapsed to a single `(kind, name)` tuple
|
|
215
|
+
(or absent, before any dispatch) -- the ONE most-recently-dispatched
|
|
216
|
+
entity between the two, for `session_log.corrections`' `component`
|
|
217
|
+
field, which needs a single answer rather than two independently-sticky
|
|
218
|
+
ones.
|
|
219
|
+
|
|
220
|
+
Counts DISPATCHES, not runs: a dispatch made from inside a subagent is
|
|
221
|
+
only visible in that subagent's own transcript, and a dispatch whose
|
|
222
|
+
transcript is absent never ran. Run counts come from `attributionAgent`
|
|
223
|
+
(#1994)."""
|
|
224
|
+
if skill:
|
|
225
|
+
skills_invoked[redact.redact(skill)] += 1
|
|
226
|
+
if not isinstance(content, list):
|
|
227
|
+
return
|
|
228
|
+
for block in content:
|
|
229
|
+
if not isinstance(block, dict) or block.get("type") != "tool_use":
|
|
230
|
+
continue
|
|
231
|
+
name = block.get("name", "?")
|
|
232
|
+
inp = block.get("input", {}) if isinstance(block.get("input"), dict) else {}
|
|
233
|
+
if name == "Skill":
|
|
234
|
+
s = inp.get("skill") or inp.get("name")
|
|
235
|
+
if isinstance(s, str) and s:
|
|
236
|
+
active["skill"] = redact.redact(classify.strip_ns(s))
|
|
237
|
+
skills_invoked[active["skill"]] += 1
|
|
238
|
+
active["last"] = ("skill", active["skill"])
|
|
239
|
+
elif name in ("Agent", "Task"):
|
|
240
|
+
a = inp.get("subagent_type")
|
|
241
|
+
if isinstance(a, str) and a:
|
|
242
|
+
active["agent"] = redact.redact(classify.strip_ns(a))
|
|
243
|
+
agent_dispatches[active["agent"]] += 1
|
|
244
|
+
active["last"] = ("agent", active["agent"])
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def track_tool_call(block: dict, pending_tool: dict[str, str], tool_calls: Counter) -> None:
|
|
248
|
+
"""Error-classification bookkeeping: count every tool invocation (the
|
|
249
|
+
error-rate denominator) and remember its id -> name so a later
|
|
250
|
+
tool_result can be attributed back to the tool that produced it."""
|
|
251
|
+
name = redact.redact(str(block.get("name", "?")))
|
|
252
|
+
tool_calls[name] += 1
|
|
253
|
+
bid = block.get("id")
|
|
254
|
+
if isinstance(bid, str) and bid:
|
|
255
|
+
pending_tool[bid] = name
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def classify_tool_result(
|
|
259
|
+
block: dict,
|
|
260
|
+
pending_tool: dict[str, str],
|
|
261
|
+
tool_errors: Counter,
|
|
262
|
+
error_counts: Counter,
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Error-classification concern: tally errors by tool, and detect the
|
|
265
|
+
two rework sub-signals (failed edits via old_string mismatches, and
|
|
266
|
+
permission denials) from a tool_result block."""
|
|
267
|
+
if not block.get("is_error"):
|
|
268
|
+
return
|
|
269
|
+
bid = block.get("tool_use_id")
|
|
270
|
+
tool_name = pending_tool.get(bid, "?") if isinstance(bid, str) else "?"
|
|
271
|
+
tool_errors[tool_name] += 1
|
|
272
|
+
rcontent = classify.text_of(block.get("content"))
|
|
273
|
+
if tool_name in EDIT_TOOLS and classify.OLDSTRING_RE.search(rcontent):
|
|
274
|
+
error_counts["failed_edits"] += 1
|
|
275
|
+
if classify.PERMISSION_RE.search(rcontent):
|
|
276
|
+
error_counts["permission_denials"] += 1
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def new_thread() -> dict:
|
|
280
|
+
"""Per-transcript-file state for `track_edit`/`track_bash`. One
|
|
281
|
+
transcript file is one thread of execution: a main-thread session, or a
|
|
282
|
+
single dispatched agent's run. Reset at the top of each file's
|
|
283
|
+
processing loop."""
|
|
284
|
+
return {"bash_commands": Counter(), "last_verify_norm": None, "edited_since_verify": False}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def track_edit(block: dict, edits_per_file: Counter, thread: dict) -> None:
|
|
288
|
+
"""Edit-tracking concern: count Edit/Write/... calls per file basename,
|
|
289
|
+
so repeated edits to the same file (a rework signal) can be derived.
|
|
290
|
+
Also marks this thread's pending stuck-verify-loop streak (#708) as
|
|
291
|
+
consumed — an edit resets it, same as verify_guard.py's own reset."""
|
|
292
|
+
name = block.get("name", "?")
|
|
293
|
+
inp = block.get("input", {}) if isinstance(block.get("input"), dict) else {}
|
|
294
|
+
if name in EDIT_TOOLS and inp.get("file_path"):
|
|
295
|
+
edits_per_file[redact.redact(str(inp["file_path"]), from_path=True)] += 1
|
|
296
|
+
if name in EDIT_TOOLS:
|
|
297
|
+
thread["edited_since_verify"] = True
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def track_bash(
|
|
301
|
+
block: dict,
|
|
302
|
+
bash_signal_counts: Counter,
|
|
303
|
+
thread: dict,
|
|
304
|
+
*,
|
|
305
|
+
active: dict | None = None,
|
|
306
|
+
retried_by_skill: Counter | None = None,
|
|
307
|
+
retried_by_agent: Counter | None = None,
|
|
308
|
+
) -> None:
|
|
309
|
+
"""Bash-retry / commit-bypass / stuck-verify-loop concern (#111, #708):
|
|
310
|
+
normalize the command for near-identical retry detection, detect a
|
|
311
|
+
stuck-verify-loop repeat (the same normalized verify command run again
|
|
312
|
+
with no Edit/Write/... call since the previous run in this thread), and
|
|
313
|
+
detect the review-gate bypass signal on `git commit` invocations.
|
|
314
|
+
|
|
315
|
+
Bash signals are scoped to ONE thread of execution (`thread`, a
|
|
316
|
+
per-transcript-file dict). Retries and repeated verify runs are only
|
|
317
|
+
meaningful within a thread: a review panel's sibling agents share their
|
|
318
|
+
parent's sessionId, so a session-keyed tally would score fifteen agents
|
|
319
|
+
each running `git diff --cached` once as fourteen retries.
|
|
320
|
+
|
|
321
|
+
`active`/`retried_by_skill`/`retried_by_agent` (#2110) attribute each
|
|
322
|
+
retry, at the moment it's detected, to whichever skill/agent is the
|
|
323
|
+
thread's current sticky pointer (`active["skill"]`/`active["agent"]`,
|
|
324
|
+
the same pointer `accumulate_skill_agent_signals` maintains and the
|
|
325
|
+
correction-turn signal already attributes against) — "unattributed"
|
|
326
|
+
when neither is set. All three are optional and default to no
|
|
327
|
+
attribution, so a caller with no `active` pointer to offer (there is
|
|
328
|
+
none today) gets the prior behavior unchanged."""
|
|
329
|
+
name = block.get("name", "?")
|
|
330
|
+
inp = block.get("input", {}) if isinstance(block.get("input"), dict) else {}
|
|
331
|
+
if name != "Bash" or not isinstance(inp.get("command"), str):
|
|
332
|
+
return
|
|
333
|
+
cmd = inp["command"].strip()
|
|
334
|
+
# near-identical retry detection: normalize whitespace
|
|
335
|
+
norm = re.sub(r"\s+", " ", cmd)
|
|
336
|
+
if active is not None and thread["bash_commands"][norm] >= 1:
|
|
337
|
+
# This exact normalized command already ran once in this thread —
|
|
338
|
+
# this call is a retry, attributed to whichever skill/agent is
|
|
339
|
+
# active right now.
|
|
340
|
+
if retried_by_skill is not None:
|
|
341
|
+
retried_by_skill[active.get("skill") or "unattributed"] += 1
|
|
342
|
+
if retried_by_agent is not None:
|
|
343
|
+
retried_by_agent[active.get("agent") or "unattributed"] += 1
|
|
344
|
+
thread["bash_commands"][norm] += 1
|
|
345
|
+
if classify.VERIFY_RE.search(cmd):
|
|
346
|
+
if thread["last_verify_norm"] == norm and not thread["edited_since_verify"]:
|
|
347
|
+
bash_signal_counts["repeated_verify_runs"] += 1
|
|
348
|
+
thread["last_verify_norm"] = norm
|
|
349
|
+
thread["edited_since_verify"] = False
|
|
350
|
+
# gate signal (#111): commit + review-gate bypass, scoped to the
|
|
351
|
+
# git-commit argv (#2036) — see classify.bash_segments()/is_git_commit_argv().
|
|
352
|
+
for segment in classify.bash_segments(cmd):
|
|
353
|
+
if classify.is_git_commit_argv(segment):
|
|
354
|
+
bash_signal_counts["commit_attempts"] += 1
|
|
355
|
+
if any(tok in classify.COMMIT_BYPASS_TOKENS for tok in segment[1:]):
|
|
356
|
+
bash_signal_counts["commit_bypasses"] += 1
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def detect_correction_turn(rec: dict, content) -> bool:
|
|
360
|
+
"""Correction-turn concern: a real user message (not a tool_result
|
|
361
|
+
envelope) containing a correction keyword ("no", "actually", "revert",
|
|
362
|
+
...)."""
|
|
363
|
+
if rec.get("type") != "user" or rec.get("isMeta"):
|
|
364
|
+
return False
|
|
365
|
+
utext = classify.text_of(content)
|
|
366
|
+
if not utext:
|
|
367
|
+
return False
|
|
368
|
+
# skip pure tool_result envelopes (no free-text user prompt)
|
|
369
|
+
if isinstance(content, list) and all(
|
|
370
|
+
isinstance(b, dict) and b.get("type") == "tool_result" for b in content
|
|
371
|
+
):
|
|
372
|
+
return False
|
|
373
|
+
return bool(classify.CORRECTION_RE.search(utext.lower()))
|