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,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dev_team_subagent` tool — pi equivalent of Claude Code's Agent/Task tool for dev-team agents.
|
|
3
|
+
*
|
|
4
|
+
* Each dispatch runs a child `pi --mode json -p --no-session` process with:
|
|
5
|
+
* - the agent's markdown body appended to the system prompt
|
|
6
|
+
* - its Claude tool list mapped to pi tools (Glob -> find/ls, Agent -> dev_team_subagent, Skill -> skill, ...)
|
|
7
|
+
* - its model tier (opus/sonnet/haiku/fable) resolved through dev-team.json, default: inherit
|
|
8
|
+
* - its `effort` mapped to --thinking
|
|
9
|
+
* Around each dispatch it fires the upstream hooks Claude Code would fire:
|
|
10
|
+
* PreToolUse(Agent) before (dispatch ledger for the PR gate), SubagentStop after
|
|
11
|
+
* (review-verdict ledger, completion guard, task metrics) with a synthetic Claude transcript.
|
|
12
|
+
*/
|
|
13
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import * as os from "node:os";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
19
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
20
|
+
import { Type } from "typebox";
|
|
21
|
+
import { type AgentDef, DEV_TEAM_SUBAGENT_TOOL, discoverDispatchAgents, mapTools, resolveAgentName, resolveModel, resolveThinking } from "./agents.ts";
|
|
22
|
+
import type { DevTeamConfig } from "./config.ts";
|
|
23
|
+
import type { HookBridge } from "./hooks.ts";
|
|
24
|
+
import { applyChildEvent, type ChildEvent, newChildRunState } from "./child-run.ts";
|
|
25
|
+
import { recentCallLines, renderSubagentCall, renderSubagentResult } from "./subagent-render.ts";
|
|
26
|
+
import {
|
|
27
|
+
type AgentSource,
|
|
28
|
+
type DispatchArgs,
|
|
29
|
+
describeWorktree,
|
|
30
|
+
dispatchAgent,
|
|
31
|
+
dispatchTask,
|
|
32
|
+
emptyPiUsage,
|
|
33
|
+
type NestedUsage,
|
|
34
|
+
type ProgressPatch,
|
|
35
|
+
type SubagentDetails,
|
|
36
|
+
type SubagentTaskView,
|
|
37
|
+
sumPiUsage,
|
|
38
|
+
toUsageTotals,
|
|
39
|
+
type UsageTotals,
|
|
40
|
+
type WorktreeInfo,
|
|
41
|
+
} from "./subagent-types.ts";
|
|
42
|
+
import { saveFullOutput } from "./session-files.ts";
|
|
43
|
+
import { buildTranscriptLines, type PiMessageLike, writeTranscript } from "./transcript.ts";
|
|
44
|
+
import { type ChildTrust, childTrustOf, trustArgs } from "./trust.ts";
|
|
45
|
+
|
|
46
|
+
export interface SubagentRunResult {
|
|
47
|
+
agent: string;
|
|
48
|
+
source?: AgentSource;
|
|
49
|
+
task: string;
|
|
50
|
+
ok: boolean;
|
|
51
|
+
output: string;
|
|
52
|
+
/** Where the complete output was saved when it is longer than the tool result may carry. */
|
|
53
|
+
fullOutputFile?: string;
|
|
54
|
+
error?: string;
|
|
55
|
+
model?: string;
|
|
56
|
+
tier?: string;
|
|
57
|
+
stopReason?: string;
|
|
58
|
+
/** The child's own turns. */
|
|
59
|
+
usage: UsageTotals;
|
|
60
|
+
/** Agents the child dispatched itself (any depth), credited to those agents. */
|
|
61
|
+
nested: NestedUsage[];
|
|
62
|
+
/** Everything the child cost, nested dispatches included, in pi's Usage shape for the tool result. */
|
|
63
|
+
totalUsage?: Usage;
|
|
64
|
+
messages: PiMessageLike[];
|
|
65
|
+
worktree?: WorktreeInfo;
|
|
66
|
+
blocked?: boolean;
|
|
67
|
+
durationMs: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const TASK_PREVIEW_CHARS = 400;
|
|
71
|
+
const STATUS_LINE_TOOLS = 3;
|
|
72
|
+
|
|
73
|
+
const OUTPUT_CAP = 50 * 1024;
|
|
74
|
+
|
|
75
|
+
class Semaphore {
|
|
76
|
+
private active = 0;
|
|
77
|
+
private readonly queue: (() => void)[] = [];
|
|
78
|
+
limit: number;
|
|
79
|
+
constructor(limit: number) {
|
|
80
|
+
this.limit = limit;
|
|
81
|
+
}
|
|
82
|
+
async acquire(): Promise<() => void> {
|
|
83
|
+
if (this.active >= this.limit) await new Promise<void>((r) => this.queue.push(r));
|
|
84
|
+
this.active++;
|
|
85
|
+
return () => {
|
|
86
|
+
this.active--;
|
|
87
|
+
this.queue.shift()?.();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
93
|
+
const currentScript = process.argv[1];
|
|
94
|
+
const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
|
|
95
|
+
if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
|
|
96
|
+
return { command: process.execPath, args: [currentScript, ...args] };
|
|
97
|
+
}
|
|
98
|
+
const execName = path.basename(process.execPath).toLowerCase();
|
|
99
|
+
if (!/^(node|bun)(\.exe)?$/.test(execName)) return { command: process.execPath, args };
|
|
100
|
+
return { command: "pi", args };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** CLI flags of the parent process that children must share to see the same resources. */
|
|
104
|
+
export function forwardedArgs(argv: string[] = process.argv.slice(2)): string[] {
|
|
105
|
+
const out: string[] = [];
|
|
106
|
+
const withValue = new Set(["-e", "--extension", "--skill", "--prompt-template", "--provider"]);
|
|
107
|
+
const flags = new Set(["-ne", "--no-extensions", "--no-skills", "-ns", "--offline"]);
|
|
108
|
+
for (let i = 0; i < argv.length; i++) {
|
|
109
|
+
const a = argv[i];
|
|
110
|
+
if (withValue.has(a) && i + 1 < argv.length) {
|
|
111
|
+
let v = argv[i + 1];
|
|
112
|
+
if (!v.includes(":") && (v.startsWith(".") || v.includes("/"))) v = path.resolve(v);
|
|
113
|
+
out.push(a, v);
|
|
114
|
+
i++;
|
|
115
|
+
} else if (flags.has(a)) out.push(a);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function finalText(messages: PiMessageLike[]): string {
|
|
121
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
122
|
+
const m = messages[i];
|
|
123
|
+
if (m.role !== "assistant" || !Array.isArray(m.content)) continue;
|
|
124
|
+
const texts = (m.content as { type: string; text?: string }[]).filter((c) => c.type === "text").map((c) => c.text ?? "");
|
|
125
|
+
if (texts.join("").trim()) return texts.join("\n");
|
|
126
|
+
}
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The one rule for when a child's output is too long to return whole. */
|
|
131
|
+
export function isOversized(text: string): boolean {
|
|
132
|
+
return Buffer.byteLength(text, "utf8") > OUTPUT_CAP;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The first OUTPUT_CAP bytes, cut on a character boundary. */
|
|
136
|
+
function cutOutput(text: string): string {
|
|
137
|
+
return Buffer.from(text, "utf8").subarray(0, OUTPUT_CAP).toString("utf8").replace(/\uFFFD$/, "");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The output as the model gets it: cut when oversized, with where to read the complete text. */
|
|
141
|
+
export function outputForModel(text: string, fullOutputFile?: string): string {
|
|
142
|
+
if (!isOversized(text)) return text;
|
|
143
|
+
const fullOutputNote = fullOutputFile ? `; the complete output is in ${fullOutputFile} (read it with offset/limit)` : "";
|
|
144
|
+
return `${cutOutput(text)}\n\n[output truncated at ${OUTPUT_CAP} bytes${fullOutputNote}]`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The output as the TUI shows it: cut when oversized, naming the file without instructions for the model. */
|
|
148
|
+
export function outputForView(text: string, fullOutputFile?: string): string {
|
|
149
|
+
if (!isOversized(text)) return text;
|
|
150
|
+
return `${cutOutput(text)}\n\n[output truncated at ${OUTPUT_CAP} bytes${fullOutputFile ? `; complete output: ${fullOutputFile}` : ""}]`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function git(cwd: string, args: string[]): { ok: boolean; out: string } {
|
|
154
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
155
|
+
return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createWorktree(cwd: string, agent: string, id: string): { path: string; branch: string; base: string; repoRoot: string } {
|
|
159
|
+
const top = git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
160
|
+
if (!top.ok) throw new Error(`isolation "worktree" needs a git repository: ${top.out}`);
|
|
161
|
+
const root = top.out.split("\n")[0];
|
|
162
|
+
const base = git(root, ["rev-parse", "HEAD"]);
|
|
163
|
+
if (!base.ok) throw new Error(`isolation "worktree" needs at least one commit: ${base.out}`);
|
|
164
|
+
const wtPath = path.join(root, ".claude", "worktrees", `${agent}-${id}`);
|
|
165
|
+
const branch = `dev-team/${agent}-${id}`;
|
|
166
|
+
fs.mkdirSync(path.dirname(wtPath), { recursive: true });
|
|
167
|
+
// Branch from local HEAD (Claude Code's worktree.baseRef=head), so freshly written specs/plans are visible.
|
|
168
|
+
const add = git(root, ["worktree", "add", "-b", branch, wtPath, "HEAD"]);
|
|
169
|
+
if (!add.ok) throw new Error(`git worktree add failed: ${add.out}`);
|
|
170
|
+
return { path: wtPath, branch, base: base.out.split("\n")[0], repoRoot: root };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function finishWorktree(wt: { path: string; branch: string; base: string }): WorktreeInfo {
|
|
174
|
+
// Runtime state the hooks write under .claude/ does not count as work.
|
|
175
|
+
const status = git(wt.path, ["status", "--porcelain", "--", ".", ":(exclude).claude"]);
|
|
176
|
+
const dirty = status.ok && status.out.length > 0;
|
|
177
|
+
const count = git(wt.path, ["rev-list", "--count", `${wt.base}..HEAD`]);
|
|
178
|
+
const commits = count.ok ? Number(count.out) || 0 : 0;
|
|
179
|
+
if (!dirty && commits === 0) {
|
|
180
|
+
git(wt.path, ["worktree", "remove", "--force", wt.path]);
|
|
181
|
+
git(path.dirname(path.dirname(path.dirname(wt.path))), ["branch", "-D", wt.branch]);
|
|
182
|
+
return { path: wt.path, branch: wt.branch, kept: false, dirty, commits };
|
|
183
|
+
}
|
|
184
|
+
return { path: wt.path, branch: wt.branch, kept: true, dirty, commits };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface SubagentDeps {
|
|
188
|
+
pi: ExtensionAPI;
|
|
189
|
+
packageRoot: string;
|
|
190
|
+
getConfig: () => DevTeamConfig;
|
|
191
|
+
hooks: HookBridge;
|
|
192
|
+
depth: number;
|
|
193
|
+
onUsage: (r: SubagentRunResult) => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const TaskFields = {
|
|
197
|
+
agent: Type.Optional(Type.String({ description: "Agent name (Claude: subagent_type), e.g. security-review, software-engineer, Explore" })),
|
|
198
|
+
task: Type.Optional(Type.String({ description: "Full task prompt for the agent (Claude: prompt). The agent sees only this, not the conversation." })),
|
|
199
|
+
description: Type.Optional(Type.String({ description: "Short 3-5 word label" })),
|
|
200
|
+
model: Type.Optional(
|
|
201
|
+
Type.String({ description: "Override model: a tier (opus|sonnet|haiku|fable|inherit) or provider/model-id" }),
|
|
202
|
+
),
|
|
203
|
+
thinking: Type.Optional(Type.String({ description: "Override thinking/effort level (low|medium|high|xhigh)" })),
|
|
204
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the agent" })),
|
|
205
|
+
isolation: Type.Optional(
|
|
206
|
+
Type.String({ description: 'Set "worktree" to run the agent in a new git worktree branched from HEAD' }),
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const SubagentParams = Type.Object({
|
|
211
|
+
...TaskFields,
|
|
212
|
+
tasks: Type.Optional(
|
|
213
|
+
Type.Array(Type.Object(TaskFields), {
|
|
214
|
+
description: "Run several agents concurrently. Each item has the same fields as a single dispatch.",
|
|
215
|
+
}),
|
|
216
|
+
),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export function registerSubagentTool(deps: SubagentDeps): void {
|
|
220
|
+
const { pi, packageRoot, getConfig, hooks } = deps;
|
|
221
|
+
const semaphore = new Semaphore(getConfig().maxParallelAgents);
|
|
222
|
+
|
|
223
|
+
async function runOne(
|
|
224
|
+
input: DispatchArgs,
|
|
225
|
+
ctx: ExtensionContext,
|
|
226
|
+
signal: AbortSignal | undefined,
|
|
227
|
+
agents: Map<string, AgentDef>,
|
|
228
|
+
update: (patch: ProgressPatch) => void,
|
|
229
|
+
trust: ChildTrust,
|
|
230
|
+
): Promise<SubagentRunResult> {
|
|
231
|
+
const started = Date.now();
|
|
232
|
+
const config = getConfig();
|
|
233
|
+
const requested = dispatchAgent(input);
|
|
234
|
+
let task = dispatchTask(input);
|
|
235
|
+
const empty = toUsageTotals(emptyPiUsage(), 0);
|
|
236
|
+
const fail = (error: string, extra: Partial<SubagentRunResult> = {}): SubagentRunResult => ({
|
|
237
|
+
agent: requested || "(none)",
|
|
238
|
+
task,
|
|
239
|
+
ok: false,
|
|
240
|
+
output: "",
|
|
241
|
+
error,
|
|
242
|
+
usage: empty,
|
|
243
|
+
nested: [],
|
|
244
|
+
messages: [],
|
|
245
|
+
durationMs: Date.now() - started,
|
|
246
|
+
...extra,
|
|
247
|
+
});
|
|
248
|
+
if (!requested || !task) return fail("Both `agent` and `task` are required.");
|
|
249
|
+
const def = resolveAgentName(agents, requested);
|
|
250
|
+
if (!def) {
|
|
251
|
+
return fail(`Unknown agent "${requested}". Available: ${[...agents.keys()].sort().join(", ")}`);
|
|
252
|
+
}
|
|
253
|
+
update({ agent: def.name, source: def.source });
|
|
254
|
+
|
|
255
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
256
|
+
const baseCwd = input.cwd ? path.resolve(ctx.cwd, input.cwd) : ctx.cwd;
|
|
257
|
+
|
|
258
|
+
// PreToolUse(Agent): dispatch ledger etc. (one per dispatch, as Claude fires one per Agent call)
|
|
259
|
+
const pre = await hooks.run(
|
|
260
|
+
"PreToolUse",
|
|
261
|
+
{
|
|
262
|
+
session_id: sessionId,
|
|
263
|
+
cwd: baseCwd,
|
|
264
|
+
transcript_path: ctx.sessionManager.getSessionFile() ?? "",
|
|
265
|
+
tool_name: "Agent",
|
|
266
|
+
tool_input: { subagent_type: def.name, prompt: task, description: input.description ?? "" },
|
|
267
|
+
},
|
|
268
|
+
baseCwd,
|
|
269
|
+
{ matchTarget: "Agent" },
|
|
270
|
+
);
|
|
271
|
+
if (pre.block) return fail(`Dispatch blocked by hook:\n${pre.block}`, { agent: def.name, blocked: true });
|
|
272
|
+
if (pre.updatedInput) {
|
|
273
|
+
if (typeof pre.updatedInput.prompt === "string") task = pre.updatedInput.prompt;
|
|
274
|
+
if (typeof pre.updatedInput.additionalContext === "string") task = `${task}\n\n${pre.updatedInput.additionalContext}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const release = await semaphore.acquire();
|
|
278
|
+
let wt: ReturnType<typeof createWorktree> | undefined;
|
|
279
|
+
const agentId = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
280
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-dev-team-agent-"));
|
|
281
|
+
try {
|
|
282
|
+
const runCwd = input.isolation === "worktree" ? (wt = createWorktree(baseCwd, def.name, agentId.slice(0, 8))).path : baseCwd;
|
|
283
|
+
|
|
284
|
+
const parentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
285
|
+
const choice = resolveModel(def.model, input.model, config.models, parentModel);
|
|
286
|
+
const thinking = resolveThinking(def.effort, input.thinking, config.thinking, ctx.thinkingLevel);
|
|
287
|
+
|
|
288
|
+
const available = pi.getAllTools().map((t) => t.name);
|
|
289
|
+
const mapping = mapTools(def.claudeTools, available);
|
|
290
|
+
let tools = mapping?.tools;
|
|
291
|
+
if (tools && deps.depth + 1 >= config.maxSubagentDepth) tools = tools.filter((t) => t !== DEV_TEAM_SUBAGENT_TOOL);
|
|
292
|
+
|
|
293
|
+
const prompt = buildSystemPrompt(def, packageRoot, mapping?.scopedBash ?? [], mapping?.unmapped ?? []);
|
|
294
|
+
const promptFile = path.join(tmpDir, `agent-${def.name}.md`);
|
|
295
|
+
fs.writeFileSync(promptFile, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
296
|
+
|
|
297
|
+
const args = ["--mode", "json", "-p", "--no-session", ...forwardedArgs()];
|
|
298
|
+
args.push(...trustArgs(trust, runCwd, wt?.repoRoot));
|
|
299
|
+
if (choice.model) args.push("--model", choice.model);
|
|
300
|
+
if (thinking) args.push("--thinking", thinking);
|
|
301
|
+
if (tools) args.push("--tools", tools.length ? tools.join(",") : "read");
|
|
302
|
+
args.push("--append-system-prompt", promptFile);
|
|
303
|
+
args.push(`Task: ${task}`);
|
|
304
|
+
|
|
305
|
+
const env: NodeJS.ProcessEnv = {
|
|
306
|
+
...process.env,
|
|
307
|
+
...config.env,
|
|
308
|
+
DEV_TEAM_SUBAGENT: "1",
|
|
309
|
+
DEV_TEAM_SUBAGENT_DEPTH: String(deps.depth + 1),
|
|
310
|
+
DEV_TEAM_AGENT_NAME: def.name,
|
|
311
|
+
DEV_TEAM_PARENT_SESSION_ID: sessionId,
|
|
312
|
+
};
|
|
313
|
+
delete env.DEV_TEAM_INTERACTIVE;
|
|
314
|
+
|
|
315
|
+
const run = newChildRunState(choice.model);
|
|
316
|
+
let stderr = "";
|
|
317
|
+
let aborted = false;
|
|
318
|
+
let timedOut = false;
|
|
319
|
+
|
|
320
|
+
const exitCode = await new Promise<number>((resolve) => {
|
|
321
|
+
const inv = getPiInvocation(args);
|
|
322
|
+
const proc = spawn(inv.command, inv.args, { cwd: runCwd, env, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
323
|
+
let buf = "";
|
|
324
|
+
const onLine = (line: string) => {
|
|
325
|
+
if (!line.trim()) return;
|
|
326
|
+
let ev: ChildEvent;
|
|
327
|
+
try {
|
|
328
|
+
ev = JSON.parse(line);
|
|
329
|
+
} catch {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const patch = applyChildEvent(run, ev);
|
|
333
|
+
if (patch) update(patch);
|
|
334
|
+
};
|
|
335
|
+
proc.stdout.on("data", (d) => {
|
|
336
|
+
buf += d.toString();
|
|
337
|
+
const lines = buf.split("\n");
|
|
338
|
+
buf = lines.pop() ?? "";
|
|
339
|
+
for (const l of lines) onLine(l);
|
|
340
|
+
});
|
|
341
|
+
proc.stderr.on("data", (d) => {
|
|
342
|
+
stderr += d.toString();
|
|
343
|
+
});
|
|
344
|
+
const kill = () => {
|
|
345
|
+
proc.kill("SIGTERM");
|
|
346
|
+
setTimeout(() => {
|
|
347
|
+
if (!proc.killed) proc.kill("SIGKILL");
|
|
348
|
+
}, 5000);
|
|
349
|
+
};
|
|
350
|
+
const timer =
|
|
351
|
+
config.subagentTimeoutSec > 0
|
|
352
|
+
? setTimeout(() => {
|
|
353
|
+
timedOut = true;
|
|
354
|
+
kill();
|
|
355
|
+
}, config.subagentTimeoutSec * 1000)
|
|
356
|
+
: undefined;
|
|
357
|
+
proc.on("close", (code) => {
|
|
358
|
+
if (timer) clearTimeout(timer);
|
|
359
|
+
if (buf.trim()) onLine(buf);
|
|
360
|
+
resolve(code ?? 0);
|
|
361
|
+
});
|
|
362
|
+
proc.on("error", (e) => {
|
|
363
|
+
if (timer) clearTimeout(timer);
|
|
364
|
+
stderr += String(e);
|
|
365
|
+
resolve(1);
|
|
366
|
+
});
|
|
367
|
+
if (signal) {
|
|
368
|
+
const onAbort = () => {
|
|
369
|
+
aborted = true;
|
|
370
|
+
kill();
|
|
371
|
+
};
|
|
372
|
+
if (signal.aborted) onAbort();
|
|
373
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const worktree = wt ? finishWorktree(wt) : undefined;
|
|
378
|
+
const output = finalText(run.messages);
|
|
379
|
+
const fullOutputFile = isOversized(output) ? saveFullOutput(sessionId, def.name, agentId, output) : undefined;
|
|
380
|
+
const ok = exitCode === 0 && !aborted && !timedOut && run.stopReason !== "error" && run.stopReason !== "aborted";
|
|
381
|
+
const result: SubagentRunResult = {
|
|
382
|
+
agent: def.name,
|
|
383
|
+
source: def.source,
|
|
384
|
+
task,
|
|
385
|
+
ok,
|
|
386
|
+
output,
|
|
387
|
+
fullOutputFile,
|
|
388
|
+
error: ok
|
|
389
|
+
? undefined
|
|
390
|
+
: timedOut
|
|
391
|
+
? `timed out after ${config.subagentTimeoutSec}s`
|
|
392
|
+
: aborted
|
|
393
|
+
? "aborted"
|
|
394
|
+
: run.errorMessage || stderr.trim().slice(-4000) || `exit code ${exitCode}`,
|
|
395
|
+
model: run.model,
|
|
396
|
+
tier: choice.tier,
|
|
397
|
+
stopReason: run.stopReason,
|
|
398
|
+
usage: toUsageTotals(run.own, run.turns),
|
|
399
|
+
nested: run.nested,
|
|
400
|
+
totalUsage: run.turns ? run.total : undefined,
|
|
401
|
+
messages: run.messages,
|
|
402
|
+
worktree,
|
|
403
|
+
durationMs: Date.now() - started,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// SubagentStop with a synthetic Claude-format transcript.
|
|
407
|
+
try {
|
|
408
|
+
const lines = buildTranscriptLines({ agentName: def.name, agentId, sessionId, cwd: runCwd, prompt: task, messages: run.messages });
|
|
409
|
+
const transcript = writeTranscript(sessionId, agentId, lines);
|
|
410
|
+
await hooks.run(
|
|
411
|
+
"SubagentStop",
|
|
412
|
+
{
|
|
413
|
+
session_id: sessionId,
|
|
414
|
+
cwd: baseCwd,
|
|
415
|
+
transcript_path: transcript,
|
|
416
|
+
agent_transcript_path: transcript,
|
|
417
|
+
agent_id: agentId,
|
|
418
|
+
agent_type: `dev-team:${def.name}`,
|
|
419
|
+
stop_hook_active: false,
|
|
420
|
+
},
|
|
421
|
+
baseCwd,
|
|
422
|
+
);
|
|
423
|
+
} catch {
|
|
424
|
+
/* fail-open */
|
|
425
|
+
}
|
|
426
|
+
deps.onUsage(result);
|
|
427
|
+
if (aborted) throw new Error(`Subagent ${def.name} was aborted`);
|
|
428
|
+
return result;
|
|
429
|
+
} catch (e) {
|
|
430
|
+
if (e instanceof Error && e.message.endsWith("was aborted")) throw e;
|
|
431
|
+
return fail(e instanceof Error ? e.message : String(e), { agent: def.name });
|
|
432
|
+
} finally {
|
|
433
|
+
release();
|
|
434
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
pi.registerTool({
|
|
439
|
+
name: DEV_TEAM_SUBAGENT_TOOL,
|
|
440
|
+
label: "Dev-team subagent",
|
|
441
|
+
description: [
|
|
442
|
+
"Dispatch a dev-team agent (Claude Code's Agent/Task tool) in an isolated pi process with its own context.",
|
|
443
|
+
"The agent sees only `task`, so include every file path, diff range, scope marker and constraint it needs.",
|
|
444
|
+
"For parallel work either call this tool several times in one message or pass `tasks`.",
|
|
445
|
+
'Accepts Claude field names too: subagent_type (= agent), prompt (= task). isolation: "worktree" runs it on a new branch in its own git worktree.',
|
|
446
|
+
"Agents: any file in the package agents/ directory (e.g. software-engineer, qa-engineer, architect, security-review, test-review, spec-compliance-review, plan-review-*), project .pi/agents or .claude/agents, plus Explore and general-purpose.",
|
|
447
|
+
].join(" "),
|
|
448
|
+
promptSnippet: "Dispatch dev-team agents (Claude Agent/Task tool equivalent)",
|
|
449
|
+
// Children run with their own tools (edit, write, bash) and call a model provider.
|
|
450
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
451
|
+
parameters: SubagentParams,
|
|
452
|
+
prepareArguments: (raw: unknown) => {
|
|
453
|
+
const a = (raw ?? {}) as Record<string, unknown>;
|
|
454
|
+
// Claude Agent/Task aliases, resolved once here by the same rule the tool uses everywhere.
|
|
455
|
+
const norm = (t: Record<string, unknown>) => ({ ...t, agent: dispatchAgent(t as DispatchArgs), task: dispatchTask(t as DispatchArgs) });
|
|
456
|
+
const out: Record<string, unknown> = norm(a);
|
|
457
|
+
if (Array.isArray(a.tasks)) out.tasks = a.tasks.map((t) => norm((t ?? {}) as Record<string, unknown>));
|
|
458
|
+
return out as never;
|
|
459
|
+
},
|
|
460
|
+
async execute(_id, params, signal, onUpdate, ctx) {
|
|
461
|
+
semaphore.limit = Math.max(1, getConfig().maxParallelAgents);
|
|
462
|
+
if (deps.depth >= getConfig().maxSubagentDepth) {
|
|
463
|
+
throw new Error(`Subagent nesting limit reached (maxSubagentDepth=${getConfig().maxSubagentDepth}).`);
|
|
464
|
+
}
|
|
465
|
+
const list = (params.tasks?.length ? params.tasks : [params]) as DispatchArgs[];
|
|
466
|
+
// Project agents are skipped only when the user declined pi's own trust prompt for this
|
|
467
|
+
// project, as pi skips its other project resources. No extra prompt or setup step.
|
|
468
|
+
// One trust decision per call, shared by every child.
|
|
469
|
+
const trust = childTrustOf(ctx);
|
|
470
|
+
const { agents, skippedProjectAgents } = discoverDispatchAgents(ctx.cwd, packageRoot, trust.projectTrusted, list.map(dispatchAgent));
|
|
471
|
+
const progress = new DispatchProgress(list, skippedProjectAgents, onUpdate as DispatchUpdate | undefined);
|
|
472
|
+
const results = await Promise.all(
|
|
473
|
+
list.map(async (t, i) => {
|
|
474
|
+
const r = await runOne(t, ctx, signal, agents, (patch) => progress.update(i, patch), trust);
|
|
475
|
+
progress.finish(i, r);
|
|
476
|
+
return r;
|
|
477
|
+
}),
|
|
478
|
+
);
|
|
479
|
+
const usage = sumPiUsage(results.map((r) => r.totalUsage));
|
|
480
|
+
return {
|
|
481
|
+
content: [{ type: "text", text: formatResultText(results, skippedProjectAgents) }],
|
|
482
|
+
details: progress.snapshot(),
|
|
483
|
+
...(usage ? { usage } : {}),
|
|
484
|
+
...(results.length === 1 && !results[0].ok ? { isError: true } : {}),
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
renderCall: (args, theme) => renderSubagentCall(args as DispatchArgs & { tasks?: DispatchArgs[] }, theme),
|
|
488
|
+
renderResult: (result, options, theme) => renderSubagentResult(result, options, theme),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Live per-task views for the TUI, streamed through onUpdate while children run. */
|
|
493
|
+
export type DispatchUpdate = (r: { content: { type: "text"; text: string }[]; details: SubagentDetails }) => void;
|
|
494
|
+
|
|
495
|
+
export class DispatchProgress {
|
|
496
|
+
private readonly views: SubagentTaskView[];
|
|
497
|
+
private readonly skippedProjectAgents: string[];
|
|
498
|
+
private readonly onUpdate: DispatchUpdate | undefined;
|
|
499
|
+
constructor(list: DispatchArgs[], skippedProjectAgents: string[], onUpdate: DispatchUpdate | undefined) {
|
|
500
|
+
this.skippedProjectAgents = skippedProjectAgents;
|
|
501
|
+
this.onUpdate = onUpdate;
|
|
502
|
+
this.views = list.map((t) => ({
|
|
503
|
+
agent: dispatchAgent(t) || "(none)",
|
|
504
|
+
task: dispatchTask(t).slice(0, TASK_PREVIEW_CHARS),
|
|
505
|
+
status: "running",
|
|
506
|
+
ok: false,
|
|
507
|
+
turns: 0,
|
|
508
|
+
recentCalls: [],
|
|
509
|
+
}));
|
|
510
|
+
this.emit();
|
|
511
|
+
}
|
|
512
|
+
snapshot(): SubagentDetails {
|
|
513
|
+
return {
|
|
514
|
+
results: this.views.map((v) => ({ ...v, recentCalls: [...v.recentCalls] })),
|
|
515
|
+
...(this.skippedProjectAgents.length ? { skippedProjectAgents: this.skippedProjectAgents } : {}),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
update(taskIndex: number, patch: ProgressPatch): void {
|
|
519
|
+
this.views[taskIndex] = { ...this.views[taskIndex], ...patch };
|
|
520
|
+
this.emit();
|
|
521
|
+
}
|
|
522
|
+
finish(taskIndex: number, result: SubagentRunResult): void {
|
|
523
|
+
this.views[taskIndex] = { ...this.views[taskIndex], ...viewFromResult(result) };
|
|
524
|
+
this.emit();
|
|
525
|
+
}
|
|
526
|
+
private emit(): void {
|
|
527
|
+
this.onUpdate?.({ content: [{ type: "text", text: this.views.map(statusLine).join("\n") }], details: this.snapshot() });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function statusLine(v: SubagentTaskView): string {
|
|
532
|
+
if (v.status !== "running") return `${v.agent}: ${v.status}`;
|
|
533
|
+
const calls = recentCallLines(v);
|
|
534
|
+
const tools = calls.length ? ` → ${calls.slice(-STATUS_LINE_TOOLS).join(", ")}` : "";
|
|
535
|
+
return `${v.agent}: turn ${v.turns}${tools}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function viewFromResult(r: SubagentRunResult): Partial<SubagentTaskView> {
|
|
539
|
+
return {
|
|
540
|
+
agent: r.agent,
|
|
541
|
+
source: r.source,
|
|
542
|
+
status: r.ok ? "ok" : "failed",
|
|
543
|
+
ok: r.ok,
|
|
544
|
+
model: r.model,
|
|
545
|
+
tier: r.tier,
|
|
546
|
+
turns: r.usage.turns,
|
|
547
|
+
usage: r.usage,
|
|
548
|
+
nested: r.nested.length ? r.nested : undefined,
|
|
549
|
+
durationMs: r.durationMs,
|
|
550
|
+
stopReason: r.stopReason,
|
|
551
|
+
error: r.error,
|
|
552
|
+
output: r.output ? outputForView(r.output, r.fullOutputFile) : undefined,
|
|
553
|
+
worktree: r.worktree,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** The model-facing result text (the TUI draws `details` instead). */
|
|
558
|
+
export function formatResultText(results: SubagentRunResult[], skippedProjectAgents: string[]): string {
|
|
559
|
+
const skipped = skippedProjectAgents.length
|
|
560
|
+
? `\n\n[project agents not run (project not trusted): ${skippedProjectAgents.join(", ")}. You declined trust for this project in pi; package agents were used where they exist.]`
|
|
561
|
+
: "";
|
|
562
|
+
if (results.length === 1) {
|
|
563
|
+
const r = results[0];
|
|
564
|
+
const wt = r.worktree ? `\n\n[worktree ${describeWorktree(r.worktree)}]` : "";
|
|
565
|
+
return `${r.ok ? outputForModel(r.output || "(no output)", r.fullOutputFile) : `Agent ${r.agent} failed: ${r.error}`}${wt}${skipped}`;
|
|
566
|
+
}
|
|
567
|
+
const section = (r: SubagentRunResult) => {
|
|
568
|
+
const head = `### ${r.agent} — ${r.ok ? "completed" : "failed"}${r.model ? ` (${r.model}${r.tier && r.tier !== "inherit" ? `, tier ${r.tier}` : ""})` : ""}`;
|
|
569
|
+
const body = r.ok
|
|
570
|
+
? outputForModel(r.output || "(no output)", r.fullOutputFile)
|
|
571
|
+
: `Error: ${r.error}${r.output ? `\n\nLast output:\n${outputForModel(r.output, r.fullOutputFile)}` : ""}`;
|
|
572
|
+
const wt = r.worktree ? `\n\nWorktree: ${describeWorktree(r.worktree)}` : "";
|
|
573
|
+
return `${head}\n\n${body}${wt}`;
|
|
574
|
+
};
|
|
575
|
+
return `${results.filter((r) => r.ok).length}/${results.length} agents succeeded\n\n${results.map(section).join("\n\n---\n\n")}${skipped}`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function buildSystemPrompt(def: AgentDef, packageRoot: string, scopedBash: string[], unmapped: string[]): string {
|
|
579
|
+
const parts = [def.body.trim()];
|
|
580
|
+
const runtime: string[] = [
|
|
581
|
+
`You are the dev-team agent "${def.name}", dispatched as a subagent. Your final message is returned to the dispatcher verbatim; make it the complete deliverable (for review agents: the JSON result exactly as your output contract specifies).`,
|
|
582
|
+
`Plugin root: ${packageRoot} (also $CLAUDE_PLUGIN_ROOT in bash). Relative references like knowledge/X.md, skills/X/SKILL.md and scripts/X.py are under it.`,
|
|
583
|
+
`Tool names: Read=read, Grep=grep, Glob=find, Bash=bash, Edit=edit, Write=write, Skill=skill, Agent/Task=${DEV_TEAM_SUBAGENT_TOOL}.`,
|
|
584
|
+
`Use ${DEV_TEAM_SUBAGENT_TOOL} for dev-team dispatch, including instructions that say to use the subagent tool. Other extensions' subagent tools do not apply the dev-team tier mappings or dispatch hooks.`,
|
|
585
|
+
"You run non-interactively: never wait for a human; where instructions ask the user, take the documented non-interactive default and report it.",
|
|
586
|
+
];
|
|
587
|
+
if (def.skills.length) {
|
|
588
|
+
runtime.push(
|
|
589
|
+
`Relevant skills for this dispatch (per your frontmatter \`skills:\` list): ${def.skills.join(", ")}. Load one with the skill tool (or read ${packageRoot}/skills/<name>/SKILL.md) when the task calls for it.`,
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (scopedBash.length) runtime.push(`Only use bash for: ${scopedBash.join("; ")}.`);
|
|
593
|
+
if (unmapped.length) runtime.push(`Unavailable in this runtime (fall back as your instructions describe): ${unmapped.join(", ")}.`);
|
|
594
|
+
parts.push(`## Runtime (pi port of dev-team)\n\n${runtime.map((r) => `- ${r}`).join("\n")}`);
|
|
595
|
+
return `${parts.join("\n\n")}\n`;
|
|
596
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-text helpers for anything drawn in the terminal from untrusted text (child output, session
|
|
3
|
+
* files): sanitizers that keep that text from driving the terminal, and a width-safe cut. One copy, so
|
|
4
|
+
* a fix to what counts as an escape reaches the subagent renderer and the usage overlay alike.
|
|
5
|
+
*/
|
|
6
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
7
|
+
|
|
8
|
+
// ANSI CSI/OSC/other escape sequences, then remaining C0/C1 controls (keeping \t and \n), the
|
|
9
|
+
// interlinear annotation characters pi's sanitizeBinaryOutput also removes, and the bidirectional
|
|
10
|
+
// controls and Unicode line/paragraph separators, which could reorder or break a line. A bare \r would
|
|
11
|
+
// let a line overwrite itself, so CR and CRLF become \n first.
|
|
12
|
+
const ANSI_RE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\-_])/g;
|
|
13
|
+
const CONTROL_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f-\u202a-\u202e\u2066-\u2069\u2028\u2029]/g;
|
|
14
|
+
/** What sanitizeTerminalText keeps that a single line cannot hold. */
|
|
15
|
+
const TAB_OR_NEWLINE = /[\t\n]/g;
|
|
16
|
+
/** Every C0/C1 control, tab and newline included: in one-line text with spacing kept, a run of them becomes one space. */
|
|
17
|
+
const CONTROL_RUNS = /[\x00-\x1f\x7f-\x9f]+/g;
|
|
18
|
+
const ANNOTATION_CHARS = /[-]/g;
|
|
19
|
+
const ELLIPSIS = "…";
|
|
20
|
+
const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
21
|
+
|
|
22
|
+
/** Multi-line text with its escape sequences and controls removed; tab and newline stay. */
|
|
23
|
+
export function sanitizeTerminalText(text: string): string {
|
|
24
|
+
return text.replace(/\r\n?/g, "\n").replace(ANSI_RE, "").replace(CONTROL_RE, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One-line text (a label): sanitized, with tabs and newlines removed too. */
|
|
28
|
+
export const toSingleLine = (text: string) => sanitizeTerminalText(text).replace(TAB_OR_NEWLINE, "");
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One-line text where words must stay apart (an error message): escape sequences are removed, then
|
|
32
|
+
* each run of control characters (tab, newline, form feed, NUL...) becomes one space.
|
|
33
|
+
*/
|
|
34
|
+
export const toSpacedSingleLine = (text: string) => text.replace(ANSI_RE, "").replace(ANNOTATION_CHARS, "").replace(CONTROL_RUNS, " ");
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `text` cut to at most `max` columns, ending in an ellipsis when something was cut ("" when not
|
|
38
|
+
* even the ellipsis fits). Plain text in, plain text out: unlike pi-tui's truncateToWidth it never
|
|
39
|
+
* appends an SGR reset, so colour stays with the caller's style.
|
|
40
|
+
*/
|
|
41
|
+
export function cutToWidth(text: string, max: number): string {
|
|
42
|
+
if (visibleWidth(text) <= max) return text;
|
|
43
|
+
const room = max - visibleWidth(ELLIPSIS);
|
|
44
|
+
if (room < 0) return "";
|
|
45
|
+
let kept = "";
|
|
46
|
+
let used = 0;
|
|
47
|
+
for (const { segment } of graphemes.segment(text)) {
|
|
48
|
+
const width = visibleWidth(segment);
|
|
49
|
+
if (used + width > room) break;
|
|
50
|
+
kept += segment;
|
|
51
|
+
used += width;
|
|
52
|
+
}
|
|
53
|
+
return kept + ELLIPSIS;
|
|
54
|
+
}
|