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,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/dev-team usage [session|month]`: opens the usage overlay, or prints the plain-text summary
|
|
3
|
+
* when no overlay can be shown. The overlay is out of reach without a UI (print mode) and in RPC mode,
|
|
4
|
+
* where `ui.custom()` is a stub that resolves without ever calling the factory, so "the factory never
|
|
5
|
+
* ran" is the signal to fall back to text. A `ui.custom()` that rejects falls back to text too.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { type SpendRun, sessionEntries, sessionSpend } from "./session-spend.ts";
|
|
9
|
+
import { monthSnapshot, usageBreakdown } from "./usage-breakdown.ts";
|
|
10
|
+
import { loadSpendHistory, sessionRoot } from "./usage-history.ts";
|
|
11
|
+
import { usageMonthStart } from "./ai-credits.ts";
|
|
12
|
+
import type { UsageStyle } from "./usage-render.ts";
|
|
13
|
+
import { openUsage, type Scope, type Transition } from "./usage-state.ts";
|
|
14
|
+
import { errorReason, loadFailedMessage, usageSummary } from "./usage-text.ts";
|
|
15
|
+
import { OVERLAY_HEIGHT_PERCENT, UsageView } from "./usage-view.ts";
|
|
16
|
+
|
|
17
|
+
export interface UsageDeps {
|
|
18
|
+
now(): Date;
|
|
19
|
+
loadHistory: typeof loadSpendHistory;
|
|
20
|
+
/** Shows text to the user (a notification with a UI, stdout without). */
|
|
21
|
+
emit(text: string): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const USAGE_SYNTAX = "Usage: /dev-team usage [session|month]";
|
|
25
|
+
|
|
26
|
+
const SCOPE_ARGS: ReadonlyMap<string, Scope> = new Map([
|
|
27
|
+
["", "session"],
|
|
28
|
+
["session", "session"],
|
|
29
|
+
["month", "month"],
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** The scope `/dev-team usage <args>` asks for (this session when none is given), or the usage message for an argument it does not know. */
|
|
33
|
+
export function parseUsageArgs(args: string): { scope: Scope } | { error: string } {
|
|
34
|
+
const scope = SCOPE_ARGS.get(args.trim().toLowerCase());
|
|
35
|
+
return scope ? { scope } : { error: USAGE_SYNTAX };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sessionRunsOf = (ctx: ExtensionContext): SpendRun[] => [...sessionSpend(sessionEntries(ctx))];
|
|
39
|
+
const sessionRootOf = (ctx: ExtensionContext): string => sessionRoot(ctx.sessionManager.getSessionDir());
|
|
40
|
+
|
|
41
|
+
/** A distinct colour per split segment slot, one per glyph in SPLIT_GLYPHS (usage-split-bar.ts), which tell the slots apart without colour. */
|
|
42
|
+
const SEGMENT_COLORS = ["accent", "success", "warning", "muted"] as const;
|
|
43
|
+
|
|
44
|
+
/** pi theme tokens for the overlay: accent bars, a distinct colour per split segment, muted secondary text. */
|
|
45
|
+
function themeStyle(theme: Theme): UsageStyle {
|
|
46
|
+
return {
|
|
47
|
+
title: (text) => theme.bold(text),
|
|
48
|
+
error: (text) => theme.fg("error", text),
|
|
49
|
+
bar: (text) => theme.fg("accent", text),
|
|
50
|
+
muted: (text) => theme.fg("muted", text),
|
|
51
|
+
segment: (slot, text) => theme.fg(SEGMENT_COLORS[slot] ?? "muted", text),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The text summary: this month is read from every saved session (no progress to show), a failed read is reported, never thrown. */
|
|
56
|
+
async function printSummary(ctx: ExtensionContext, scope: Scope, deps: UsageDeps): Promise<void> {
|
|
57
|
+
const now = deps.now();
|
|
58
|
+
if (scope === "session") {
|
|
59
|
+
deps.emit(usageSummary({ scope, breakdown: usageBreakdown(sessionRunsOf(ctx)), now }));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const history = await deps.loadHistory({ root: sessionRootOf(ctx), since: usageMonthStart(now) });
|
|
64
|
+
const snapshot = monthSnapshot(history, deps.now());
|
|
65
|
+
deps.emit(usageSummary({ scope, breakdown: snapshot.breakdown, now, month: snapshot }));
|
|
66
|
+
} catch (err) {
|
|
67
|
+
deps.emit(loadFailedMessage(errorReason(err)));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Shows the overlay; false when it could not be shown (the factory never ran, or `ui.custom()` rejected). */
|
|
72
|
+
async function showOverlay(ctx: ExtensionContext, initial: Transition, deps: UsageDeps): Promise<boolean> {
|
|
73
|
+
let factoryRan = false;
|
|
74
|
+
try {
|
|
75
|
+
await ctx.ui.custom<void>(
|
|
76
|
+
(tui, theme, _keybindings, done) => {
|
|
77
|
+
factoryRan = true;
|
|
78
|
+
return new UsageView(
|
|
79
|
+
{
|
|
80
|
+
sessionRuns: () => sessionRunsOf(ctx),
|
|
81
|
+
now: deps.now,
|
|
82
|
+
terminalRows: () => tui.terminal.rows,
|
|
83
|
+
style: themeStyle(theme),
|
|
84
|
+
requestRender: () => tui.requestRender(),
|
|
85
|
+
close: () => done(),
|
|
86
|
+
loadHistory: (options) => deps.loadHistory({ root: sessionRootOf(ctx), ...options }),
|
|
87
|
+
},
|
|
88
|
+
initial,
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
{ overlay: true, overlayOptions: { maxHeight: `${OVERLAY_HEIGHT_PERCENT}%` } },
|
|
92
|
+
);
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return factoryRan;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function runUsage(ctx: ExtensionContext, args: string, deps: UsageDeps): Promise<void> {
|
|
100
|
+
const parsed = parseUsageArgs(args);
|
|
101
|
+
if ("error" in parsed) {
|
|
102
|
+
deps.emit(parsed.error);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (ctx.hasUI && (await showOverlay(ctx, openUsage(parsed.scope), deps))) return;
|
|
106
|
+
await printSummary(ctx, parsed.scope, deps);
|
|
107
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timestamped runs of spend loaded from every saved pi session file, for the "This month" scope of
|
|
3
|
+
* /dev-team usage. All providers are loaded; grouping them is usage-breakdown's job.
|
|
4
|
+
* Reads are stat-first and async so a large session history does not stall the TUI. Spend is
|
|
5
|
+
* classified by sessionSpendByEntry() (session-spend.ts), the same walk the status line uses, so both
|
|
6
|
+
* views agree on what a run is.
|
|
7
|
+
*/
|
|
8
|
+
import { createReadStream, type Dirent } from "node:fs";
|
|
9
|
+
import { readdir, stat } from "node:fs/promises";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
import { sessionSpendByEntry, type SpendRun } from "./session-spend.ts";
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/** One run of spend, with when its session entry was written (the entry's ISO 8601 `timestamp`, as stored). */
|
|
16
|
+
export interface SpendRecord {
|
|
17
|
+
timestamp: string;
|
|
18
|
+
run: SpendRun;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SpendHistory {
|
|
22
|
+
records: SpendRecord[];
|
|
23
|
+
/** Session files that could not be read. Files skipped for being too old, or gone before they were read, are not counted. */
|
|
24
|
+
unreadable: number;
|
|
25
|
+
/** True when the load was cancelled; `records` then hold only what was read before that. */
|
|
26
|
+
aborted: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** pi names a project's session folder `--<encoded cwd>--`. */
|
|
30
|
+
const PROJECT_DIR = /^--.+--$/;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The folder every project's sessions live under: the parent of a session's own `--<cwd>--` folder,
|
|
34
|
+
* or the folder itself when a custom session dir keeps its files flat.
|
|
35
|
+
*/
|
|
36
|
+
export function sessionRoot(sessionDir: string): string {
|
|
37
|
+
return PROJECT_DIR.test(path.basename(sessionDir)) ? path.dirname(sessionDir) : sessionDir;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const SESSION_FILE_EXTENSION = ".jsonl";
|
|
41
|
+
|
|
42
|
+
const isNotFound = (err: unknown): boolean => (err as { code?: string } | undefined)?.code === "ENOENT";
|
|
43
|
+
|
|
44
|
+
/** By code unit, so the order that decides which copy of a shared entry wins is the same on every machine and locale. */
|
|
45
|
+
const sortedByName = (dirents: readonly Dirent[]): Dirent[] => [...dirents].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
46
|
+
|
|
47
|
+
/** The session files among a folder's dirents, in name order. */
|
|
48
|
+
const sessionFilesIn = (dir: string, dirents: readonly Dirent[]): string[] =>
|
|
49
|
+
sortedByName(dirents).filter((dirent) => dirent.isFile() && dirent.name.endsWith(SESSION_FILE_EXTENSION)).map((dirent) => path.join(dir, dirent.name));
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Session files directly under the root (`<root>/*.jsonl`, a flat custom session dir) or one folder
|
|
53
|
+
* down (`<root>/<project>/*.jsonl`, pi's default layout), and nothing deeper: another extension keeps
|
|
54
|
+
* its own `<session>/<hash>/run-N/session.jsonl` transcripts under the same root, and those are not
|
|
55
|
+
* this session history. A missing root is no history; any other failure to list it is thrown. A project
|
|
56
|
+
* folder that cannot be listed counts as one unreadable file, since its sessions are unknown.
|
|
57
|
+
*/
|
|
58
|
+
async function listSessionFiles(root: string): Promise<{ files: string[]; unlistable: number }> {
|
|
59
|
+
const files: string[] = [];
|
|
60
|
+
let unlistable = 0;
|
|
61
|
+
let rootDirents;
|
|
62
|
+
try {
|
|
63
|
+
rootDirents = await readdir(root, { withFileTypes: true });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (isNotFound(err)) return { files, unlistable };
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
files.push(...sessionFilesIn(root, rootDirents));
|
|
69
|
+
for (const folder of sortedByName(rootDirents).filter((dirent) => dirent.isDirectory())) {
|
|
70
|
+
const dir = path.join(root, folder.name);
|
|
71
|
+
try {
|
|
72
|
+
files.push(...sessionFilesIn(dir, await readdir(dir, { withFileTypes: true })));
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (!isNotFound(err)) unlistable++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { files, unlistable };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A line's JSON object, or undefined for a blank line, a truncated last line or any other line that is
|
|
82
|
+
* not an entry. pi's own parseSessionEntries is not used: pi exports it for its tests only, and it keeps
|
|
83
|
+
* any parsed value, where a line that is not an object (`null`, `[1]`, `7`) cannot be an entry and is
|
|
84
|
+
* dropped here.
|
|
85
|
+
*/
|
|
86
|
+
function parseEntry(line: string): Record<string, unknown> | undefined {
|
|
87
|
+
if (!line.trim()) return undefined;
|
|
88
|
+
try {
|
|
89
|
+
const value: unknown = JSON.parse(line);
|
|
90
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
91
|
+
} catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A file's entries, read line by line so the file's text is never held whole; only the parsed entries
|
|
98
|
+
* are, since a compaction's model depends on entries before it. `signal` is checked between lines, and
|
|
99
|
+
* the stream is destroyed however the read ends. Undefined when aborted partway: the file is then
|
|
100
|
+
* unfinished and must add nothing. Throws when the file cannot be opened or read.
|
|
101
|
+
*/
|
|
102
|
+
async function readEntries(file: string, signal: AbortSignal | undefined): Promise<Record<string, unknown>[] | undefined> {
|
|
103
|
+
const entries: Record<string, unknown>[] = [];
|
|
104
|
+
const input = createReadStream(file, { encoding: "utf8" });
|
|
105
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
106
|
+
try {
|
|
107
|
+
for await (const line of lines) {
|
|
108
|
+
if (signal?.aborted) return undefined;
|
|
109
|
+
const entry = parseEntry(line);
|
|
110
|
+
if (entry) entries.push(entry);
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
lines.close();
|
|
114
|
+
input.destroy();
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Identifies an entry across session files: a fork or clone copies entries verbatim, id and timestamp
|
|
121
|
+
* included, so the same pair in two files is one piece of spend. Undefined for an entry without an id.
|
|
122
|
+
*/
|
|
123
|
+
const entryKey = (entry: Record<string, unknown>): string | undefined => (typeof entry.id === "string" ? `${entry.id}|${entry.timestamp}` : undefined);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* One session file's runs of spend since `since`, leaving out entries whose key is in `seenKeys` (kept
|
|
127
|
+
* from earlier files) or that repeat an earlier entry of this file, and the keys of the entries it
|
|
128
|
+
* kept. Throws when the file cannot be read or holds an entry sessionSpendByEntry() cannot make sense
|
|
129
|
+
* of; nothing partial is returned, so an unreadable file adds no records and claims no keys. Undefined
|
|
130
|
+
* when `signal` aborted the read partway.
|
|
131
|
+
*/
|
|
132
|
+
async function readRecords(file: string, since: Date, seenKeys: ReadonlySet<string>, signal: AbortSignal | undefined): Promise<{ records: SpendRecord[]; entryKeys: string[] } | undefined> {
|
|
133
|
+
const entries = await readEntries(file, signal);
|
|
134
|
+
if (!entries) return undefined;
|
|
135
|
+
const records: SpendRecord[] = [];
|
|
136
|
+
const entryKeys = new Set<string>();
|
|
137
|
+
for (const { entry, runs } of sessionSpendByEntry(entries)) {
|
|
138
|
+
const { timestamp } = entry;
|
|
139
|
+
// An entry with no usable time cannot be placed in the month, so it is left out.
|
|
140
|
+
if (typeof timestamp !== "string" || !(Date.parse(timestamp) >= since.getTime())) continue;
|
|
141
|
+
const key = entryKey(entry);
|
|
142
|
+
if (runs.length === 0 || (key !== undefined && (seenKeys.has(key) || entryKeys.has(key)))) continue;
|
|
143
|
+
if (key !== undefined) entryKeys.add(key);
|
|
144
|
+
for (const run of runs) records.push({ timestamp, run });
|
|
145
|
+
}
|
|
146
|
+
return { records, entryKeys: [...entryKeys] };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface LoadOptions {
|
|
150
|
+
/** The folder holding the project session folders (see sessionRoot). */
|
|
151
|
+
root: string;
|
|
152
|
+
/** Entries stamped before this are left out. */
|
|
153
|
+
since: Date;
|
|
154
|
+
/**
|
|
155
|
+
* Called after each file that was read (or failed to be), over the files modified since `since`.
|
|
156
|
+
* An error it throws is swallowed: progress display must not be able to fail the load.
|
|
157
|
+
*/
|
|
158
|
+
onProgress?: (done: number, total: number) => void;
|
|
159
|
+
/**
|
|
160
|
+
* Stops the load, between files and between a file's lines; the result is then `aborted`, with the
|
|
161
|
+
* files read whole so far (a file cut off partway adds nothing).
|
|
162
|
+
*/
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Every run of spend in the saved session files under `root`, with the time of the entry that spent
|
|
168
|
+
* it. A file last modified before `since` cannot hold an entry from after it, so it is never opened.
|
|
169
|
+
* An entry shared by several files (a fork or clone of a session) is counted once, from the first
|
|
170
|
+
* file read: root-level files by name, then each project folder's by name.
|
|
171
|
+
*/
|
|
172
|
+
export async function loadSpendHistory({ root, since, onProgress, signal }: LoadOptions): Promise<SpendHistory> {
|
|
173
|
+
const { files, unlistable } = await listSessionFiles(root);
|
|
174
|
+
const recentFiles: string[] = [];
|
|
175
|
+
let unreadable = unlistable;
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
if (signal?.aborted) return { records: [], unreadable, aborted: true };
|
|
178
|
+
try {
|
|
179
|
+
if ((await stat(file)).mtimeMs >= since.getTime()) recentFiles.push(file);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (!isNotFound(err)) unreadable++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const records: SpendRecord[] = [];
|
|
185
|
+
const seenKeys = new Set<string>();
|
|
186
|
+
for (const [i, file] of recentFiles.entries()) {
|
|
187
|
+
if (signal?.aborted) return { records, unreadable, aborted: true };
|
|
188
|
+
try {
|
|
189
|
+
const fileResult = await readRecords(file, since, seenKeys, signal);
|
|
190
|
+
if (!fileResult) return { records, unreadable, aborted: true };
|
|
191
|
+
for (const record of fileResult.records) records.push(record);
|
|
192
|
+
for (const key of fileResult.entryKeys) seenKeys.add(key);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (!isNotFound(err)) unreadable++;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
onProgress?.(i + 1, recentFiles.length);
|
|
198
|
+
} catch {
|
|
199
|
+
// a failing progress display must not fail the load
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { records, unreadable, aborted: false };
|
|
203
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the /dev-team usage overlay. renderUsage() turns the view's state plus the usage breakdown
|
|
3
|
+
* into the lines of the panel (header, split bars, ranked chart, footer), fitted to the terminal's width
|
|
4
|
+
* and height; it is pure (colour comes from the injected style), so tests drive it without pi. The
|
|
5
|
+
* component that holds the state and reacts to keys is UsageView (usage-view.ts).
|
|
6
|
+
*/
|
|
7
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
8
|
+
import { formatShare, formatUsd, hasUsage, type MonthSnapshot, type UsageBreakdown, type UsageRow } from "./usage-breakdown.ts";
|
|
9
|
+
import { cutToWidth } from "./terminal-text.ts";
|
|
10
|
+
import { barChartLines, type ChartStyle } from "./usage-chart.ts";
|
|
11
|
+
import { type SplitBarOptions, type SplitStyle, splitBarLines } from "./usage-split-bar.ts";
|
|
12
|
+
import type { UsageState } from "./usage-state.ts";
|
|
13
|
+
import {
|
|
14
|
+
asOfLabel,
|
|
15
|
+
CHART_FORMAT,
|
|
16
|
+
emptyUsageMessage,
|
|
17
|
+
loadFailedMessage,
|
|
18
|
+
noSubagentUsageMessage,
|
|
19
|
+
pressSHint,
|
|
20
|
+
READING_SESSIONS,
|
|
21
|
+
SCOPE_NOUN,
|
|
22
|
+
SCOPE_TITLE,
|
|
23
|
+
SEPARATOR,
|
|
24
|
+
scopeHeading,
|
|
25
|
+
splitSections,
|
|
26
|
+
totalHeaderParts,
|
|
27
|
+
unreadableFilesNote,
|
|
28
|
+
VIEW_TITLE,
|
|
29
|
+
viewRows,
|
|
30
|
+
wantedColumns,
|
|
31
|
+
} from "./usage-text.ts";
|
|
32
|
+
|
|
33
|
+
export interface UsageStyle extends ChartStyle, SplitStyle {
|
|
34
|
+
/** The header line. */
|
|
35
|
+
title(text: string): string;
|
|
36
|
+
/** The load failure message. */
|
|
37
|
+
error(text: string): string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface UsageViewModel {
|
|
41
|
+
state: UsageState;
|
|
42
|
+
/** This session's breakdown. */
|
|
43
|
+
session: UsageBreakdown;
|
|
44
|
+
/** This month's, once loaded. */
|
|
45
|
+
month?: MonthSnapshot;
|
|
46
|
+
now: Date;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface RenderOptions {
|
|
50
|
+
width: number;
|
|
51
|
+
/** Most lines the panel may take. */
|
|
52
|
+
height: number;
|
|
53
|
+
style: UsageStyle;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The breakdown the state is showing, or undefined while it is not known (loading, failed). */
|
|
57
|
+
function shownBreakdown(viewModel: UsageViewModel): UsageBreakdown | undefined {
|
|
58
|
+
if (viewModel.state.scope === "session") return viewModel.session;
|
|
59
|
+
return viewModel.state.load.kind === "ready" ? viewModel.month?.breakdown : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The header from most to least detailed: the snapshot time goes first, then the dates, then the
|
|
64
|
+
* AI credits, so a narrow terminal keeps the scope, the view and the USD total.
|
|
65
|
+
*/
|
|
66
|
+
function headerCandidates(viewModel: UsageViewModel): string[] {
|
|
67
|
+
const { scope, view } = viewModel.state;
|
|
68
|
+
const breakdown = shownBreakdown(viewModel);
|
|
69
|
+
const totals = breakdown ? totalHeaderParts(breakdown.total) : [];
|
|
70
|
+
const [usdPart] = totals;
|
|
71
|
+
const usdOnly = usdPart ? [usdPart] : [];
|
|
72
|
+
const asOfParts = scope === "month" && breakdown && viewModel.month ? [asOfLabel(viewModel.month.loadedAt)] : [];
|
|
73
|
+
const join = (scopeText: string, shownTotals: readonly string[], ...more: string[]) => [scopeText, VIEW_TITLE[view], ...shownTotals, ...more].join(SEPARATOR);
|
|
74
|
+
const heading = scopeHeading(scope, viewModel.now);
|
|
75
|
+
return [join(heading, totals, ...asOfParts), join(heading, totals), join(SCOPE_TITLE[scope], totals), join(SCOPE_TITLE[scope], usdOnly)];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The key hints from most to least detailed: hints drop from the left, so "Esc close" is the last to go. */
|
|
79
|
+
function footerCandidates(state: UsageState): string[] {
|
|
80
|
+
const otherScopeNoun = SCOPE_NOUN[state.scope === "session" ? "month" : "session"];
|
|
81
|
+
let hints = ["Tab view", `s ${otherScopeNoun}`, "Esc close"];
|
|
82
|
+
if (state.load.kind === "error") hints = ["s back", "Esc close"];
|
|
83
|
+
else if (state.load.kind === "loading") hints = ["Tab view", "s cancel", "Esc close"];
|
|
84
|
+
return hints.map((_, i) => hints.slice(i).join(SEPARATOR));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The first candidate that fits `width`, else the last one cut to it. */
|
|
88
|
+
function fitLine(candidates: readonly string[], width: number): string {
|
|
89
|
+
return candidates.find((c) => visibleWidth(c) <= width) ?? cutToWidth(candidates.at(-1) ?? "", width);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Text that fills the chart area in place of a chart. */
|
|
93
|
+
interface Message {
|
|
94
|
+
text: string;
|
|
95
|
+
tone: "plain" | "error";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function loadingMessage(load: UsageState["load"]): string {
|
|
99
|
+
return load.kind === "loading" && load.progress ? `${READING_SESSIONS} ${load.progress.done}/${load.progress.total} files` : READING_SESSIONS;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** What replaces the chart when there is none to draw; undefined when the view has rows. */
|
|
103
|
+
function bodyMessage(viewModel: UsageViewModel, breakdown: UsageBreakdown | undefined): Message | undefined {
|
|
104
|
+
const { state } = viewModel;
|
|
105
|
+
if (state.load.kind === "loading") return { text: loadingMessage(state.load), tone: "plain" };
|
|
106
|
+
if (state.load.kind === "error") return { text: loadFailedMessage(state.load.reason), tone: "error" };
|
|
107
|
+
if (!breakdown || !hasUsage(breakdown.total)) return { text: `${emptyUsageMessage(state.scope)}${pressSHint(state.scope)}`, tone: "plain" };
|
|
108
|
+
if (state.view === "agent" && breakdown.byAgent.length === 0) return { text: noSubagentUsageMessage(state.scope), tone: "plain" };
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function footnoteText(viewModel: UsageViewModel): string | undefined {
|
|
113
|
+
const { state, month } = viewModel;
|
|
114
|
+
const unreadable = state.scope === "month" && state.load.kind === "ready" ? (month?.unreadable ?? 0) : 0;
|
|
115
|
+
return unreadableFilesNote(unreadable);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One split bar's lines: the bar, then its legend lines. */
|
|
119
|
+
interface SplitBlock {
|
|
120
|
+
bar: string;
|
|
121
|
+
legend: string[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The split bars of splitSections(): providers (legend gives each share), then threads (legend gives each USD). */
|
|
125
|
+
function splitBlocks(breakdown: UsageBreakdown, options: Omit<SplitBarOptions, "formatValue">): SplitBlock[] {
|
|
126
|
+
return splitSections(breakdown).flatMap((section): SplitBlock[] => {
|
|
127
|
+
const formatValue = (usd: number, share: number) => (section.legend === "share" ? formatShare(share) : formatUsd(usd));
|
|
128
|
+
const lines = splitBarLines(
|
|
129
|
+
section.parts.map((p) => ({ label: p.label, amount: p.usd })),
|
|
130
|
+
{ ...options, formatValue },
|
|
131
|
+
);
|
|
132
|
+
return lines.length ? [{ bar: lines[0], legend: lines.slice(1) }] : [];
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** `count` split lines at most: every bar first, then legend lines block by block; each block stays in order. */
|
|
137
|
+
function fitSplitLines(blocks: readonly SplitBlock[], count: number): string[] {
|
|
138
|
+
let legendBudget = Math.max(0, count - blocks.length);
|
|
139
|
+
return blocks.slice(0, count).flatMap((block) => {
|
|
140
|
+
const legend = block.legend.slice(0, legendBudget);
|
|
141
|
+
legendBudget -= legend.length;
|
|
142
|
+
return [block.bar, ...legend];
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** What the panel would like to show, in lines. */
|
|
147
|
+
interface LayoutRequest {
|
|
148
|
+
splitLineCount: number;
|
|
149
|
+
/** The split bars without their legends: what is left when the legends give way. */
|
|
150
|
+
splitBarCount: number;
|
|
151
|
+
hasFootnote: boolean;
|
|
152
|
+
chartRowCount: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface Layout {
|
|
156
|
+
/** Blank lines between header, split bar, chart and footer. */
|
|
157
|
+
hasGaps: boolean;
|
|
158
|
+
/** Split bar lines kept (the bar first, then its legend). */
|
|
159
|
+
splitLineCount: number;
|
|
160
|
+
hasFootnote: boolean;
|
|
161
|
+
chartRowCount: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const PINNED_LINES = 2;
|
|
165
|
+
const FOOTNOTE_LINES = 1;
|
|
166
|
+
/** Chart rows worth keeping over the extras before the extras start to go. */
|
|
167
|
+
const MIN_CHART_ROWS = 3;
|
|
168
|
+
/** Blank lines after the header and before the footer. */
|
|
169
|
+
const OUTER_GAP_LINES = 2;
|
|
170
|
+
/** The blank line after the split bar, when there is one. */
|
|
171
|
+
const SPLIT_GAP_LINES = 1;
|
|
172
|
+
|
|
173
|
+
/** The blank lines kept between header, split bar (when there is one), chart and footer. */
|
|
174
|
+
const gapLineCount = (hasSplit: boolean) => OUTER_GAP_LINES + (hasSplit ? SPLIT_GAP_LINES : 0);
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Decides which lines are pinned and which flex for `height`. The header and footer always stay; the
|
|
178
|
+
* rest give way in order (blank gaps, footnote, split legends, split bars) until the chart gets
|
|
179
|
+
* MIN_CHART_ROWS rows or what it asked for, whichever is fewer. What is left goes to the chart.
|
|
180
|
+
*/
|
|
181
|
+
function planLayout(height: number, request: LayoutRequest): Layout {
|
|
182
|
+
const free = Math.max(0, height - PINNED_LINES);
|
|
183
|
+
const { splitLineCount, hasFootnote } = request;
|
|
184
|
+
const options: Layout[] = [
|
|
185
|
+
{ hasGaps: true, splitLineCount, hasFootnote, chartRowCount: 0 },
|
|
186
|
+
{ hasGaps: false, splitLineCount, hasFootnote, chartRowCount: 0 },
|
|
187
|
+
{ hasGaps: false, splitLineCount, hasFootnote: false, chartRowCount: 0 },
|
|
188
|
+
{ hasGaps: false, splitLineCount: Math.min(request.splitBarCount, splitLineCount), hasFootnote: false, chartRowCount: 0 },
|
|
189
|
+
{ hasGaps: false, splitLineCount: 0, hasFootnote: false, chartRowCount: 0 },
|
|
190
|
+
];
|
|
191
|
+
const withChartRows = (o: Layout): Layout => ({
|
|
192
|
+
...o,
|
|
193
|
+
chartRowCount: Math.max(0, free - (o.hasGaps ? gapLineCount(splitLineCount > 0) : 0) - o.splitLineCount - (o.hasFootnote ? FOOTNOTE_LINES : 0)),
|
|
194
|
+
});
|
|
195
|
+
const sized = options.map(withChartRows);
|
|
196
|
+
return sized.find((o) => o.chartRowCount >= Math.min(request.chartRowCount, MIN_CHART_ROWS)) ?? sized[sized.length - 1];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function renderUsage(viewModel: UsageViewModel, { width, height, style }: RenderOptions): string[] {
|
|
200
|
+
const breakdown = shownBreakdown(viewModel);
|
|
201
|
+
const message = bodyMessage(viewModel, breakdown);
|
|
202
|
+
// The split bars show whenever there is spend to split, even when the view's own chart is empty.
|
|
203
|
+
const showSplit = !!breakdown && hasUsage(breakdown.total) && viewModel.state.load.kind !== "loading";
|
|
204
|
+
const blocks = showSplit ? splitBlocks(breakdown, { width, style }) : [];
|
|
205
|
+
const splitLineCount = blocks.reduce((sum, b) => sum + 1 + b.legend.length, 0);
|
|
206
|
+
const footnote = footnoteText(viewModel);
|
|
207
|
+
const rankedRows: UsageRow[] = breakdown ? viewRows(viewModel.state.view, breakdown) : [];
|
|
208
|
+
const plan = planLayout(height, { splitLineCount, splitBarCount: blocks.length, hasFootnote: !!footnote, chartRowCount: message ? 1 : rankedRows.length });
|
|
209
|
+
|
|
210
|
+
const splitLines = fitSplitLines(blocks, plan.splitLineCount);
|
|
211
|
+
const paint = message?.tone === "error" ? style.error : (text: string) => text;
|
|
212
|
+
const body = message
|
|
213
|
+
? plan.chartRowCount > 0 ? [paint(cutToWidth(message.text, width))] : []
|
|
214
|
+
: barChartLines(rankedRows, { width, maxRows: plan.chartRowCount, format: CHART_FORMAT, wantedColumns: wantedColumns(rankedRows), style });
|
|
215
|
+
const gapLines = plan.hasGaps ? [""] : [];
|
|
216
|
+
return [
|
|
217
|
+
style.title(fitLine(headerCandidates(viewModel), width)),
|
|
218
|
+
...gapLines,
|
|
219
|
+
...(splitLines.length ? [...splitLines, ...gapLines] : []),
|
|
220
|
+
...body,
|
|
221
|
+
...(footnote && plan.hasFootnote ? [style.muted(cutToWidth(footnote, width))] : []),
|
|
222
|
+
...gapLines,
|
|
223
|
+
style.muted(fitLine(footerCandidates(viewModel.state), width)),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stacked split for the /dev-team usage overlay (splitBarLines): one bar cut into segments by
|
|
3
|
+
* amount, then a legend. The overlay draws two: threads (main, subagents, overhead) and providers.
|
|
4
|
+
* Segments keep the order the caller gives, and each input position has its own glyph (█ ▓ ▒ ░) and
|
|
5
|
+
* colour, so the split reads without colour. A part with no amount draws nothing but keeps its
|
|
6
|
+
* position, so a fixed list of parts (the threads) always draws the same part with the same glyph. When
|
|
7
|
+
* there are more parts than glyphs, the parts with an amount are drawn in order and those past the
|
|
8
|
+
* last glyph fold into one "other" segment. Like the bar
|
|
9
|
+
* chart (usage-chart.ts) it is pure string rendering: plain text is measured and cut first, then
|
|
10
|
+
* styled.
|
|
11
|
+
*/
|
|
12
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
13
|
+
import { cutToWidth, toSingleLine } from "./terminal-text.ts";
|
|
14
|
+
import { FULL_BLOCK } from "./usage-chart.ts";
|
|
15
|
+
|
|
16
|
+
/** One input to the split bar: an amount, which becomes (part of) a segment. */
|
|
17
|
+
export interface SplitPart {
|
|
18
|
+
label: string;
|
|
19
|
+
amount: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SplitStyle {
|
|
23
|
+
/** Colours the glyphs of the segment in slot `slot` (0 to SPLIT_GLYPHS.length - 1). */
|
|
24
|
+
segment(slot: number, text: string): string;
|
|
25
|
+
/** De-emphasises the legend separators. */
|
|
26
|
+
muted(text: string): string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SplitBarOptions {
|
|
30
|
+
width: number;
|
|
31
|
+
/** The legend's text for a part's amount; `share` is its fraction of the bar's total. */
|
|
32
|
+
formatValue(amount: number, share: number): string;
|
|
33
|
+
style: SplitStyle;
|
|
34
|
+
/** Cap on the bar; it also never exceeds `width`. */
|
|
35
|
+
maxBarCells?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The glyph of each segment slot; there are as many segments as glyphs at most. */
|
|
39
|
+
export const SPLIT_GLYPHS: readonly string[] = [FULL_BLOCK, "▓", "▒", "░"];
|
|
40
|
+
const OTHER_LABEL = "other";
|
|
41
|
+
type Segment = { label: string; slot: number; amount: number };
|
|
42
|
+
const MAX_SPLIT_BAR_CELLS = 40;
|
|
43
|
+
const LEGEND_SEPARATOR = " · ";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Splits `cells` across the segment weights by largest remainder, so the result always sums to
|
|
47
|
+
* `cells` (ties go to the earlier weight). A segment that would get no cell borrows one from the
|
|
48
|
+
* largest segment, so a small non-zero thread stays visible, as long as there are cells to go round.
|
|
49
|
+
*/
|
|
50
|
+
function allocateCells(weights: readonly number[], cells: number): number[] {
|
|
51
|
+
const sum = (numbers: readonly number[]) => numbers.reduce((total, n) => total + n, 0);
|
|
52
|
+
const total = sum(weights);
|
|
53
|
+
const ideal = weights.map((w) => (w * cells) / total);
|
|
54
|
+
const wholeCells = ideal.map(Math.floor);
|
|
55
|
+
const remainder = (i: number) => ideal[i] - wholeCells[i];
|
|
56
|
+
const indicesByRemainder = ideal.map((_, i) => i).sort((a, b) => remainder(b) - remainder(a) || a - b);
|
|
57
|
+
const leftover = cells - sum(wholeCells);
|
|
58
|
+
const gainsLeftover = new Set(indicesByRemainder.slice(0, leftover));
|
|
59
|
+
return keepVisible(wholeCells.map((n, i) => (gainsLeftover.has(i) ? n + 1 : n)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Moves one cell from the largest segment to each empty one (while the largest has cells to spare). */
|
|
63
|
+
function keepVisible(allocated: readonly number[]): number[] {
|
|
64
|
+
return allocated.reduce(
|
|
65
|
+
(segmentCells, _, i) => {
|
|
66
|
+
if (segmentCells[i] > 0) return segmentCells;
|
|
67
|
+
const largestIndex = segmentCells.indexOf(Math.max(...segmentCells));
|
|
68
|
+
if (segmentCells[largestIndex] < 2) return segmentCells;
|
|
69
|
+
return segmentCells.map((n, j) => (j === largestIndex ? n - 1 : j === i ? n + 1 : n));
|
|
70
|
+
},
|
|
71
|
+
[...allocated],
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The parts with an amount, each in its slot: its input position, or its rank among them when the parts outnumber the glyphs. */
|
|
76
|
+
function toSegments(parts: readonly SplitPart[]): Segment[] {
|
|
77
|
+
// Labels can come from session files, so they are made one line before they are measured.
|
|
78
|
+
const slotted = (part: SplitPart, slot: number): Segment => ({ label: toSingleLine(part.label), slot, amount: part.amount });
|
|
79
|
+
if (parts.length <= SPLIT_GLYPHS.length) return parts.map(slotted).filter((s) => s.amount > 0);
|
|
80
|
+
const positive = parts.filter((p) => p.amount > 0);
|
|
81
|
+
if (positive.length <= SPLIT_GLYPHS.length) return positive.map(slotted);
|
|
82
|
+
const kept = positive.slice(0, SPLIT_GLYPHS.length - 1);
|
|
83
|
+
const other = { label: OTHER_LABEL, amount: positive.slice(kept.length).reduce((sum, p) => sum + p.amount, 0) };
|
|
84
|
+
return [...kept, other].map(slotted);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A bar split into segments by amount, in the given order, then a legend naming each with its value. */
|
|
88
|
+
export function splitBarLines(parts: readonly SplitPart[], { width, formatValue, style, maxBarCells = MAX_SPLIT_BAR_CELLS }: SplitBarOptions): string[] {
|
|
89
|
+
const segments = toSegments(parts);
|
|
90
|
+
if (!segments.length) return [];
|
|
91
|
+
|
|
92
|
+
const segmentCells = allocateCells(segments.map((s) => s.amount), Math.max(0, Math.min(width, maxBarCells)));
|
|
93
|
+
const bar = segments.map((s, i) => style.segment(s.slot, SPLIT_GLYPHS[s.slot].repeat(segmentCells[i]))).join("");
|
|
94
|
+
return [bar, ...legendLines(segments, { width, formatValue, style })];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The legend ("█ main $0.30 · ▓ subagents $0.60"), packed onto as few lines as fit `width`. An entry
|
|
99
|
+
* never splits across lines; one wider than `width` on its own is cut to it. Each entry is cut as
|
|
100
|
+
* plain text, then its glyph is styled.
|
|
101
|
+
*/
|
|
102
|
+
function legendLines(
|
|
103
|
+
segments: readonly Segment[],
|
|
104
|
+
{ width, formatValue, style }: Pick<SplitBarOptions, "width" | "formatValue" | "style">,
|
|
105
|
+
): string[] {
|
|
106
|
+
const separatorWidth = visibleWidth(LEGEND_SEPARATOR);
|
|
107
|
+
const total = segments.reduce((sum, s) => sum + s.amount, 0);
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
let line = "";
|
|
110
|
+
let lineWidth = 0;
|
|
111
|
+
for (const s of segments) {
|
|
112
|
+
const glyph = SPLIT_GLYPHS[s.slot];
|
|
113
|
+
const plain = cutToWidth(`${glyph} ${s.label} ${formatValue(s.amount, s.amount / total)}`, width);
|
|
114
|
+
const entry = plain && style.segment(s.slot, plain.slice(0, glyph.length)) + plain.slice(glyph.length);
|
|
115
|
+
const entryWidth = visibleWidth(plain);
|
|
116
|
+
if (line && lineWidth + separatorWidth + entryWidth <= width) {
|
|
117
|
+
line += style.muted(LEGEND_SEPARATOR) + entry;
|
|
118
|
+
lineWidth += separatorWidth + entryWidth;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (line) lines.push(line);
|
|
122
|
+
line = entry;
|
|
123
|
+
lineWidth = entryWidth;
|
|
124
|
+
}
|
|
125
|
+
lines.push(line);
|
|
126
|
+
return lines;
|
|
127
|
+
}
|